Docs · Concepts
Errors
One envelope for every non-2xx response, and a stable machine-readable code inside it.
The error envelope
{ "error": { "code": "session_not_found", "message": "Verification not found" } }code is stable and machine-readable. message is
human-readable and its wording may change between versions.
Codes
| HTTP | Code | Means |
|---|---|---|
| 400 | invalid_request | Schema validation failed, or neither/both of template and query were given. |
| 400 | invalid_idempotency_key | The Idempotency-Key header isn't 1–255 printable ASCII characters with no spaces. |
| 401 | invalid_api_key | Missing, malformed, revoked or unknown key. Deliberately indistinguishable. |
| 401 | api_key_expired | The key was valid and its deadline has passed. Rotate it. |
| 403 | insufficient_scope | The key authenticated but lacks the scope this route needs. The message names it. |
| 404 | session_not_found | Unknown id, another organisation's id, or an id already erased. |
| 404 | invalid_response_code | Wrong code, already-redeemed code, or unknown session. |
| 409 | session_already_responded | A wallet response arrived for a session that already had one. |
| 409 | idempotency_key_in_flight | A request with this Idempotency-Key is still being processed. Retry-After carries the lease. |
| 410 | session_expired | The session passed expires_at. |
| 422 | unknown_template | Not a built-in, and no template of yours matches the key. |
| 422 | invalid_webhook_url | The URL is refused by the egress policy — private, loopback or link-local. |
| 422 | idempotency_key_reused | The same Idempotency-Key was sent with a different request body. |
| 429 | rate_limit_exceeded | A per-key, per-IP or per-session budget was exceeded. Back off; the same request will work shortly. |
| 429 | quota_exceeded | The day's verification creations are all spent. Retry-After carries the seconds until midnight UTC; retrying sooner cannot succeed. |
| 500 | internal_error | Ours. Retry, and tell us if it persists. |
Why some errors look unhelpful
Several distinctions are collapsed on purpose. A revoked key and a key that
never existed both return invalid_api_key. Another organisation's
session id and a nonexistent one both return session_not_found. A wrong
response code and a spent one are the same 404.
Each of those pairs, if distinguished, would let anyone with a key probe for what
exists outside their own organisation. The cost is that a genuinely confusing
404 stays confusing — check the id against
GET /v1/verifications, which only ever lists your own.