Limits and errors
Rate limits, daily quotas, response headers, the error format and the list of error codes.
No credits and no per-call billing: the API is included in the subscription. Usage is bounded by limits instead.
Rate limits
| What | Limit |
|---|---|
Reads (GET) | 120 requests per minute per key |
Writes (POST, PUT, PATCH, DELETE) | 30 requests per minute per key |
PUT /v1/sequences/sync | 10 calls per minute per project |
| Whole API | 1,000 requests per minute across all keys, as a circuit breaker |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. GET /me returns the limits that apply to your key under limits. A 429 carries Retry-After in seconds: wait that long, then retry.
Daily quotas (scope send)
| What | Quota |
|---|---|
| One-off messages | 30 per day per project |
| Broadcasts | 8 per day per project, 200 communities each |
| Media uploads | 50 per day per project |
Quotas count API calls only, not what you do in the app. They protect the reputation of the WhatsApp number. Broadcasts take 1 to 200 communities each.
Other limits
| What | Limit |
|---|---|
| API keys | 5 active per project |
| Webhook endpoints | 5 per project |
| Messages per sequence | 200 |
| Sequences per account | 200 |
| Text of a message | 10,000 characters |
| Media URL | 2,000 characters |
| Poll | question 255 characters, 2 to 12 options of 100 characters |
| Media | image 16 MB (jpg, jpeg, png), video 40 MB (mp4, mov, 3gp), voice 16 MB (ogg, m4a, wav, mp3), document 100 MB; import from URL 40 MB |
| Backup admin selection | 250 communities per confirmation; apply and retry take one community unless bulk apply is enabled (otherwise 409 bulk_apply_disabled) |
| Pagination | see below |
Error format
{ "error": { "code": "validation_error", "message": "timing must match J-1 10:00 or H-1", "field": "messages[1].timing" } }code is stable and meant for your code. message is for humans and can change. field is present when the error concerns one field.
Error codes
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_json | Request body is not valid JSON |
| 401 | unauthorized | Missing token |
| 401 | invalid_api_key | Invalid, revoked or expired API key or OAuth token |
| 403 | api_not_enabled | API not enabled on the project |
| 403 | insufficient_scope | Token lacks the scope |
| 403 | forbidden | Broadcast refused because the account cannot send (billing or automation blocked), see message |
| 404 | not_found | Unknown id, or id of another project |
| 409 | conflict | Action refused in the current state (stopped event, send no longer pending, broadcast already started, sequence still linked to an event, number not connected…), see message |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still being processed, or ended without a known outcome. See Retry-After, and check the result before sending again with a new key |
| 409 | bulk_apply_disabled | Backup admin apply or retry on several communities while bulk apply is off |
| 409 | community_update_not_editable | A community update step cannot be edited for one occurrence |
| 400 | validation_error | Body failed validation, see field |
| 400 | nonexistent_local_time / ambiguous_local_time | Local time falls in a DST gap or overlap |
| 400 | malformed_scheduled_at | Broadcast date malformed |
| 400 | scheduled_at_not_future | Broadcast time not in the future |
| 400 | invalid_time / invalid_calendar | Occurrence: scheduled_at_local is not a real date or time |
| 413 | payload_too_large | Request body over 4 MiB |
| 422 | idempotency_key_reused | Same Idempotency-Key with a different body |
| 429 | rate_limited | Rate limit or daily quota reached, see Retry-After |
| 500 / 503 | internal_error | Error on our side. A 5xx raised once the request is being processed keeps the Idempotency-Key on purpose, for up to 24 hours: a retry with the same key answers 409 idempotency_in_progress with Retry-After, never a second send. After a 5xx on a write (except rate_limit_unavailable, which can be retried), check the result first, then send again with a new key only if nothing happened |
| 503 | rate_limit_unavailable | Rate limiter unavailable, retry after Retry-After |
| 503 | channel_unavailable | The communities of the number could not be listed, nothing was created. After Retry-After, send again, with a new Idempotency-Key if you set one |
| 4xx / 503 | invalid_e164, not_whatsapp_user, contact_not_saved, same_as_primary, stale_preview, preview_expired, provider_unavailable… | Backup admin: number or preview refused, see message |
Idempotency
Send an Idempotency-Key header (1 to 128 characters: letters, digits, -, _, . or :; a UUID is fine) on POST, PUT and PATCH. For 24 hours, the same key with the same body returns the stored response; the same key with another body returns 422 idempotency_key_reused.
Pagination
Every list returns its elements under items. Three styles, chosen per endpoint and visible in the reference:
| Endpoints | Style |
|---|---|
GET /events/{id}/jobs, GET /jobs/{id}/deliveries | Cursor: limit (100 by default, 500 max) and cursor; the response carries next_cursor, null on the last page. A cursor is only valid with the same order. |
GET /events/{id}/occurrences, GET /broadcasts/{id}/recipients | Pages: page (from 1); the response carries page, page_size, total (and counts per filter for occurrences). Recipients come 50 per page. |
GET /notifications | limit (20 by default, 100 max), offset, unread_only. |
GET /events, GET /communities, GET /sequences and GET /broadcasts return the whole list.
Your current limits are also returned by GET /me under limits.
Versioning
Everything lives under /v1. Fields can be added at any time; your parser must ignore unknown fields. Removing or renaming anything means a /v2, announced in the changelog.