Skip to main content
Every failure comes back in the same shape, whatever went wrong:
Branch your code on error.code and the HTTP status. error.message is written for a human reading a log and its wording can change without notice.
Some errors add a details object with extra machine-readable context — for example retry_after_seconds on a rate limit. Where that happens, it is noted below. Every response, success or failure, carries a request_id. Log it. It is the one thing that lets support find your exact request.

Handle these three first

Almost every integration only really needs to get three cases right:

409 instance_not_connected

Stop and alert a human. Retrying cannot help — somebody has to re-scan the QR. What to do

429 rate_limit_exceeded

Wait, then retry. The Retry-After header tells you how many seconds.

422 invalid_recipient

Skip this recipient. The number is not usable on WhatsApp. Do not retry it.

Full error reference

Authentication — 401

If you drive the instance endpoints from a signed-in portal session instead of a key, you may also see missing_session and invalid_session. Those never appear on the apikey path described here.

Billing and access — 402, 403

The request itself — 400, 404, 413, 422

Connection — 409

Rate limit — 429

Our side — 500, 502, 503

This page covers every error reachable through the public API described on this site. The portal has some additional administrative error codes that no API key can trigger; they are deliberately not listed here.

Telling the two number limits apart

instance_limit_reached covers two different situations, on purpose — to you both mean “you cannot add another number”. But the remedies differ, so details.limit_source tells you which one you hit: What each plan allows is on Plans and limits.
Branch on the field, never on the wording
Never match on error.message to tell them apart. The wording can change; error.code and details.limit_source are the stable contract.
details.limit_source also appears on admin_free_slot_limit_reached, where it reads free_slots. That code only ever reaches a platform operator’s own account, so a customer integration will only see plan or account — but branch with a default case rather than an if/else, and a third value can never surprise you.

Rate limits

Sending is limited per minute, per API key.
  • One allowance is shared by every message type and the number check. It is one budget, not one per endpoint.
  • The instance endpoints (/v1/instances/...) have their own separate allowance of 120 requests per minute — comfortable for polling a QR every couple of seconds — so managing numbers never eats into your sending budget.

The headers

Any request that reaches a rate limiter carries all four X-RateLimit-* headers — on success as well as on a 429. Retry-After is the exception and appears only on a 429.
Always read the scope before caching a limit. The two budgets are different sizes: managing numbers allows 120 a minute, sending usually far fewer. A client that reads X-RateLimit-Limit: 120 off a GET /v1/instances call and applies it to sending will overrun its real send budget and not understand why.
Read X-RateLimit-Limit rather than hardcoding a number — your send allowance comes from your plan and can change without any change to this API. Plans and limits sets out what each plan allows and where to read your own figures. Requests rejected before the limiter — a missing or invalid key, for instance — carry no X-RateLimit-* headers at all.

About X-RateLimit-Reset

The window is a rolling minute, not a fixed one that empties on the hour. There is no single instant when your count drops to zero, so X-RateLimit-Reset reports the useful thing instead: seconds until the oldest request ages out, which is when X-RateLimit-Remaining next goes up by one.
  • If X-RateLimit-Remaining is above 0, you can send now — you do not need to wait for the reset.
  • If it is 0, waiting X-RateLimit-Reset seconds gets you at least one more request.
  • On a 429, X-RateLimit-Reset and Retry-After are the same number.

A 429 body

Pacing a list

Do not fire your whole list at once and mop up the 429s afterwards. Space the sends out from the start — it is gentler on your allowance and much gentler on your WhatsApp number’s standing.
Node.js — respect the headers

What to retry, and what not to

Make retries idempotent on your side. This API has no idempotency key, so a retry after a network timeout can genuinely send the message twice. Record the message_id you get back and skip anything already recorded as sent.