CallableAI

Documentation

Errors

One envelope, one stable code to branch on — and a rule for the codes this table doesn't list yet.

Every non-2xx response has the same body:

{
  "error": {
    "code": "resend_too_soon",
    "message": "A code was just sent to this number. Try again in 18s."
  }
}

Branch on code. It is stable. message is written for a human reading your logs and may be reworded without notice.

CodeStatusWhat happened
unauthorized401Missing, malformed, unknown, revoked, or expired key
forbidden403The key lacks the required scope
invalid_request400A field failed validation; message names it
invalid_phone400phone could not be read, or country is unsupported
invalid_channel400channel is not voice or sms
missing_target400check was sent without a verification_id or phone
not_found404No such verification in your workspace
insufficient_balance402Out of Callable tokens — top up to keep sending
no_phone_number409Your workspace has no number to call from
no_sms_number409Your workspace has no SMS-capable (mobile) number
sms_sender_unavailable409Your SMS number could not be used — contact support
voice_unconfigured / sms_unconfigured409That channel is not available — contact support
verification_expired409The code's time ran out
verification_failed409The attempt budget was spent
verification_canceled409Superseded by a newer code, or cancelled
verification_undelivered409The code was never delivered — request a new one
resend_too_soon429Inside the 30-second resend cooldown
phone_rate_limited4295 codes to this number in the last hour
workspace_rate_limited429Your workspace's hourly send cap
too_many_attempts429This verification's attempts are spent
check_conflict409Two checks for one verification arrived at once — retry
carrier_rejected502We tried; the carrier did not accept it
provider_unreachable502We could not reach the delivery network
delivery_failed, provider_<number>502The delivery network refused it. The number is its HTTP status, for support to read
storage_failed, internal_error500Our fault. Safe to retry
method_not_allowed405Wrong HTTP verb for that path

Treat an unlisted code as its status class. This table covers what you will realistically see, but it is not a closed set — delivery networks surface new failure modes and we pass them through rather than flattening them into something vaguer. Write your switch over the codes you handle specially and let everything else fall through to a status-based default: 4xx is your request, 5xx is ours, 429 means wait. A client that throws on an unrecognised code is a client that breaks on a Tuesday.

A wrong code is not an error

POST /v1/otp/check returns 200 with "verified": false when the code does not match. Mistyping is the expected outcome of a verification flow, not a failed request — branch on the field, not on the status code. Only genuine failures use the error envelope.

The rate limits behind resend_too_soon, phone_rate_limited and workspace_rate_limited are worth reading before you design your retry behaviour.