Authentication
Login
Try it/auth/loginExchange an email and password for an access token and a refresh token.
Request
Content-Type: application/json
{
"email": "you@youragency.com",
"password": "your-password"
}Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| string | Yes | Account email address | |
| password | string | Yes | Account password |
Response
200 OK{
"access_token": "sp_at_3fJb2c1a8d4e...",
"refresh_token": "sp_rt_9c4e1b8a2f3d...",
"expires_in": 3600,
"token_type": "Bearer"
}Refresh Token
Try it/auth/refreshExchange a refresh token for a new access token before the current one expires.
Request
Content-Type: application/json
{
"refresh_token": "sp_rt_9c4e1b8a2f3d..."
}Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| refresh_token | string | Yes | A valid, unexpired refresh token |
Response
200 OK{
"access_token": "sp_at_7d2c9f4e1b8a...",
"expires_in": 3600,
"token_type": "Bearer"
}Organizations
Create Organization
Try it/v1/organizationsCreate a new organization to manage WhatsApp channels, templates, flows, and team settings.
Request
Content-Type: application/json
{
"name": "Acme Digital Agency",
"timezone": "America/Sao_Paulo",
"currency": "BRL",
"industry": "Marketing",
"website": "https://acme.digital"
}Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Name of the organization |
| timezone | string | Yes | Timezone (e.g., America/Sao_Paulo) |
| currency | string | No | Currency code (e.g., BRL, USD) |
| industry | string | No | Industry or business category |
| website | string | No | Organization website URL |
Response
201 Created{
"id": "org_9fJb2c1a8d4e",
"name": "Acme Digital Agency",
"timezone": "America/Sao_Paulo",
"currency": "BRL",
"industry": "Marketing",
"website": "https://acme.digital",
"created_at": "2025-05-21T10:15:30Z"
}List Organizations
Try it/v1/organizationsReturn every organization on the account, most recently created first.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| limit | integer | No | Max results per page (default 20, max 100) |
| starting_after | string | No | Cursor: an organization id to page after |
Response
200 OK{
"data": [
{
"id": "org_9fJb2c1a8d4e",
"name": "Acme Digital Agency",
"created_at": "2025-05-21T10:15:30Z"
},
{
"id": "org_2ab7f0c4e9d1",
"name": "Nexo Digital",
"created_at": "2025-04-02T08:41:12Z"
}
],
"has_more": false,
"total": 2
}Get Organization
Try it/v1/organizations/{id}Retrieve a single organization by id.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Organization id (path parameter) |
Response
200 OK{
"id": "org_9fJb2c1a8d4e",
"name": "Acme Digital Agency",
"timezone": "America/Sao_Paulo",
"currency": "BRL",
"industry": "Marketing",
"website": "https://acme.digital",
"created_at": "2025-05-21T10:15:30Z"
}Update Organization
Try it/v1/organizations/{id}Update one or more fields on an existing organization. Only the fields you send are changed.
Request
Content-Type: application/json
{
"website": "https://acmedigital.com"
}Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Organization id (path parameter) |
| name | string | No | New organization name |
| website | string | No | New organization website URL |
Response
200 OK{
"id": "org_9fJb2c1a8d4e",
"name": "Acme Digital Agency",
"website": "https://acmedigital.com",
"updated_at": "2025-05-22T09:03:11Z"
}WhatsApp Connect
Connect WhatsApp
Try it/v1/whatsapp/connectStart Embedded Signup for an organization and register the resulting WhatsApp number as a channel.
Request
Content-Type: application/json
{
"organization_id": "org_9fJb2c1a8d4e",
"display_name": "Acme Support"
}Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| organization_id | string | Yes | Organization this channel belongs to |
| display_name | string | Yes | Name shown to WhatsApp users |
Response
202 Accepted{
"id": "wa_12345678",
"organization_id": "org_9fJb2c1a8d4e",
"status": "pending_verification",
"signup_url": "https://api.kovalinq.com/v1/whatsapp/signup/wa_12345678"
}List Channels
Try it/v1/whatsapp/channelsReturn every connected WhatsApp channel, optionally filtered by organization.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| organization_id | string | No | Filter to one organization |
Response
200 OK{
"data": [
{
"id": "wa_12345678",
"organization_id": "org_9fJb2c1a8d4e",
"phone_number": "+5511999999999",
"status": "connected"
}
]
}Disconnect Channel
Try it/v1/whatsapp/channels/{id}Disconnect a WhatsApp channel. Message history is retained; the number stops receiving traffic.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Channel id (path parameter) |
Response
200 OK{
"id": "wa_12345678",
"status": "disconnected"
}Messages
Send Message
Try it/v1/messagesSend a WhatsApp message to a contact.
Request
Content-Type: application/json
{
"to": "+5511999999999",
"type": "text",
"text": {
"body": "Hello from SalePilot! 👋"
}
}Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| to | string | Yes | Recipient phone number in E.164 format |
| type | string | Yes | Message type: text, template, media |
| text.body | string | Yes | Message text content (max 4096 chars) |
Response
202 Accepted{
"id": "msg_8e7d6f4c2b1a",
"status": "accepted",
"to": "+5511999999999",
"type": "text",
"created_at": "2025-05-21T10:16:45Z"
}Get Message
Try it/v1/messages/{id}Retrieve a single message and its current delivery status.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Message id (path parameter) |
Response
200 OK{
"id": "msg_8e7d6f4c2b1a",
"status": "delivered",
"to": "+5511999999999",
"type": "text",
"created_at": "2025-05-21T10:16:45Z",
"delivered_at": "2025-05-21T10:16:47Z"
}List Messages
Try it/v1/messagesReturn recent messages, optionally filtered by recipient.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| to | string | No | Filter to one recipient (E.164 format) |
| limit | integer | No | Max results per page (default 20, max 100) |
Response
200 OK{
"data": [
{
"id": "msg_8e7d6f4c2b1a",
"to": "+5511999999999",
"status": "delivered",
"type": "text"
}
],
"has_more": false
}Templates
Create Template
Try it/v1/templatesSubmit a message template for WhatsApp review. Approval usually completes within minutes to a few hours.
Request
Content-Type: application/json
{
"name": "order_confirmation",
"category": "UTILITY",
"language": "pt_BR",
"components": [
{
"type": "BODY",
"text": "Seu pedido {{1}} foi confirmado."
}
]
}Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Unique template name (lowercase, underscores) |
| category | string | Yes | MARKETING, UTILITY, or AUTHENTICATION |
| language | string | Yes | WhatsApp language code (e.g., pt_BR) |
| components | array | Yes | Header, body, footer and button blocks |
Response
201 Created{
"id": "tpl_4c1a9e7d2b8f",
"name": "order_confirmation",
"status": "pending_review"
}List Templates
Try it/v1/templatesReturn every message template on the organization, with its current review status.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| organization_id | string | No | Filter to one organization |
Response
200 OK{
"data": [
{
"id": "tpl_4c1a9e7d2b8f",
"name": "order_confirmation",
"status": "approved"
}
]
}Get Template
Try it/v1/templates/{id}Retrieve a single template by id, including its full component definition.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Template id (path parameter) |
Response
200 OK{
"id": "tpl_4c1a9e7d2b8f",
"name": "order_confirmation",
"category": "UTILITY",
"language": "pt_BR",
"status": "approved"
}Flows
Create Flow
Try it/v1/flowsCreate a WhatsApp Flow — a structured, multi-screen form rendered inside the chat.
Request
Content-Type: application/json
{
"name": "Book a demo",
"categories": [
"APPOINTMENT_BOOKING"
]
}Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Internal flow name |
| categories | array | Yes | One or more Meta Flow categories |
Response
201 Created{
"id": "flow_1a9c4e7d2b8f",
"name": "Book a demo",
"status": "draft"
}List Flows
Try it/v1/flowsReturn every flow on the organization, with its current publish status.
Response
200 OK{
"data": [
{
"id": "flow_1a9c4e7d2b8f",
"name": "Book a demo",
"status": "published"
}
]
}Get Flow
Try it/v1/flows/{id}Retrieve a single flow by id, including its screen definition.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Flow id (path parameter) |
Response
200 OK{
"id": "flow_1a9c4e7d2b8f",
"name": "Book a demo",
"status": "published",
"screens": 3
}Webhooks
Register Webhook
Try it/v1/webhooksRegister a URL to receive event notifications. Every delivery is signed — verify it before trusting the payload.
Request
Content-Type: application/json
{
"url": "https://acme.digital/webhooks/salepilot",
"events": [
"message.received",
"message.status"
]
}Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | HTTPS URL to receive event deliveries |
| events | array | Yes | Event types to subscribe to |
Response
201 Created{
"id": "wh_6e1a9c4d7f2b",
"url": "https://acme.digital/webhooks/salepilot",
"events": [
"message.received",
"message.status"
],
"signing_secret": "whsec_7d2c9f4e1b8a3d6c"
}List Webhooks
Try it/v1/webhooksReturn every registered webhook endpoint on the organization.
Response
200 OK{
"data": [
{
"id": "wh_6e1a9c4d7f2b",
"url": "https://acme.digital/webhooks/salepilot",
"status": "active"
}
]
}Delete Webhook
Try it/v1/webhooks/{id}Remove a webhook endpoint. Already-queued deliveries to it are cancelled.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Webhook id (path parameter) |
Response
200 OK{
"id": "wh_6e1a9c4d7f2b",
"deleted": true
}Media
Upload Media
Try it/v1/mediaUpload an image, document, audio or video file to attach to an outgoing message.
Request
Content-Type: application/json
{
"url": "https://acme.digital/assets/catalog.pdf",
"type": "document"
}Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | Publicly reachable source URL to fetch and store |
| type | string | Yes | image, document, audio, or video |
Response
201 Created{
"id": "media_3d7f1a9c4e2b",
"type": "document",
"url": "https://api.kovalinq.com/v1/media/media_3d7f1a9c4e2b",
"expires_at": "2025-05-28T10:15:30Z"
}Get Media
Try it/v1/media/{id}Retrieve metadata and a signed download URL for a stored media object.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Media id (path parameter) |
Response
200 OK{
"id": "media_3d7f1a9c4e2b",
"type": "document",
"url": "https://api.kovalinq.com/v1/media/media_3d7f1a9c4e2b",
"size_bytes": 184320
}Events
List Events
Try it/v1/eventsReturn recent account events — the same events delivered to your webhooks, replayable on demand.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | No | Filter to one event type |
Response
200 OK{
"data": [
{
"id": "evt_5b8a2f4c9e1d",
"type": "message.received",
"created_at": "2025-05-21T10:18:22Z"
}
]
}Get Event
Try it/v1/events/{id}Retrieve a single event by id, including its full payload.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Event id (path parameter) |
Response
200 OK{
"id": "evt_5b8a2f4c9e1d",
"type": "message.received",
"created_at": "2025-05-21T10:18:22Z"
}Webhook Payload Example
/v1/webhooksWe send event notifications to your webhook URL. Example payload for a message.received event.
{
"event": "message.received",
"organization_id": "org_9fJb2c1a8d4e",
"channel_id": "wa_12345678",
"data": {
"message_id": "msg_8e7d6f4c2b1a",
"from": "+5511999999999",
"type": "text",
"text": {
"body": "Hi, I need help with pricing."
},
"timestamp": "2025-05-21T10:18:22Z"
}
}Error Codes
Every error response carries a stable error.code in addition to the HTTP status, so you can branch on the reason rather than parsing a message.
| Code | Status | Description |
|---|---|---|
| VALIDATION_ERROR | 400 | One or more request fields failed validation. |
| AUTHENTICATION_FAILED | 401 | The bearer token is missing, malformed, or expired. |
| INSUFFICIENT_SCOPE | 403 | The API key does not carry the scope this endpoint requires. |
| ORGANIZATION_NOT_FOUND | 404 | No organization exists with the given id, or it is outside this key's restrictions. |
| TEMPLATE_REJECTED | 422 | WhatsApp declined the template during review — see rejection_reason on the resource. |
| RATE_LIMITED | 429 | Too many requests for this key in the current window. Retry after Retry-After seconds. |
| WEBHOOK_UNREACHABLE | 502 | The registered webhook URL failed to respond with a 2xx within 10 seconds. |
| INTERNAL_ERROR | 500 | Something failed on our side. Safe to retry with backoff. |
Rate Limits
Limits are per API key. A request over the limit gets a 429 with RATE_LIMITED and a Retry-After header — back off and retry rather than polling immediately.
| Plan | Requests / min | Messages / sec | Webhook retries |
|---|---|---|---|
| Sandbox | 60 | 5 | 3 attempts |
| Growth | 600 | 50 | 5 attempts |
| Scale | 3,000 | 250 | 8 attempts |
| Enterprise | Custom | Custom | Custom |