Errors
A failed request answers with an HTTP status and one body:
{
"error": {
"code": "invalid_request",
"message": "name must be at most 200 characters.",
"param": "body.name"
}
}| Field | Always there | What it is |
|---|---|---|
code | yes | Stable and machine-readable: branch on this, never on the message. |
message | yes | Plain English, safe to show to whoever runs the account. It may be reworded. |
param | no | The input at fault: body.name, query.limit, params.id, header.Zentiq-Version. |
details | no | Structured extras for some codes (below). |
The request's id is in the X-Request-Id header of every answer, failed or not.
Codes#
| HTTP | Code | Retry? | Meaning |
|---|---|---|---|
| 400 | invalid_request | No | The request is malformed or a field is invalid; |
| 400 | consent_required | No | A likeness, voice or guardian attestation is missing. |
| 400 | consent_invalid | No | The consent attestation is not valid. |
| 400 | consent_outdated | No | The consent wording has changed; read it again and confirm the current version. |
| 401 | unauthorized | No | The API key is missing, invalid or revoked. |
| 402 | insufficient_minutes | No | No minutes left, or the organisation's own monthly cap is reached. |
| 403 | forbidden | No | The key lacks a scope (the message names it) or may not take this action. |
| 403 | upgrade_required | No | The plan lacks a feature or a limit is reached; |
| 403 | feature_not_in_plan | No | A session can't start: the plan doesn't include it. |
| 403 | account_paused | No | A session can't start: the account is paused. |
| 403 | candidate_interview | No | A job interview is never read as a session: a hiring system reads a candidate's interview with GET /candidates/{id}/interview. |
| 403 | learner_conversation | No | A young learner's conversation stays in the Zentiq dashboard. |
| 404 | not_found | No | No such id in this organisation (an id of another kind is not found either). |
| 409 | conflict | No | The resource's state doesn't allow this; |
| 409 | version_conflict | No |
|
| 409 | session_ended | No | The session has ended. |
| 409 | duo_not_live | No | The duo is not live yet. |
| 409 | face_not_ready | Yes, after a pause | The persona's new or changed face is still being prepared on the live engine (about a minute); retry after |
| 409 | consent_withdrawn | No | The consent for this face or voice was withdrawn. |
| 409 | on_hold | No | The record is under a hold and can't change. |
| 409 | idempotency_conflict | No | The Idempotency-Key was used with a different request. |
| 409 | idempotency_in_progress | Yes, after a pause | A request with this Idempotency-Key is still running; retry after |
| 410 | gone | No | The resource was deleted. |
| 413 | too_large | No | The body, a file or a field is too large. |
| 415 | unsupported_media_type | No | The file or content type is not supported. |
| 422 | go_live_refused | No | The go-live check refused it; |
| 429 | rate_limited | Yes, after a pause | Too many requests; retry after |
| 429 | concurrency_limit | Yes, after a pause | Too many live sessions at once. |
| 500 | server_error | No | Something went wrong on Zentiq's side. |
| 502 | upstream_error | Yes, after a pause | A service Zentiq relies on failed; retry shortly. |
| 503 | capacity | Yes, after a pause | No capacity right now; retry shortly. |
| 503 | consent_unverified | Yes, after a pause | The consent for this face or voice couldn't be checked just now, so nothing started (it fails closed); retry shortly. |
| 504 | upstream_timeout | Yes, after a pause | A service Zentiq relies on timed out; retry shortly. |
New codes can appear (treat an unknown one by its HTTP status). Each operation in the reference lists the codes it can answer with.
Details worth reading#
| Code | details |
|---|---|
upgrade_required | feature (what the plan lacks, such as voice.cloning or limits.personas), plan (the cheapest plan that includes it, or null), limit (for a count), and reason: "plan_required" when the feature's switch is off because the plan doesn't include it (where the switch simply off answers 409 conflict). Show it to whoever runs the account. |
go_live_refused | problems: [{ field, message }], what to finish before it can go live; warnings alike, which don't block. |
version_conflict | currentVersion: the version someone else saved first. Read it again, then retry. |
conflict | reason: why the state doesn't allow it (a duo's persona is changed in the duo studio, a decision was already made, …). |
Plan refusals on older keys#
On the 2026-10-01 contract a plan refusal is in the same shape as every other error, its extras in details. A key on the older 2026-07-08 contract gets the shape it always had:
{ "error": "upgrade_required", "feature": "voice.cloning", "plan": "pro", "message": "Voice cloning is on the Pro plan." }The SDKs read both.
What to retry#
- Retry after a pause:
rate_limitedandconcurrency_limit(429, wait forRetry-After),idempotency_in_progress, and the5xxcodesupstream_error,capacityandupstream_timeout. - Only if the retry is safe: a
GET,PUT,PATCHorDELETE, or aPOSTsent with an Idempotency-Key to an operation that takes one. Any otherPOSTmay have been done already. - Never unchanged: every
4xxother than those. Fix the request (or the plan, or the key's scopes) first.
The SDKs do all of this for you: they retry safe requests on the codes above, with a growing pause, twice by default.