Errors and rate limits: what to retry, what to surface
HTTP status codes Call2Me returns, rate limit headers, retry-after semantics, and the patterns that make integrations survive carrier hiccups.
Updated October 6, 2026
Most production issues don't come from the happy path — they come from how integrations handle the unhappy ones. This is the reference for what the platform returns and how to react.
Status codes you'll see
| Code | Meaning | What to do |
|---|---|---|
| 200 OK | Success | — |
| 201 Created | Resource created | Read Location header for canonical URL |
| 400 Bad Request | Validation failed | Fix the request; don't retry as-is |
| 401 Unauthorized | Missing or bad auth | Check Bearer token; do NOT retry |
| 402 Payment Required | Wallet too low | Top up, then retry |
| 403 Forbidden | Auth okay, scope insufficient | Mint a key with the right scope |
| 404 Not Found | Resource doesn't exist | Don't retry; check the ID |
| 409 Conflict | State conflict (e.g. number already bound) | Inspect; usually no retry |
| 422 Unprocessable | Schema valid but logically wrong | Fix the request |
| 429 Too Many Requests | Rate limited | Back off; respect Retry-After |
| 500 Internal Server Error | Platform side bug | Retry with exponential backoff |
| 502 / 503 / 504 | Upstream issue | Retry with exponential backoff |
Error response shape
Error bodies come in two shapes. There is no request_id or error.code.
401 (and 429) use a flat object:
{ "error": "unauthorized", "message": "API key has been revoked" }
For an invalid sk_call2me_ key the message says why: invalid, revoked or
expired.
All other 4xx errors use the FastAPI shape:
{ "detail": "Insufficient balance" }
On 422 validation errors, detail is an array of objects (loc, msg,
type) pointing at the offending field.
Rate limits
Limits are per API key: 1000 requests per minute. Each response includes:
x-ratelimit-limit: 1000
x-ratelimit-remaining: 87
x-ratelimit-reset: 1714998000
When you hit zero:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Retry-After is in seconds. Wait that long, then retry. If you're seeing
429 frequently, contact support to discuss raising your limits — for
high-volume customers we tune them per workspace.
Retrying POSTs safely
There is no idempotency header. An earlier version of this page documented one with a 24-hour response cache; it was never built and the section was removed on 6 October 2026.
For POSTs that create something, a blind retry can create it twice — in
particular POST /v1/calls can place a second call and charge for it.
Safe pattern:
- Put your own request id in
metadata({"metadata": {"req": "…"}}). - On timeout or 5xx, query first —
GET /v1/calls?agent_id=…and look for your id inmetadata— and only then retry. - For wallet top-ups the checkout is the dedupe boundary; never re-POST
/v1/payments/checkouton a timeout, open the returned URL again.
Retry strategy that works in production
attempt 1 → fail with 502
wait 1s
attempt 2 → fail with 502
wait 2s
attempt 3 → fail with 502
wait 4s
attempt 4 → succeed
Caps to apply:
- max attempts: 5
- max total wait: 30s
- never retry 4xx (except 408, 429)
- always honor
Retry-Afterif present
Most HTTP client libraries have this built in — use the library, don't reinvent it.
What's next
- Authentication — the 401 and 403 root causes
- Webhooks — retry semantics for events you receive
- Pricing — when 402 is right and what to do about it
Frequently asked
Q.Are 5xx errors safe to retry?
Yes — with exponential backoff. 5xx means the platform side failed; idempotent operations (GET, PUT, DELETE) can be retried as-is. For POST that creates something (a call, an agent), check first whether the resource was created — the API has no idempotency header, so a blind retry can place a second call.
Q.How does rate limiting work?
Per-API-key, sliding window. Limit is 1000 requests/minute per key. Headers x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset tell you where you stand. When you hit zero, you get 429 with a Retry-After header.
Q.Is there an idempotency key for POST requests?
No. An earlier version of this page described such a header with a 24-hour cache; it was never implemented and was removed on 6 October 2026. Dedupe on your side: keep your own request id in metadata and query before retrying.
Q.Why am I getting 402 Payment Required?
Wallet balance is below the minimum to start a call. Top up via the dashboard or POST /v1/payments/checkout, then retry.