API Reference

Complete reference for the SalePilot Agency API.

Base URLhttps://api.kovalinq.com

Authentication

Login

Try it
POST/auth/login

Exchange 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

NameTypeRequiredDescription
emailstringYesAccount email address
passwordstringYesAccount password

Response

200 OK
{
  "access_token": "sp_at_3fJb2c1a8d4e...",
  "refresh_token": "sp_rt_9c4e1b8a2f3d...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Refresh Token

Try it
POST/auth/refresh

Exchange a refresh token for a new access token before the current one expires.

Request

Content-Type: application/json

{
  "refresh_token": "sp_rt_9c4e1b8a2f3d..."
}

Parameters

NameTypeRequiredDescription
refresh_tokenstringYesA valid, unexpired refresh token

Response

200 OK
{
  "access_token": "sp_at_7d2c9f4e1b8a...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Organizations

Create Organization

Try it
POST/v1/organizations

Create 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

NameTypeRequiredDescription
namestringYesName of the organization
timezonestringYesTimezone (e.g., America/Sao_Paulo)
currencystringNoCurrency code (e.g., BRL, USD)
industrystringNoIndustry or business category
websitestringNoOrganization 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
GET/v1/organizations

Return every organization on the account, most recently created first.

Parameters

NameTypeRequiredDescription
limitintegerNoMax results per page (default 20, max 100)
starting_afterstringNoCursor: 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
GET/v1/organizations/{id}

Retrieve a single organization by id.

Parameters

NameTypeRequiredDescription
idstringYesOrganization 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
PATCH/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

NameTypeRequiredDescription
idstringYesOrganization id (path parameter)
namestringNoNew organization name
websitestringNoNew 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
POST/v1/whatsapp/connect

Start 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

NameTypeRequiredDescription
organization_idstringYesOrganization this channel belongs to
display_namestringYesName 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
GET/v1/whatsapp/channels

Return every connected WhatsApp channel, optionally filtered by organization.

Parameters

NameTypeRequiredDescription
organization_idstringNoFilter to one organization

Response

200 OK
{
  "data": [
    {
      "id": "wa_12345678",
      "organization_id": "org_9fJb2c1a8d4e",
      "phone_number": "+5511999999999",
      "status": "connected"
    }
  ]
}

Disconnect Channel

Try it
DELETE/v1/whatsapp/channels/{id}

Disconnect a WhatsApp channel. Message history is retained; the number stops receiving traffic.

Parameters

NameTypeRequiredDescription
idstringYesChannel id (path parameter)

Response

200 OK
{
  "id": "wa_12345678",
  "status": "disconnected"
}

Messages

Send Message

Try it
POST/v1/messages

Send a WhatsApp message to a contact.

Request

Content-Type: application/json

{
  "to": "+5511999999999",
  "type": "text",
  "text": {
    "body": "Hello from SalePilot! 👋"
  }
}

Parameters

NameTypeRequiredDescription
tostringYesRecipient phone number in E.164 format
typestringYesMessage type: text, template, media
text.bodystringYesMessage 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
GET/v1/messages/{id}

Retrieve a single message and its current delivery status.

Parameters

NameTypeRequiredDescription
idstringYesMessage 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
GET/v1/messages

Return recent messages, optionally filtered by recipient.

Parameters

NameTypeRequiredDescription
tostringNoFilter to one recipient (E.164 format)
limitintegerNoMax 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
POST/v1/templates

Submit 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

NameTypeRequiredDescription
namestringYesUnique template name (lowercase, underscores)
categorystringYesMARKETING, UTILITY, or AUTHENTICATION
languagestringYesWhatsApp language code (e.g., pt_BR)
componentsarrayYesHeader, body, footer and button blocks

Response

201 Created
{
  "id": "tpl_4c1a9e7d2b8f",
  "name": "order_confirmation",
  "status": "pending_review"
}

List Templates

Try it
GET/v1/templates

Return every message template on the organization, with its current review status.

Parameters

NameTypeRequiredDescription
organization_idstringNoFilter to one organization

Response

200 OK
{
  "data": [
    {
      "id": "tpl_4c1a9e7d2b8f",
      "name": "order_confirmation",
      "status": "approved"
    }
  ]
}

Get Template

Try it
GET/v1/templates/{id}

Retrieve a single template by id, including its full component definition.

Parameters

NameTypeRequiredDescription
idstringYesTemplate id (path parameter)

Response

200 OK
{
  "id": "tpl_4c1a9e7d2b8f",
  "name": "order_confirmation",
  "category": "UTILITY",
  "language": "pt_BR",
  "status": "approved"
}

Flows

Create Flow

Try it
POST/v1/flows

Create 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

NameTypeRequiredDescription
namestringYesInternal flow name
categoriesarrayYesOne or more Meta Flow categories

Response

201 Created
{
  "id": "flow_1a9c4e7d2b8f",
  "name": "Book a demo",
  "status": "draft"
}

List Flows

Try it
GET/v1/flows

Return 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
GET/v1/flows/{id}

Retrieve a single flow by id, including its screen definition.

Parameters

NameTypeRequiredDescription
idstringYesFlow id (path parameter)

Response

200 OK
{
  "id": "flow_1a9c4e7d2b8f",
  "name": "Book a demo",
  "status": "published",
  "screens": 3
}

Webhooks

Register Webhook

Try it
POST/v1/webhooks

Register 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

NameTypeRequiredDescription
urlstringYesHTTPS URL to receive event deliveries
eventsarrayYesEvent 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
GET/v1/webhooks

Return 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
DELETE/v1/webhooks/{id}

Remove a webhook endpoint. Already-queued deliveries to it are cancelled.

Parameters

NameTypeRequiredDescription
idstringYesWebhook id (path parameter)

Response

200 OK
{
  "id": "wh_6e1a9c4d7f2b",
  "deleted": true
}

Media

Upload Media

Try it
POST/v1/media

Upload 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

NameTypeRequiredDescription
urlstringYesPublicly reachable source URL to fetch and store
typestringYesimage, 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
GET/v1/media/{id}

Retrieve metadata and a signed download URL for a stored media object.

Parameters

NameTypeRequiredDescription
idstringYesMedia 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
GET/v1/events

Return recent account events — the same events delivered to your webhooks, replayable on demand.

Parameters

NameTypeRequiredDescription
typestringNoFilter 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
GET/v1/events/{id}

Retrieve a single event by id, including its full payload.

Parameters

NameTypeRequiredDescription
idstringYesEvent id (path parameter)

Response

200 OK
{
  "id": "evt_5b8a2f4c9e1d",
  "type": "message.received",
  "created_at": "2025-05-21T10:18:22Z"
}

Webhook Payload Example

POST/v1/webhooks

We 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.

CodeStatusDescription
VALIDATION_ERROR400One or more request fields failed validation.
AUTHENTICATION_FAILED401The bearer token is missing, malformed, or expired.
INSUFFICIENT_SCOPE403The API key does not carry the scope this endpoint requires.
ORGANIZATION_NOT_FOUND404No organization exists with the given id, or it is outside this key's restrictions.
TEMPLATE_REJECTED422WhatsApp declined the template during review — see rejection_reason on the resource.
RATE_LIMITED429Too many requests for this key in the current window. Retry after Retry-After seconds.
WEBHOOK_UNREACHABLE502The registered webhook URL failed to respond with a 2xx within 10 seconds.
INTERNAL_ERROR500Something 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.

PlanRequests / minMessages / secWebhook retries
Sandbox6053 attempts
Growth600505 attempts
Scale3,0002508 attempts
EnterpriseCustomCustomCustom
2,500+Agencies trust us
75,000+Organizations onboarded
1.2B+Messages delivered
99.99%Uptime SLA
“

SalePilot Agency’s API is clean, reliable, and developer-friendly. We integrated in a day and scaled to millions of messages effortlessly.

— Rodrigo Silva, CTO at Nexo Digital