API reference

Errors and idempotency

The error envelope, status codes, retry-safety and rate limits.

Errors and idempotency

Error format

Every failure on a matched /api/v1 route returns JSON. No stack traces, class names or file paths reach you — those are recorded server-side, whatever the server's debug setting.

{ "message": "The connection was not found." }

Validation failures add a per-field breakdown:

{
  "message": "The given data was invalid.",
  "errors": {
    "connection_id": ["The connection id field is required."]
  }
}
Status Meaning What to do
401 Missing, invalid or revoked key Check the Authorization header
403 Key valid, scope or workspace not permitted Check the key's scopes
409 Idempotency conflict See below
422 Validation failed, or the request cannot be carried out Read the message
429 Rate limited Back off and retry
5xx Our fault Retry with backoff; safe if you sent an idempotency key

422 is broader than validation

It covers both "your fields are wrong" and "your fields are fine but this cannot be done". The first form carries errors; the second is a bare message:

{ "message": "The selected connection is not ready to send." }

Others you will meet: The connection was not found. · The contact was not found. · A valid E.164 phone number is required. · The selected media object is not available for sending. · The contact has opted out of outbound messages. · The contact is currently suppressed from outbound messages.

A missing id is not always 404. Media has its own routes, so an unknown media id returns 404. A connection, contact or media_id referenced inside a send is validated by the service and returns 422 — the request was well-formed, it just cannot be carried out.

Treat any 422 without an errors object as "well-formed but not possible", and read the message rather than inferring the cause from the status alone.

Malformed JSON

A truncated or invalid JSON body is parsed as an empty request rather than rejected outright, so you get a 422 about missing fields rather than a 400 about syntax. If you see required-field errors for fields you know you sent, check that your body is valid JSON and your Content-Type is application/json.

Idempotency

Send an Idempotency-Key header so a retry cannot repeat an action.

POST /api/v1/messages
Idempotency-Key: order-4821-confirmation

Applies to POST /contacts and POST /messages. It is optional — omit it and each request is treated as new. POST /media ignores the header; uploading the same file twice simply produces two media objects, which is harmless.

The key must be 8–255 characters of A–Z a–z 0–9 . _ : -. Anything else is rejected with 422. Use something derived from the thing you are acting on — an order id, a job id, a UUID you generate and store — so a retry after a timeout naturally reuses it.

Keys are scoped to the API key, method and path. The same value on POST /messages and POST /contacts never collides.

What happens on a repeat

Case Result
Same key, same body Original status and body replayed, with Idempotency-Replayed: true
Same key, different body 409 — the key is bound to a different request
Request still in flight 409 The idempotent request is still in progress.
Previous attempt threw The key is released and can be reused

A replay is the case you are protecting against: your process died after we accepted the message but before you recorded it, and your retry must not produce a second WhatsApp message.

Check for Idempotency-Replayed: true if you need to tell a fresh action from a replayed one.

How a repeat is recognised

We fingerprint the parsed request — the endpoint, the field values sorted by key, and a content digest of any uploaded file. Two requests match when they would do the same thing, regardless of JSON key order or how the body was encoded.

File uploads are covered by content, not filename: re-uploading identical bytes under a different filename is the same request.

It also makes failures clean

A request sent with an idempotency key runs inside a database transaction, so a failure rolls back everything it touched. The same request without one can leave a partially-applied side effect behind — a contact created for a message that was then refused, for instance.

Send the header on every write you care about.

Was this guide useful? Report an issue