Errors
Errors use conventional status codes with a machine-readabledetail:
Structured errors always carry a stable
code you can branch on; extra keys (like conflicting_contact_id on a duplicate contact) give you what you need to resolve it. Every error shape is published in the API reference.
Sending a template
POST /v1/templates/{id}/send refuses the whole request rather than queuing part of it, so a rejection never leaves you half-sent and never costs you anything. The codes worth branching on:
GET /v1/templates/{id}/send-requirements tells you what a template needs before you send, so most of these never have to happen.
Rate limits
Each organization has a shared budget of 20 requests per second across all its keys — 5 per second during the free trial. Over the budget, requests return429 with a Retry-After header (seconds; always 1, since the window is one second).
Every response — including the 429 — carries your current budget, so you can pace without counting calls yourself:
Read
X-RateLimit-Remaining and slow down as it approaches zero rather than sprinting into the 429. If your integration genuinely needs more, talk to us.
One request is one request no matter how much it does.
POST /v1/templates/{id}/send counts once whether it carries one recipient or a hundred — so batching recipients into a single send is the cheapest way to stay inside the budget. What bounds actual message volume is WhatsApp’s own rolling 24-hour limit, not this one.Retrying safely
POST /v1/templates/{id}/send accepts an Idempotency-Key header. Send the same key again within 24 hours and you get the original broadcast back instead of a second send — which is what you want after a timeout, when you genuinely cannot tell whether the request landed. Use a fresh key for each distinct send.
Pagination
List endpoints return:next_cursor back as ?cursor= to fetch the next page; a null cursor means you’ve reached the end. Cursors are opaque — don’t parse them.
Property and funnel-stage lists aren’t paginated: they return your full set, with no next_cursor.
Compatibility
This is an early, fast-moving API — expect it to evolve. Two habits keep your integration stable:- Most changes are additive: new endpoints, new optional parameters, new response fields, new values in string fields. Tolerate unknown fields and values — ignore what you don’t recognize.
- Every contract change is listed in the changelog — check it when something looks different.