# Errors

Every endpoint fails the same way: an HTTP status and one JSON envelope.

```json
{
  "error": {
    "type": "invalid_request",
    "code": "invalid_input",
    "message": "duration must be one of 5, 10.",
    "param": "duration",
    "details": [ { "param": "duration", "message": "duration must be one of 5, 10." } ],
    "request_id": null,
    "doc_url": "https://doitong.com/developers/docs/errors#invalid_input"
  }
}
```

| Field | Meaning |
|---|---|
| `type` | Broad class of the error. Decide what to do from this. |
| `code` | Exact reason, stable and machine-readable. Every code has a section below; `doc_url` links to it. |
| `message` | Human-readable explanation, safe to log or show to a developer. |
| `param` | The input parameter or header at fault, when there is one. |
| `details` | For input errors, every problem found: `[{ "param", "message" }]`. |
| `request_id` | The request it concerns, when one exists. |

Nothing is charged for a call that returns an error. A request that fails *after* it was accepted keeps status
`failed`, carries `error: { "type", "code", "message" }` on the request object, and is refunded automatically.

## Types

| Type | HTTP status | What to do |
|---|---|---|
| `invalid_request` | 400, 413 or 422 | Fix the request. Retrying the same call fails again. |
| `authentication_error` | 401 | Send a valid key in `Authorization: Bearer <key>`. |
| `permission_error` | 403 | Use a key with the needed scope. |
| `not_found` | 404 | Check the model id or request id. |
| `insufficient_credits` | 402 | Top up the API balance (`top_up_url` in the error). |
| `budget_exceeded` | 402 | The key's own budget is used up. Final: ask the key owner to raise it. |
| `conflict` | 409 | The resource is not in a state that allows this call. |
| `rate_limited` | 429 | Wait `Retry-After` seconds, then retry. |
| `upstream_error` | 502 | The model backend failed. Safe to retry with the same `Idempotency-Key`: it runs again; you were not charged. |
| `service_unavailable` | 503 | The API is paused or a dependency is down. Retry later. |
| `internal_error` | 500 | Our bug. Retry; if it persists, contact support with the time and `request_id`. |

## Codes

### `invalid_request`

Generic malformed request (400).

### `invalid_json`

The body is not valid JSON (400).

### `invalid_body`

The body is valid JSON but not an object; model input must be a JSON object (400).

### `body_too_large`

The body is larger than 16 MB (413). Upload large files and pass their URL instead (see [Files](https://doitong.com/developers/docs/files)).

### `invalid_parameter`

A query parameter has a wrong value, for example `limit` outside 1 to 100, `wait` not a number, an unknown
`status` or `category`, or `storage` other than `permanent` / `temporary` (400). `param` names it.

### `invalid_category`

`GET /openapi.json?category=` names a category that does not exist (400). `GET https://api.doitong.com/v1/categories` lists them.

### `invalid_cursor`

The `cursor` of a list call does not belong to this list (400). Start again without a cursor.

### `invalid_idempotency_key`

The `Idempotency-Key` header is empty or longer than 255 characters (400).

### `invalid_arguments`

An MCP tool call has a missing or malformed argument (MCP only). `param` names the argument.

### `invalid_input`

The model input does not pass validation (422): an unknown parameter (the message suggests the closest valid name),
a missing required parameter, a wrong type, a value outside the allowed list or range, or both the canonical and the
native name given with different values. `param` is the first offending parameter and `details` lists all of them.
Nothing is charged.

### `unsupported_file`

A file has a type we do not accept (422), for example an upload with an unsupported extension, or a `content_type`
that does not match the extension.

### `invalid_webhook_url`

`webhook_url` is not a public `https://` URL (422). Private, local and plain `http://` addresses are refused.

### `missing_api_key`

The endpoint needs an API key and none was sent (401).

### `invalid_api_key`

The key is unknown, revoked or expired (401).

### `insufficient_scope`

The key lacks the scope this call needs (403), for example `models:run` for a run, or `models:read` to read
requests and the balance. See [Authentication](https://doitong.com/developers/docs/authentication#scopes).

### `account_disabled`

The account is closed or suspended (403). Its keys no longer work.

### `not_found`

The path does not exist (404).

### `model_not_found`

No public model has this id (404). List models with `GET https://api.doitong.com/v1/models`.

### `request_not_found`

No request with this id exists in your account (404). Requests of other accounts are never visible.

### `insufficient_credits`

The account's **API balance** (API credits, 1,000 API credits = $1 — not the credits of the Doitong apps) does not cover the
price of this call (402). The response is sent before anything is charged. The error carries `top_up_url`; add money
at [https://doitong.com/developers/dashboard#balance](https://doitong.com/developers/dashboard#balance) and retry.

```json
{ "error": { "type": "insufficient_credits", "code": "insufficient_credits",
             "message": "This request costs 560 API credits ($0.56); the API balance is 120 API credits ($0.12). Top up at https://doitong.com/developers/dashboard#balance",
             "top_up_url": "https://doitong.com/developers/dashboard#balance", "doc_url": "https://doitong.com/developers/docs/errors#insufficient_credits" } }
```

### `budget_exceeded`

The call would exceed the API key's own budget in API credits (402). The budget is set by the key's owner; retrying does not
help until they raise it.

### `conflict`

The call conflicts with the current state of the resource (409).

### `idempotency_mismatch`

The `Idempotency-Key` was already used with a different body (409). Use a new key for a new operation.

### `idempotency_in_progress`

A request with the same `Idempotency-Key` is being created right now (409). Retry in a moment to get it.

### `request_active`

The request is still `queued` or `running`, so it has no files to delete yet (409). Delete them once it has finished.

### `not_cancelable`

The request is already `succeeded` or `failed` and cannot be canceled (409).

### `rate_limited`

The key made too many requests in the current window (429). See the `X-RateLimit-*` and `Retry-After` headers.

### `too_many_inflight`

The account already has 10 requests `queued` or `running` (429). Start the next one when one of them finishes.

### `upstream_error`

The model backend refused or failed the request (502 on run, or `failed` with this code on the request). The API credits are
refunded. If the backend rejected the input itself, the call returns `422 invalid_input` with its message instead.

### `upstream_lost`

The model backend no longer knows the request, so no result will ever arrive. It ends as `failed` with this code
(type `upstream_error`) and is refunded. Run it again.

### `timeout`

The request ran longer than its limit (15 minutes for images, 60 for video, 30 otherwise) and was stopped. It ends as
`failed` with this code and is refunded.

### `service_unavailable`

The API is temporarily paused, or a dependency such as file uploads is down (503). Retry later.

### `internal_error`

Something broke on our side (500). Retry; if it persists, contact support.

### `charge_failed`

The account could not be charged because of an internal problem (500). The request is marked `failed`; nothing was
taken. Retry.

### `pricing_unavailable`

The model's price is not configured, so it cannot be quoted or run right now (500).
