Skip to content

Errors, limits and idempotency

Every error of the WhatSetter API has the same shape and a stable code. This page lists them all, the headers that describe your limits, and what to do when a call fails.

{
"error": {
"code": "insufficient_scope",
"message": "This API key is missing the \"messages:send\" scope."
}
}
  • code is stable: branch your code on it.
  • message is for humans and can change. Log it, do not parse it.
  • The HTTP status matches the code (see the table). Success responses use { "data": … }, and lists add { "pagination": { "has_more", "next_cursor" } }.
HTTP code When it happens What to do
400 validation_error The path contains malformed URL encoding (for example %ZZ). Fix the URL.
401 missing_api_key No Authorization: Bearer header and no X-Api-Key header. Send the key.
401 invalid_api_key The key is unknown, revoked, or not in the ws_live_ format. Check the key, or create a new one.
402 plan_limit_reached POST /campaigns/{id}/resume when your plan’s pool of active campaigns is full. Pause another campaign, or upgrade.
403 insufficient_scope The key lacks the scope this endpoint needs. The message names it. Create a key with that scope.
404 not_found Unknown route, or the resource does not exist, or it belongs to another workspace. Check the id and the path.
409 invalid_state Pausing a campaign that is not active, resuming one that is not paused, adding a lead to a campaign that is not active, or handing over a lead that has no conversation yet. Read the message and change the state in the dashboard.
409 lead_not_contacted POST /messages on a lead nobody has messaged yet. First contact goes through a list and a campaign.
409 request_in_flight A request with the same Idempotency-Key is still being processed. Wait a few seconds, then retry with the same key.
409 already_blocked POST /blocked-contacts for a number or prefix already in the list. Nothing: it is already blocked.
422 validation_error A parameter or a body field is missing or invalid: limit outside 1 to 100, a date that is not ISO 8601, an empty text, an unknown member id in assigned_to. Fix the request. Retrying as is will fail again.
429 rate_limited More requests per minute than your plan allows. Wait Retry-After seconds, then retry.
429 quota_exceeded The daily WhatsApp quota of the sending number is reached (it resets at midnight UTC), or a workspace cap is reached: 200 lists, 10 webhook subscriptions, 500 blocked contacts. Wait for X-Quota-Reset, or delete unused items.
500 internal_error An unexpected error on our side, or the messaging backend is not configured. Retry later. Contact support if it persists.
500 server_error Agent control is not configured on this deployment (handoff, resume). Contact support.
502 send_failed The WhatsApp engine did not confirm the send. Retry with the same Idempotency-Key. Without a key, read the conversation before sending again.
502 upstream_error The workflow behind handoff, resume or POST /campaigns/{id}/leads rejected the call or was unreachable. Retry in a moment.
503 agent_disconnected The campaign’s WhatsApp number is disconnected, or no number is attached to it. Reconnect the number in the dashboard.
503 service_unavailable The authentication backend is unavailable. Retry in a few seconds.

Requests are counted per workspace and per minute, all keys combined, with a limit set by your plan (60 to 1000 per minute, see Authentication and permissions). Every response carries:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 2026-09-19T10:01:00.000Z

When the limit is exceeded, the response is 429 rate_limited and adds Retry-After in seconds:

HTTP/1.1 429 Too Many Requests
Retry-After: 23

Sending messages has a second, separate budget: the daily quota of the WhatsApp number, the same one the campaign engine uses (see anti-ban protection). A successful POST /messages reports it:

X-Quota-Limit: 150
X-Quota-Remaining: 149
X-Quota-Reset: 2026-09-20T00:00:00.000Z

The same numbers are in the body under data.quota (limit, used, remaining, resets_at). When the quota is reached you get 429 quota_exceeded. It resets at midnight UTC. quota_exceeded is also used for the workspace caps listed in the table above.

POST /messages is retry-safe when you send an Idempotency-Key header: any unique string of at most 200 characters, for example a UUID per send intent.

Terminal window
curl -X POST https://app.whatsetter.com/api/v1/messages \
-H "Authorization: Bearer $WHATSETTER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4d3f9b2a-7c1e-4a8b-9f0d-2e6c8a1b3d5f" \
-d '{"lead_id":"66f1a2b3c4d5e6f7a8b9c0d1","text":"Are we still on for tomorrow at 2pm?"}'

What the key does:

  • A key that already produced a response replays it with the header X-Idempotent-Replay: true. No second message is sent.
  • A retry fired while the first request is still running gets 409 request_in_flight. Wait a few seconds and retry with the same key.
  • If a send times out or answers 502 send_failed, retrying with the same key is safe: WhatSetter either replays the recorded result or answers 409 while it settles. It never sends twice.
  • Keys are scoped to your workspace and remembered for 15 minutes.

Every response carries X-Api-Version: 2026-07, the version of the REST contract. Webhook payloads have their own api_version field (2026-06-01), described on the webhooks page.

Response Retry? How
429 rate_limited Yes Wait Retry-After seconds.
429 quota_exceeded on POST /messages Yes, tomorrow Wait for X-Quota-Reset (midnight UTC).
502 send_failed, 502 upstream_error, 503 * Yes Exponential backoff, starting at a few seconds. For POST /messages, reuse the same Idempotency-Key.
500 internal_error Once, later If it persists, contact support with the request path and time.
409 request_in_flight Yes Wait a few seconds, same Idempotency-Key.
Any other 4xx No The request itself is wrong. Fix it first.