---
title: Errors
description: The single Clipia API error format — type, code and message fields, the table of HTTP statuses and codes, what to retry, and how to read message.
icon: TriangleAlert
---

Every REST API error uses the same format: an `error` object with `type`, `code` and `message` fields. The HTTP status sets the error class; `code` is the machine-readable reason. Only transient errors are worth retrying after a pause: `409 request_in_progress`, `429`, `500` and `503`.

```json
{
  "error": {
    "type": "authentication_error",
    "code": "invalid_api_key",
    "message": "Invalid or revoked API key."
  }
}
```

Branch on `code`. The `message` field is meant for humans and can change. For `400 invalid_request`, `message` carries a short detail such as `prompt_required` or `webhook_url_invalid`; the full list for generation submit is on [Submit a generation](/en/docs/api-reference/submit#submit-errors). Most messages come in English, but some input-check refusals (for example, a prompt that is too short or incompatible references) come in Russian regardless of the header. For `402` errors and music-settings refusals, the `Accept-Language: ru` header switches the text to Russian.

Sometimes `error` includes `retry_after_ms` or `retry_after_seconds` — a hint for when to retry.

Responses that never reach a route handler use the same format: `400 invalid_request` for a body that is not valid JSON, `415 unsupported_media_type` for a body type the server doesn't parse (for example, `application/xml`), `413 request_too_large` for a body over 10 MB and `404 not_found` for an unknown path under `/v1/` (including a preflight `OPTIONS` from a disallowed origin). These responses never expose internal server paths.

## Codes

| HTTP | `code` | `type` | When | Retry |
|------|--------|--------|------|-------|
| `400` | `invalid_request` | `invalid_request_error` | invalid body, parameters or ID; the reason is in `message` | no, fix the request |
| `401` | `invalid_api_key` | `authentication_error` | the key is missing, wrong, revoked or expired | no |
| `402` | `insufficient_credits` | `billing_error` | not enough credits | after topping up |
| `402` | `subscription_required` | `billing_error` | an active subscription is required: the Public API condition for live keys, or credits frozen until the plan is renewed | after subscribing |
| `402` | `insufficient_quota` | `billing_error` | the request doesn't fit the key's budget, see [Managing keys](/en/docs/api-reference/keys) | once there is room in the budget |
| `403` | `insufficient_scope` | `permission_error` | the key lacks the required permission | no |
| `404` | `not_found` | `invalid_request_error` | no such model (`model_not_found`) or request (`request_not_found`) | no |
| `409` | `idempotency_key_reuse` | `idempotency_error` | the same `Idempotency-Key` with a different body | no, change the key |
| `409` | `request_in_progress` | `idempotency_error` | a retry while the first request is still being processed | yes, in a few seconds |
| `422` | `model_input_invalid` | `invalid_request_error` | the parameters don't fit the model | no, fix `input` |
| `429` | `rate_limit_exceeded` | `rate_limit_error` | request rate limit exceeded | yes, after `Retry-After` |
| `429` | `concurrency_limit_exceeded` | `rate_limit_error` | concurrent generation limit exceeded, if one is set; `retry_after_seconds: 5` and `Retry-After: 5` | yes, once a job finishes |
| `500` | `internal_error` | `api_error` | internal error | yes, with growing pauses |
| `503` | `service_unavailable` | `api_error` | service temporarily unavailable | yes, with growing pauses |

[AI Gateway](/en/docs/llm-gateway) errors (`/v1/chat/completions` and related endpoints) use the OpenAI format; their codes are listed on [Chat Completions](/en/docs/llm-gateway/chat-completions).

<Callout type="warn" title="A live key requires an active subscription">
Generation with a `clipia_live_` key works only while the account has an active subscription on clipia.ai; a `clipia_test_` key works without payment. Without a subscription, submit returns `402 subscription_required`: buying a credit pack without a subscription does not unlock access, a plan is required. To check ahead: [`GET /v1/account`](/en/docs/api-reference/account) shows the balance, and a [cost estimate](/en/docs/api-reference/estimate) never returns `402` — it computes the price without checking payment.
</Callout>

<Callout type="info" title="Two responses outside the common format">
The response to a body that cannot be parsed as JSON (`400`) and to an unknown route (`404`) is not wrapped in the `error` envelope; it looks like `{ "statusCode", "timestamp", "path", "message" }`. Check the HTTP status before reading `error.code`.
</Callout>

## Retries

<Callout type="info" title="What to retry">
Retry `429`, `500` and `503` with growing pauses: 1, 2, 4, 8 seconds, at most 5 attempts. For `429`, wait as long as `Retry-After` says. Retry a generation submit with the same `Idempotency-Key` so the retry doesn't create a second job. Other `4xx` errors, apart from `409 request_in_progress` and `429`, come back unchanged if you resend the same request.
</Callout>

<Callout type="warn" title="No internal details">
Error messages don't reveal internal infrastructure. For `500` and `503` the text is generic: "An internal error occurred. Please retry or contact support." and "Service temporarily unavailable. Please retry later."
</Callout>

A failed generation is not an HTTP error: the result request returns `200` with `status: "FAILED"` and an `error` object whose code comes from a separate dictionary (`generation_failed`, `content_moderated`, `model_unavailable` and others), and the credits go back to your balance. See [Result](/en/docs/api-reference/result#failed-codes).
