Appearance
Errors and limits
Errors are JSON: { "error": "human readable", "code": "MACHINE_CODE" }. Branch on code, not the message.
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
| 400 | BAD_REQUEST | Missing or invalid fields, or text too long. | Fix the request. |
| 400 | BAD_PHONE | Missing or invalid phone number. | Use international format. |
| 401 | Missing or wrong key. | Check the header and key. | |
| 404 | NOT_FOUND | Unknown route or app. | |
| 404 | NO_MEDIA | The message has no media. | |
| 409 | NOT_READY | The app is not connected. | Retry later; check /api/status. |
| 409 | APP_LIMIT | A provisioning key reached maxApps. | |
| 413 | TOO_LARGE | Body over 25 MB. | |
| 422 | NOT_REGISTERED | The number is not on WhatsApp. | Do not retry. |
| 422 | BAD_MEDIA, BAD_RECIPIENT | Unusable media or recipient. | |
| 429 | RATE_LIMITED | Rate limited (provisioning creates). Message sends return 429 too. | Wait for Retry-After. |
| 500 | INTERNAL | Unexpected server error. | Retry with backoff. |
Rate limits
POST /api/messages and POST /api/messages/media use a per-app token bucket, default 30 requests per minute (RATE_LIMIT_PER_MINUTE). Over the limit you get 429 with Retry-After and X-RateLimit-Remaining headers. Raise it for one app with PATCH /api/apps/:id {"rateLimitPerMin": 120} (null resets to the default).
Provisioning keys are also limited to PROVISION_CREATES_PER_HOUR new apps per hour.
Retry rules of thumb
- Retry
409 NOT_READY,429and500with backoff. - Never retry
422 NOT_REGISTERED. - Never resend automatically after
status: "unconfirmed".