API referenceGetting started

Errors and limits

A failure is a non-2xx status and an error string you can switch on. The strings are stable; the prose beside them is not.

The shape

400Bad RequestJSON
{ "error": "invalid_caller" }

Some carry context beside the code, which is always additional and never replaces it:

402Payment RequiredJSON
{ "error": "number_limit", "limit": 3 }
429Too Many RequestsJSON
{ "error": "slow_down", "retry_s": 47 }
400Bad RequestJSON
{ "error": "invalid_direction", "accepted": ["inbound", "outbound"] }

Status codes

CodeMeansNotes
200FineIncluding some outcomes you might expect to be errors; see below.
201Made somethingA block, a key, a number.
400We can't read itA malformed number, a missing required field, a value outside the accepted set. The body names which.
401Who are youMissing, unknown, or revoked key. Also session_required on endpoints that need a signed-in person rather than a key.
402Your plan won'tA cap you have reached: number_limit, or plan_required for something Guard-only.
403Not with thatread_only_key on a write, role_required when your team role is too low, phone_unverified for a text to a phone you have not proved.
404No such thingAlso unknown_call when you send turns for a call you never started.
409Conflicts with what's therenumber_taken when another account protects the number, or not_protected for a number you have not added.
429Too fastWith a retry-after header and retry_s in the body. Wait that long.
500OursBody is { "error": "internal" }. Retry; if it persists, tell us.

The 200s that aren't successes

Some things that went wrong still answer 200, on purpose. Anything a platform delivers to us as a webhook gets one, because the alternative is a platform retrying, and a retry storm during an attack is the last thing anybody needs. These carry a flag instead:

200OKTurns on Watch, or past one call's capJSON
{ "call_id": "CA7f3e…", "ignored": true, "why": "live_monitor_is_guard" }
{ "call_id": "CA7f3e…", "ignored": true, "why": "call_turn_cap" }
200OKA start we had already scoredJSON
{ "call_id": "CA7f3e…", "duplicate": true, "score": 0.1, "action": "allow", "reasons": [] }
200OKThe day's scoring allowance is spentJSON
{ "call_id": "CA7f3e…", "score": 0, "action": "allow", "reasons": ["daily_cap"], "capped": true, "cap": 100 }

duplicate means we had already scored that call_id and are handing back the first answer. capped means your plan's daily scoring allowance is spent: the call is recorded and allowed, not scored.

Error strings you'll actually hit

CodeMeans
invalid_callerThe calling number could not be parsed as E.164.
invalid_numberYour own number could not be parsed as E.164.
invalid_to / invalid_fromOn an event: one of the two numbers could not be parsed.
invalid_directionSomething other than inbound or outbound.
invalid_patternA block pattern that isn't a number.
missing_call_idAn event with no call_id.
unknown_callTurns or an end for a call we never saw start.
unknown_typeAn event type we don't have. The body lists the ones we do.
not_protectedA call to a number that isn't on your account, with register: false.
number_limitYour plan's protected-number cap, with the limit in the body.
number_takenAnother account already protects that number.
plan_requiredA Guard feature on Watch. The body names the feature.
read_only_keyA write attempted with a cw_read_ key.
webhook_must_be_httpsAn alert webhook URL that isn't https.
slow_downRate limited. retry_s says how long.
unauthorizedMissing or unknown key.

Sign-up, sign-in and team endpoints have their own set: weak_password, invite_invalid, token_invalid and the like. They are only reachable from the dashboard and are not part of this reference.

Rate limits

Event ingestion is limited per account at 1,200 events a minute, twenty a second, set well above any real traffic, because our busiest hour is one of your attacks. Past it we accept the request and drop it rather than erroring, so nothing upstream starts retrying.

One call may write 3,000 turns before we stop recording its turns.

Reads are limited per key at 600 a minute, with a 429 and a retry-after past it. That is ten a second on one key, far above what a dashboard or an integration does; it exists for a loop that got away, or a key that got out. Each key has its own allowance, so a busy integration and a wallboard on separate keys never slow each other.

Allowances are not limits
Your plan also sets how many calls a day are scored: 100 on Watch, 100,000 on Guard. Past it, calls are still recorded and allowed; see daily_cap above. That is a plan allowance, it resets at midnight UTC, and it is listed with the rest under plans and limits.

Retrying

Everything on events is keyed on your call_id, so retrying a delivery you are unsure about is always safe. On 429, wait retry_s. On 500, back off and retry. On a 400, do not retry; the request will fail the same way forever.