Errors and limits
Every Tenant API endpoint uses a single error format and standard HTTP status codes. The full list of endpoints and response examples is in the API reference (OpenAPI).
Error format
Error responses are JSON with a single shape:
{
"success": false,
"error": {
"type": "invalid_request_error",
"code": "validation_error",
"message": "Request validation failed",
"param": "flow_id",
"details": [
{
"field": "flow_id",
"code": "required",
"message": "flow_id is required"
}
],
"request_id": "req_abc123",
"retryable": false
}
}Field reference:
| Field | Description |
|---|---|
success | Always false on error |
error.type | Error category, derived from the HTTP status: authentication_error (401), authorization_error (403), not_found_error (404), rate_limit_error (429), service_unavailable (503), api_error (5xx), invalid_request_error (other 4xx) |
error.code | Error code (for example, validation_error, not_found, unauthorized, conflict) |
error.message | Short error description (for developers and logging) |
error.param | Parameter that caused the error (for validation errors) |
error.details | Per-field error array (for validation errors) |
error.request_id | Request identifier (for support enquiries) |
error.doc_url | Link to the relevant documentation section (when available) |
error.retryable | Whether the request is safe to retry (true for some 5xx errors and timeouts) |
HTTP status codes
| Code | Description |
|---|---|
| 200 | Request succeeded |
| 201 | Resource created |
| 400 | Bad request (error in body or parameters) |
| 401 | Authentication failed (HMAC signature / JWT token missing or invalid) |
| 403 | Forbidden (insufficient scope or attempt to access another tenant's resource) |
| 404 | Resource not found (session, applicant, link, etc.) |
| 409 | Conflict (for example, the review task is already assigned or the decision is already made) |
| 410 | Resource no longer available (for example, session expired) |
| 422 | Field validation failed |
| 429 | Rate limit exceeded; the Retry-After header indicates how long to wait |
| 500 | Internal server error; if error.retryable = true, the request can be retried |
Rate limiting
Limits apply per IP address and per API key. Default values:
| Scope | Requests/sec | Requests/min |
|---|---|---|
| Per IP address | 20 | 200 |
| Per API key | 40 | 400 |
When the limit is exceeded, the server returns HTTP 429 Too Many Requests. Response headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests in the current window |
X-RateLimit-Remaining | Remaining requests |
X-RateLimit-Reset | Unix time at which the limit resets |
Retry-After | Seconds until the next allowed attempt |
Recommendation: apply exponential backoff on retries and honour the Retry-After header.
Common errors
invalid_signature (401)
Invalid HMAC signature. Recheck the signature construction (see Authentication).
invalid_signature (401) — clock drift
If X-Timestamp deviates from the server time by more than 5 minutes, the server also returns invalid_signature (there is no separate timestamp_expired code). Confirm that the client clock is synchronised (NTP).
unauthorized (403)
The API key does not have sufficient scopes. Review the key scopes in the admin panel.
not_found (404)
The requested resource (applicant, session, link, etc.) does not exist or belongs to another tenant.
link_already_consumed (409)
The verification link has already been consumed. Create a new link.
link_expired (410)
The verification link expired. Create a new link with a longer ttl_seconds.
validation_error (422)
Request fields failed validation. Per-field details are in the error.details array.
rate_limit_exceeded (429)
Rate limit exceeded. Honour the Retry-After header and apply exponential backoff.
internal_server_error (500)
Internal server error. Check error.retryable — when true, the request can be retried.
OTP verification: limits and errors
OTP confirms a phone number or email address during a KYC session.
OTP code parameters
| Parameter | Value |
|---|---|
| Code length | 6 digits |
| Code lifetime | Phone: 10 minutes by default. Email: 15 minutes by default. Flow step configuration can override the lifetime within the supported range. |
| Minimum resend interval | 15 seconds by default; flow step configuration can override it |
| Maximum entry attempts | 10 |
OTP errors
On failed verification, OTP endpoints return the standard error envelope with an OTP error code and a text message explaining the reason. When available, the remaining attempt count is included in error.details as attempts_remaining.
rate_limit_exceeded (429)
OTP send rate limit exceeded. Wait until the limit resets (see the Retry-After header).
otp_invalid / otp_expired (400/422)
The code expired (older than 10 minutes). Request a new code through POST .../otp/send.
otp_invalid (400/422)
The entered code is wrong. The remaining attempt count is in error.details when the endpoint returns it.
otp_invalid (400/422), max attempts exceeded
All 10 attempts were used. The code is invalidated — request a new one through POST .../otp/send.
See also: Authentication (codes 401, 403), API reference (OpenAPI) (full endpoint and response code specification).