WhatsApp API
Errors & Limits
StartMessaging WhatsApp API error codes by HTTP status, how Meta refusals map to them, when to retry, per-plan rate limits, RateLimit headers and size limits.
Error format
Every error has the same shape:
{
"statusCode": 422,
"error": {
"code": "TEMPLATE_NOT_APPROVED",
"message": "The template is not approved.",
"details": { "status": "PENDING" }
},
"meta": { "requestId": "...", "timestamp": "2026-10-02T10:00:00.000Z", "path": "/api/v1/messages" }
}
- Branch on
error.code, never onmessage. Messages are for humans and may change. detailsadds structured context for some codes (listed below).- Quote
meta.requestIdwhen you contact support. Your recent requests are also in Developers → Logs (the last 500 requests from the past 7 days).
An error’s meta also has path, the request path without its query string. Successful responses use { "data": ..., "meta": ... }, where meta has only requestId and timestamp.
Error codes
Each route’s own list of codes is in the API Reference.
400 Bad Request: fix the request
| Code | Meaning |
|---|---|
VALIDATION_ERROR | A field is missing, has the wrong type, or is not accepted on this route. |
INVALID_CURSOR | The cursor did not come from the API, or came from a different query (for example a list without updatedSince). Keep every parameter unchanged while paging. |
INVALID_IDEMPOTENCY_KEY | The Idempotency-Key is not 1–255 printable ASCII characters without spaces. |
MOBILE_INVALID | The phone number is not a valid number. |
MOBILE_COUNTRY_UNRESOLVED | The number is valid but belongs to no country (a satellite or other non-geographic number). |
WABA_NOT_CONNECTED | No WhatsApp number is connected, the connection’s token expired or lost a permission, or the sending number is no longer registered. Reconnect in the dashboard. |
BAD_REQUEST | The body is not valid JSON or could not be read (for example it ended early), or the path has a broken % escape. |
401 Unauthorized
| Code | Meaning |
|---|---|
INVALID_API_KEY | The key is missing, malformed or revoked. The answer is the same in every case. |
402 Payment Required
| Code | Meaning |
|---|---|
SUBSCRIPTION_REQUIRED | The workspace is read-only. Razorpay gave up retrying a failed renewal, so the workspace keeps its old plan but every write, sends included, is refused until billing is fixed in the dashboard. Reads still work. The same code is also answered for the few minutes between a trial or cancelled subscription ending and the workspace moving to Developer Free. |
PLAN_MESSAGE_LIMIT_REACHED | This month’s API message allowance is used up. details has limit, stopAt, sent, period and resetsAt. See Monthly API message allowance. |
META_PAYMENT_REQUIRED / META_BILLING_ERROR | WhatsApp could not bill the payment method on your own WhatsApp Business Account. Fix it in WhatsApp Manager. This is never a StartMessaging charge. |
A workspace whose trial ends, whose subscription is cancelled or ends, or whose checkout was never finished is not locked: it moves to Developer Free, and the API keeps working within that plan’s limits. A failed renewal first goes to past due and stays writable while Razorpay retries.
403 Forbidden
| Code | Meaning |
|---|---|
PLAN_FEATURE_REQUIRED | Your plan lacks the developer API, or a route’s extra feature (for example the team inbox for PATCH /conversations/{id}, interactive messages, or the sales pipeline for POST /conversions). |
PLAN_LIMIT_REACHED | A plan cap is full. details has limit and max. max_contacts: a send to a number that is not a contact yet, or POST /contacts for a new one. max_api_webhooks: POST /webhooks. Delete what you no longer need or upgrade; retrying will not help. |
INSUFFICIENT_SCOPE | The key lacks the route’s scope. details has required and granted. |
MESSAGE_WINDOW_CLOSED | A free-form message outside the 24-hour window. Send a template. |
WABA_SUSPENDED / WABA_RESTRICTED | Meta suspended the WhatsApp Business Account, or restricted it for a policy violation. |
RECIPIENT_COUNTRY_RESTRICTED | Meta does not let this account send this kind of message to the recipient’s country. |
404 Not Found
| Code | Meaning |
|---|---|
CONTACT_NOT_FOUND, CONVERSATION_NOT_FOUND, MESSAGE_NOT_FOUND, TEMPLATE_NOT_FOUND, CAMPAIGN_NOT_FOUND, WEBHOOK_NOT_FOUND, PIPELINE_STAGE_NOT_FOUND, WORKSPACE_NOT_FOUND | It does not exist, or belongs to another workspace. The API never says which. |
NOT_FOUND | No such route. Check the method and the path. |
409, 413, 415
| Code | HTTP | Meaning |
|---|---|---|
IDEMPOTENCY_KEY_IN_USE | 409 | A request with this key is still running. Retry shortly. |
PAYLOAD_TOO_LARGE | 413 | The body is over the limit: 100 KB for JSON. details.maxBytes gives the limit. |
UNSUPPORTED_MEDIA_TYPE | 415 | The body uses a character set or content encoding the API cannot read. Send UTF-8 JSON. |
422 Unprocessable: the request cannot be done as asked
| Code | Meaning |
|---|---|
CONTACT_OPTED_OUT | The contact replied STOP, turned off your marketing messages in WhatsApp, or you recorded optedIn: false. They must opt back in first. Deleting the contact does not change this: the number stays opted out. |
MESSAGE_UNDELIVERABLE | WhatsApp would not deliver to this number (not on WhatsApp, or Meta’s per-customer marketing limit). |
MESSAGE_REJECTED | WhatsApp refused the request itself, for example the wrong number of template variables. The same request fails the same way. details.metaCode is Meta’s error number. |
TEMPLATE_NOT_APPROVED | The template is not approved. details.status gives its status. |
TEMPLATE_HEADER_MEDIA_REQUIRED | The template has an image, video or document header. Send the file’s URL as template.headerMediaUrl. |
TEMPLATE_BUTTON_PARAM_REQUIRED | A template button needs its value on every send (a link with a variable, a coupon code). Send it in template.buttonParameters. On an authentication template it means template.parameters was empty too: put the code in parameters. WhatsApp Form (flow) buttons never cause it. details lists each missing button’s index and buttonType. |
TEMPLATE_CAROUSEL_CARDS_INCOMPLETE | A carousel was sent without everything each card needs, or with a different number of cards than it was created with. details.expected is the card count, details.received what you sent, and details.problems lists each gap as card (from 1) and missing (media, variables or button, with buttonIndex). |
TEMPLATE_OFFER_EXPIRY_REQUIRED | A limited-time offer that shows its end time was sent without template.offerExpiresAt. |
TEMPLATE_OFFER_EXPIRY_INVALID | template.offerExpiresAt has already passed. Send a future time. |
TEMPLATE_VARIABLES_NOT_REGISTERED | Meta registered this template without its variables, so it can’t be filled in. Duplicate it in the dashboard and send the copy. |
IDEMPOTENCY_KEY_REUSED | The Idempotency-Key was used with a different body or route, or by another of your API keys. |
ASSIGNEE_NOT_MEMBER | The assignee is not an active member who may be assigned. |
WEBHOOK_URL_NOT_ALLOWED | The webhook URL is not allowed. details.reason says why. See Webhooks. |
429 Too Many Requests
| Code | Meaning |
|---|---|
RATE_LIMIT_EXCEEDED | You hit your plan’s API limit. details has limit, windowSeconds and scope (requests or sends). Retry-After is exact. |
PHONE_DAILY_LIMIT_REACHED | A WhatsApp-side limit on the sending number. Try later. Retry-After (60 seconds) is the least time to wait, since WhatsApp does not say when its limits lift. |
502 Bad Gateway
| Code | Meaning |
|---|---|
WABA_CONNECT_FAILED | A transient WhatsApp failure (a 5xx, throttling, maintenance or no answer). Whether the message went is unknown. Retry later with the same Idempotency-Key. |
BAD_REQUEST, NOT_FOUND, PAYLOAD_TOO_LARGE and UNSUPPORTED_MEDIA_TYPE are answered before the API key is read, so a request that also has a bad key gets them rather than 401.
When WhatsApp refuses a send
When Meta refuses a send, you get one of the codes above, and on MESSAGE_REJECTED Meta’s number as details.metaCode, so you can look it up in Meta’s error code reference. Only a payment-method failure is ever reported as a payment problem.
| Meta code | Meaning | API answer |
|---|---|---|
| 131042 | Payment method problem on your WhatsApp Business Account | 402 META_PAYMENT_REQUIRED |
| 131047 | More than 24 hours since the customer last replied | 403 MESSAGE_WINDOW_CLOSED (send a template) |
| 368 | Account restricted for a policy violation | 403 WABA_RESTRICTED |
| 131031 | Account locked | 403 WABA_SUSPENDED |
| 130497 | Not allowed to message users in that country | 403 RECIPIENT_COUNTRY_RESTRICTED |
| 131026, 131049 | Undeliverable, or Meta’s per-customer marketing limit | 422 MESSAGE_UNDELIVERABLE |
| 131050 | The customer stopped your marketing messages | 422 CONTACT_OPTED_OUT |
| 130429, 131048, 131056, 131064 | Throughput, spam-quality, same-recipient and template-category limits | 429 PHONE_DAILY_LIMIT_REACHED |
| 190, 0, 3, 10, 131005, 200–299 | The connection’s access token expired, was revoked or lost a permission | 400 WABA_NOT_CONNECTED (reconnect in the dashboard) |
| 33, 133010 | The sending number was deleted, or is no longer registered on the Cloud API | 400 WABA_NOT_CONNECTED (reconnect in the dashboard) |
| 100, 131008, 131009, 131051, 131053, 132000, 132001, 132005, 132012, 132015, 132016 | The request itself: an invalid or missing value, an unsupported message type, media Meta could not fetch, the wrong number of template variables, a template missing in that language, too long once filled in, or paused or disabled for quality | 422 MESSAGE_REJECTED (details.metaCode) |
| 131057 | The account is in maintenance | 502 WABA_CONNECT_FAILED (retry later) |
Any other refusal Meta marks final is also 422 MESSAGE_REJECTED with details.metaCode. Anything transient (a 5xx, throttling, a timeout, no answer) is 502 WABA_CONNECT_FAILED.
A failure Meta reports later, on a delivery receipt, arrives as a message.failed webhook with Meta’s own failureCode instead.
When to retry
| Response | What to do |
|---|---|
| 429 | Wait for Retry-After seconds, then retry with the same Idempotency-Key. |
5xx (including 502 WABA_CONNECT_FAILED) | Retry later, with backoff, with the same Idempotency-Key. |
409 IDEMPOTENCY_KEY_IN_USE | Retry shortly with the same key. |
| Any other 4xx | Do not retry unchanged. It fails the same way until the request, the plan or the account changes. |
For a code you do not recognise, decide by its HTTP status using this table. A 400 releases the Idempotency-Key, so after you fix the problem (or reconnect WhatsApp), a retry with the same key really sends. A 422 is replayed. See Pagination & Idempotency.
Rate limits
Limits are per workspace, by plan. More API keys never buy more throughput.
| Plan | Requests / minute | Messages sent / hour |
|---|---|---|
| Free trial | 300 | 100 |
| Developer Free | 60 | 2,000 |
| Developer 1K | 120 | 10,000 |
| Developer 10K | 300 | 10,000 |
| Developer 100K | 1,200 | 10,000 |
| Team | 300 | 10,000 |
| Business | 1,200 | 10,000 |
| Enterprise | 1,200 | 10,000 |
- Every counted request spends the per-minute request budget.
- Sends (
POST /api/v1/messages,POST /api/v1/trigger/send) also spend the hourly send budget. A send refused with a 4xx (by us or by Meta) gives it back; a 5xx keeps the charge. - Windows are fixed and aligned to the clock in UTC. The hourly window reopens at the top of each UTC hour (half past the hour in India).
GET /api/v1/meshows your plan’s numbers and live usage of both budgets.
Rate-limit headers
Every response to a counted request, success or error, carries:
| Header | Meaning |
|---|---|
RateLimit-Policy | Every window the request counted against, such as 60;w=60 (requests per minute) and, on a send, 100;w=3600 (sends per hour), with your plan’s numbers. |
RateLimit-Limit | The limit of the window that will refuse you first. |
RateLimit-Remaining | What is left in that window. |
RateLimit-Reset | Whole seconds until that window reopens (never 0). |
Retry-After | On every 429: seconds to wait. |
HTTP/1.1 429 Too Many Requests
RateLimit-Policy: 60;w=60, 2000;w=3600
RateLimit-Limit: 2000
RateLimit-Remaining: 0
RateLimit-Reset: 1260
Retry-After: 1260
Requests refused before they are counted carry none of these headers: 401 INVALID_API_KEY, 402 SUBSCRIPTION_REQUIRED, 403 INSUFFICIENT_SCOPE, 403 PLAN_FEATURE_REQUIRED for a plan without the API, the team inbox (PATCH /conversations/{id}) or the sales pipeline (POST /conversions), 404 WORKSPACE_NOT_FOUND, and the answers given before the key is read (400 BAD_REQUEST, 404 NOT_FOUND, 413, 415). The 403 PLAN_FEATURE_REQUIRED for interactive messages is checked after the rate limit, so it is counted and carries the headers. Read the headers when they are present and never treat a missing one as zero.
Monthly API message allowance
Each plan also includes a number of messages a month sent through the API. Only sends WhatsApp accepted count; a refused send gives its slot back. The month runs on India time and starts again on the 1st.
| Plan | API messages / month | When sends stop |
|---|---|---|
| Free trial | 1,000 | At 1,000, authentication templates included |
| Developer Free | 100 | At 100, authentication templates included |
| Developer 1K | 1,000 | At 3,000 (3× the allowance); authentication templates never stop |
| Developer 10K | 10,000 | At 30,000; authentication templates never stop |
| Developer 100K | 100,000 | At 300,000; authentication templates never stop |
| Team, Business | Unlimited (fair use) | Never |
Past the stop point, sends answer 402 PLAN_MESSAGE_LIMIT_REACHED with details.sent, details.stopAt and details.resetsAt. Nothing upgrades or charges by itself. GET /api/v1/me returns the same numbers in messages, so you can warn your own users before sends stop.
Meta’s messaging limit
Meta’s own messaging limit applies on top of ours: the number of unique customers your business can message in a rolling 24 hours. It is set per business portfolio and shared by all its numbers. Your current limit and quality rating are in the dashboard and in GET /api/v1/phone-numbers. Hitting a WhatsApp-side limit returns 429 PHONE_DAILY_LIMIT_REACHED.
Message and template limits
| Limit | Value |
|---|---|
| Text message body | 4,096 characters |
| Template body | 1,024 characters |
| Template header and footer | 60 characters each |
| Template buttons | Up to 10 (at most 2 URL buttons and 1 phone button) |
Coupon code (couponCode) | 15 characters (WhatsApp’s limit) |
| Carousel | 2–10 cards; card text up to 160 characters; up to 2 buttons per card |
| Interactive reply buttons | Up to 3 (titles up to 20 characters) |
| Interactive list rows | Up to 10 (row and section titles up to 24 characters; the list’s buttonLabel up to 20) |
| Media URLs | Must be https:// (Meta fetches the file) |
| JSON request body | 100 KB |
Idempotency-Key | 1–255 printable ASCII characters, no spaces; honoured for 24 hours |
clientReference | 1–256 printable ASCII characters (spaces allowed) |
Free-form messages (text, media, interactive) reach a customer only within 24 hours of their last message. Outside the window, send an approved template.
Webhook limits
| Limit | Value |
|---|---|
| Endpoints | Free trial 5, Developer 2, Team 1, Business 5, Enterprise unlimited |
| URL | HTTPS, port 443, public address, up to 2,048 characters |
| Response time | Answer 2xx fast. 5 seconds each to connect, to send the response headers and between parts of the body; 10 seconds in all per request |
| Retries | 1 min, 5 min, 30 min, 2 h, 6 h, 12 h |
| Auto-disable | After 50 exhausted deliveries in a row (each used all 7 attempts), or after 24 hours of failing with at least 3 exhausted |
FAQ
Why do I get 401 for every request?
The key is missing, malformed or revoked. Send Authorization: Bearer sm_live_... with a key from Developers → API keys. The API does not say which of these it was.
Is it safe to retry a send that timed out?
Yes, if you sent an Idempotency-Key. Retry with the same key: if the first request went through, you get its original response back and nothing is sent twice.
Do more API keys raise my rate limit? No. Limits are per workspace. Upgrade your plan for higher limits.
I got 402 META_PAYMENT_REQUIRED. Do I owe StartMessaging money? No. Meta could not charge the payment method on your own WhatsApp Business Account. Fix it in WhatsApp Manager.
I got 402 PLAN_MESSAGE_LIMIT_REACHED. What now?
This month’s API messages are used up. details.resetsAt says when the count starts again. To send more before then, choose a bigger Developer size or the Team or Business plan. On a paid Developer plan, authentication templates (OTPs) keep sending.
My subscription ended. Does the API stop?
No. A workspace whose trial or subscription ends unpaid moves to Developer Free, and the API keeps working within its limits. Only a renewal Razorpay gives up retrying makes the workspace read-only (402 SUBSCRIPTION_REQUIRED on writes) until billing is fixed. Writes can also get that 402 for a few minutes after a trial or cancelled period ends, until the move to Developer Free.
What does details.metaCode mean on MESSAGE_REJECTED? It is Meta’s own error number. Look it up in Meta’s error code reference to see exactly what WhatsApp objected to.