# Errors

> Every error has one shape, in HTTP responses and inside a failed job.

```json
HTTP 422
{
  "error": {
    "code": "too_many_references",
    "type": "invalid_request",
    "message": "The request sends more reference images than the task takes.",
    "field": "references",
    "retryable": false,
    "request_id": "req_…"
  }
}
```

| Field | Meaning |
|---|---|
| `code` | stable snake_case identifier. **Branch on this.** Never reused, never renamed. |
| `type` | the category (table below). Use it when you do not recognise the `code`. |
| `message` | for developers; says how to fix it. It is always one of Refabric's own sentences. May be reworded at any time — do not parse it. |
| `field` | the public path of the offending input (`references[2].image`), or `null` |
| `retryable` | `true` if the same request may succeed later |
| `docs` | a link to the error's docs page, when available |
| `request_id` | quote it when you contact [support](https://docs.refabric.com/support) |

The response also carries `X-Request-ID` and `X-Refabric-Error-Type: <type>`.

## Errors inside a job

A failed job carries the same object in its `error` — on `GET /v1/jobs/{id}` and in the webhook.
The job's own `job_id` is beside it; `request_id` is `null` (the error was not an answer to a
request). A `Prefer: wait` submit that fails answers the error itself, with `job_id` inside it.

```json
{ "job_id": "j_…", "lifecycle": "terminal", "outcome": "failed",
  "error": { "code": "start_timeout", "type": "internal",
             "message": "The job could not be started in time.",
             "field": null, "retryable": true, "request_id": null } }
```

Failed jobs use `processing_failed`, `content_refused`, `insufficient_credits` or `internal`
(`start_timeout` is `internal`, as above; live list: `GET /v1/vocab/error_type`). A **cancelled** job carries `code: cancelled`
(`type: conflict`, `retryable: false`) — on `GET /v1/jobs/{id}`, in the webhook and in a
`Prefer: wait` answer (`409`) — never `null`.

A refused prompt or image is `content_refused`. A job that delivers no output fails with
`processing_failed` (`retryable: false`) (live list: `GET /v1/vocab/error_type`).

Reading the **result** of a job that did not succeed answers an error, not an empty result:
`409 job_failed` for a failed job ("This job failed; its reason is the job read's `error`." — read
`GET /v1/jobs/{id}`), `409 cancelled` for a cancelled one (`type: conflict`, as everywhere a
cancelled job is reported), `409 result_not_ready` while it still runs.

## Warnings

A job that succeeded can report warnings in `summary.warnings[]`:

```json
"summary": { "requested": 3, "delivered": 2,
             "warnings": [ { "code": "output_not_produced", "field": "back",
                             "message": "One output of this job could not be produced; `field` names which." } ] }
```

Each row is `{code, field, message}`: `code` is catalogued with the errors (type `warning`,
`GET /v1/errors`), `field` names the output or input it is about (`back`, `poses.items[1]`,
`images[4]`) or is `null`. Branch on `code`, as for errors.

The list of warning codes, with what each means, is `GET /v1/errors` (the entries of type
`warning`); it grows as tasks learn to report more, so read it there rather than from this page.
An unknown warning `code` is safe to show as its `message`.

## Retrying

- When the answer carries an `error` object, obey its `retryable`: retry if it is `true`, never if
  it is `false` — whatever the status, `5xx` included.
- Retry on `429`, `500`, `502`, `503`, `504` only when the answer has no `error` object (an error
  from a proxy or load balancer in front of us), and on a network error.
- Use exponential backoff with jitter (for example 1 s, 2 s, 4 s … capped at 60 s).
- Always resend a `POST` with the same `Idempotency-Key` so a retry cannot run a job twice
  ([Idempotency](https://docs.refabric.com/api-reference/platform/idempotency)).
- Honour `Retry-After` when present.

```python
import random, time, requests

RETRY_STATUS = {429, 500, 502, 503, 504}

def call(session, method, url, **kw):
    for attempt in range(6):
        try:
            r = session.request(method, url, timeout=90, **kw)
        except requests.ConnectionError:
            r = None
        if r is not None and r.status_code < 400:
            return r
        if r is not None:
            try:
                err = r.json().get("error")
            except ValueError:
                err = None  # not our envelope (a proxy's error page)
            if err is not None and not err.get("retryable"):
                raise RuntimeError(f"{err.get('code')}: {err.get('message')} ({err.get('request_id')})")
            if err is None and r.status_code not in RETRY_STATUS:
                r.raise_for_status()
        wait = float(r.headers.get("Retry-After", 0)) if r is not None else 0
        time.sleep(max(wait, min(60, 2 ** attempt) + random.random()))
    r.raise_for_status()
```

## The codes

Every code the server answers — its `type`, HTTP status, message and fix — is listed live by
`GET /v1/errors`. That catalogue is the one list. The codes below are listed from that catalogue, and the task
pages and **Panel › Developers › Errors** link to the code's entry here.
Branch on `type` for anything you do not recognise.

A few codes are worth knowing by name:

- `field_not_accepted` — you sent a field the task does not take (a misspelled name, for
  example) or one the server fills itself; `field` names it. `field_not_supported` is a known field the chosen variant does not use.
- `request_in_progress` — the first request with this `Idempotency-Key` is still being processed:
  retry shortly with the same key.
- `insufficient_credits` — the balance does not cover the hold; `required` and `balance` say by
  how much ([Pricing](https://docs.refabric.com/task-apis/pricing)).
- `too_many_outputs` — a shoot that would make more images than its task allows.
- `start_timeout` — the job could not start before your `X-Refabric-Start-Timeout`.
- `file_not_deletable` — `DELETE /v1/files/{ref}` on a handle that is not `art:` (an upload, a
  library item or a record); `422`, type `invalid_request`, `field: null`.

### Type: `invalid_request`

The request cannot be used as sent; fix it and send again.

#### `invalid_request`

A field is missing or has a value this task cannot use.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Read `field` and the task's input schema (`GET /v1/tasks/{name}`), correct the value and send again.

#### `field_not_accepted`

The request sends a field the task does not take, or one Refabric sets itself.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Remove the fields the message names (`field` names it when there is one). The task's input schema (`GET /v1/tasks/{name}`) lists every field it takes.

#### `field_not_supported`

The request sends a field that is not used with the other values it sends.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Remove the field named in `field`; the message says why. The task schema shows the fields each `kind` or `type` takes (its `oneOf`).

#### `invalid_option`

A value is not one of the options this field offers.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Pick one of the values listed for the field in the task schema or at `GET /v1/vocab/{name}`.

#### `request_refused`

The request is well-formed but this task cannot work with it.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Read the message and `field`, change that input and send again.

#### `prompt_required`

This request needs a prompt.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Write `prompt`. The task's `prompt` field says when it may be left out.

#### `too_many_references`

The request sends more reference images than the task takes.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send at most the number of references the task schema allows (`maxItems`).

#### `moodboard_not_ready`

The moodboard is still being analysed, or its analysis failed.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Wait until `GET /v1/files/{moodboard}` shows the analysis, or choose another moodboard.

#### `brand_kit_look_not_ready`

The brand kit look you referenced has not finished its analysis.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Wait until the look is ready in the brand kit, or send it without `use_case: frame`.

#### `api_key_in_query`

An API key was sent in the URL. Your key may have leaked — rotate it.

| Status | Type | Retryable |
|---|---|---|
| 400 | `invalid_request` | No |

**Fix:** Roll or revoke the key now on the API keys page of your account, then send keys only in the `Authorization` header, never in a URL.

#### `malformed_request`

The request cannot be read as it was sent: a header, or the body's form.

| Status | Type | Retryable |
|---|---|---|
| 400 | `invalid_request` | No |

**Fix:** Read the message and `field`: send the body as valid JSON, and each header in its documented form.

#### `body_too_large`

The request body is larger than this operation takes.

| Status | Type | Retryable |
|---|---|---|
| 413 | `invalid_request` | No |

**Fix:** Send a smaller body; the limit is in the message and at `GET /v1/meta` (`limits.request.max_body_bytes`).

#### `job_already_ended`

The job has already ended.

| Status | Type | Retryable |
|---|---|---|
| 400 | `invalid_request` | No |

**Fix:** Read `GET /v1/jobs/{id}` for how it ended; start a new job to run the work again.

#### `moodboard_needs_images`

A moodboard needs more images.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send at least as many images as the task schema asks for (`minItems` of `images`).

#### `too_many_images`

The request sends more images than the task takes.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send at most the number of images the task schema allows (`maxItems`).

#### `too_many_items`

The request sends more items of one type than the task takes.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send at most as many items of each `type` as the task takes; the message names the number.

#### `too_many_colours`

The request sends more colours than the task takes.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send at most the number of colours the task schema allows (`maxItems`).

#### `invalid_colour`

A colour is not a hex code.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Write each colour as `#RRGGBB`, for example `"#E8D9C4"`.

#### `moodboard_required`

This request needs at least one moodboard.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Name one or more moodboards in `moodboards`, as ["moodboard:<id>"] (`GET /v1/refs/moodboard`, `?curated=true` for Refabric's own).

#### `look_not_in_moodboard`

A look is not a look of any moodboard the request names.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send a look url from one of the named moodboards (`data.items[].url` of `GET /v1/files/moodboard:<id>`), or name the moodboard it belongs to.

#### `images_required`

This request needs at least one image.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send at least one image in `images`.

#### `image_not_public`

An image address is not publicly reachable.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send a public https url, or a file from an earlier result (`art:…`) or an upload (`file:…`).

#### `name_required`

This request needs a name.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send a non-empty `name`.

#### `invalid_weight`

The weight is not a whole number of grams per square metre in the allowed range.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send `facts.weight_gsm` as a whole number, e.g. 180, or leave it out to have it filled in for you.

#### `file_not_deletable`

The API deletes the files a job produced, named by their `art:…` handle.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send the `art:…` handle of a file a job produced.

#### `unsupported_media_type`

The body, or the uploaded file, is of a media type this operation does not take.

| Status | Type | Retryable |
|---|---|---|
| 415 | `invalid_request` | No |

**Fix:** Send the body as `application/json` (an upload also takes `multipart/form-data`), and upload a PNG, JPEG, WebP, GIF, BMP or PDF file in the `file` part — or register its url instead.

#### `file_too_large`

The uploaded file is larger than the upload limit.

| Status | Type | Retryable |
|---|---|---|
| 413 | `invalid_request` | No |

**Fix:** Send a smaller file (the limit is in the message), or register a url of it instead.

#### `too_many_outputs`

The request asks for more images than one shoot makes.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send fewer products, models, poses or views; the limit is in the message.

#### `image_not_from_shoot`

An image is not an image of one of your shoots, or the images come from two shoots.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send images of ONE shoot: the `data.items[].url` of `GET /v1/files/photoshoot:<id>`.

#### `image_has_no_model`

Which model this image shows is not known.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send an image from `photoshoot.create` or `mannequin_photoshoot.create`, or an edit of one — or name the face with `model` (`model:<id>`, `GET /v1/refs/model`).

#### `kit_empty`

A brand kit needs items, colours or both.

| Status | Type | Retryable |
|---|---|---|
| 422 | `invalid_request` | No |

**Fix:** Send `items`, `colours` or both.

### Type: `authentication`

No valid API key was sent.

#### `unauthenticated`

The request carries no valid API key or token.

| Status | Type | Retryable |
|---|---|---|
| 401 | `authentication` | No |

**Fix:** Send `Authorization: Bearer <key>` with an active key.

#### `invalid_api_key`

The API key is missing, malformed, revoked or expired.

| Status | Type | Retryable |
|---|---|---|
| 401 | `authentication` | No |

**Fix:** Send `Authorization: Key rf_live_…` with an active key. Create or roll keys from your account.

### Type: `permission`

Your key or your plan does not allow this.

#### `permission_denied`

Your account or plan does not allow this.

| Status | Type | Retryable |
|---|---|---|
| 403 | `permission` | No |

**Fix:** Check which tasks your plan includes, or contact support to change it.

#### `api_key_not_allowed_here`

An API key was sent from a browser.

| Status | Type | Retryable |
|---|---|---|
| 403 | `permission` | No |

**Fix:** Call the API from your server: a key in a web page or an app a user can read is a leaked key. Keep keys server-side.

#### `scope_missing`

Your API key's scopes do not include this action.

| Status | Type | Retryable |
|---|---|---|
| 403 | `permission` | No |

**Fix:** Create a key with the scope this action needs, or add it to the key.

#### `session_required`

This endpoint is not part of the public API; an API key cannot call it.

| Status | Type | Retryable |
|---|---|---|
| 403 | `permission` | No |

**Fix:** Use the public endpoints listed in the API reference. Manage API keys and webhook endpoints in the panel, signed in to your account.

#### `api_access_not_included`

Your plan does not include API access.

| Status | Type | Retryable |
|---|---|---|
| 403 | `permission` | No |

**Fix:** Upgrade the plan or contact support.

#### `subscription_inactive`

The account's subscription is inactive.

| Status | Type | Retryable |
|---|---|---|
| 403 | `permission` | No |

**Fix:** Renew the subscription, then retry.

### Type: `not_found`

Nothing of yours has this address, or it was removed.

#### `not_found`

The task, job or file does not exist, or it is not yours.

| Status | Type | Retryable |
|---|---|---|
| 404 | `not_found` | No |

**Fix:** Check the name or id. Ids from another account are not visible to you.

#### `task_removed`

This task was removed. It was deprecated first and is no longer served.

| Status | Type | Retryable |
|---|---|---|
| 410 | `not_found` | No |

**Fix:** Use the replacement the changelog's `Removed:` entry names; `GET /v1/tasks` lists the current tasks.

### Type: `conflict`

The request conflicts with the current state of what it names.

#### `conflict`

The request conflicts with the current state of the resource.

| Status | Type | Retryable |
|---|---|---|
| 409 | `conflict` | No |

**Fix:** Read the message; wait for the resource to change, or send a different request.

#### `result_not_ready`

The job has not finished successfully, so it has no result yet.

| Status | Type | Retryable |
|---|---|---|
| 409 | `conflict` | Yes |

**Fix:** Poll `GET /v1/jobs/{id}` until it has finished, then read the result.

#### `job_failed`

This job failed; its reason is the job read's `error`.

| Status | Type | Retryable |
|---|---|---|
| 409 | `conflict` | No |

**Fix:** Read GET /v1/jobs/{id}.

#### `idempotency_key_reused`

This Idempotency-Key was already used with a different request.

| Status | Type | Retryable |
|---|---|---|
| 409 | `conflict` | No |

**Fix:** Use a new key for a different request; reuse a key only to retry the same request.

#### `request_in_progress`

The first request with this Idempotency-Key is still being processed.

| Status | Type | Retryable |
|---|---|---|
| 409 | `conflict` | Yes |

**Fix:** Retry shortly with the same key and the same request.

#### `cancelled`

The job was cancelled before it finished.

| Status | Type | Retryable |
|---|---|---|
| 409 | `conflict` | No |

**Fix:** Nothing to fix: the job was cancelled (by you, or by us with a reason in `message`). Send the request again if you still want its result.

#### `fabric_not_recolourable`

This fabric was not made with `fabric.create`.

| Status | Type | Retryable |
|---|---|---|
| 409 | `conflict` | No |

**Fix:** Add colours to a fabric made with `fabric.create`.

#### `fabric_not_ready`

The fabric is still being made, or its making failed.

| Status | Type | Retryable |
|---|---|---|
| 409 | `conflict` | No |

**Fix:** Wait until `GET /v1/files/fabric:<id>` shows `status: ready`, then send again.

### Type: `insufficient_credits`

Your balance does not cover this request.

#### `insufficient_credits`

Your balance does not cover this request.

| Status | Type | Retryable |
|---|---|---|
| 402 | `insufficient_credits` | No |

| `ctx` key | Type | Meaning |
|---|---|---|
| `required` | integer | The credits this request needs. |
| `balance` | integer | The credits your balance holds now. |

**Fix:** Top up credits; `/estimate` shows what the request needs.

### Type: `rate_limited`

Too many requests; wait and retry.

#### `rate_limited`

Too many requests in a short time.

| Status | Type | Retryable |
|---|---|---|
| 429 | `rate_limited` | Yes |

| `ctx` key | Type | Meaning |
|---|---|---|
| `retry_after` | integer | Seconds to wait before the next call. |

**Fix:** Wait for the number of seconds in the `Retry-After` header, then retry.

### Type: `content_refused`

The inputs were refused.

#### `content_refused`

The inputs were refused.

| Status | Type | Retryable |
|---|---|---|
| 422 | `content_refused` | No |

**Fix:** Change the prompt or the images. Sending the same request again will be refused again.

### Type: `processing_failed`

The job ran and failed.

#### `processing_failed`

The job ran and failed.

| Status | Type | Retryable |
|---|---|---|
| 422 | `processing_failed` | No |

**Fix:** If `retryable` is true, send the request again; otherwise change the inputs.

#### `images_not_fetchable`

None of the images could be read.

| Status | Type | Retryable |
|---|---|---|
| 422 | `processing_failed` | No |

**Fix:** Send image URLs that are public and reachable, or files from an earlier result.

#### `no_brand_dna`

None of the images could be used.

| Status | Type | Retryable |
|---|---|---|
| 422 | `processing_failed` | No |

**Fix:** Send clearer photos of garments or looks.

#### `capacity_busy`

Too many of your jobs are running at once.

| Status | Type | Retryable |
|---|---|---|
| 422 | `processing_failed` | Yes |

**Fix:** Wait for some to finish, then send the request again.

### Type: `internal`

Something went wrong on our side.

#### `start_timeout`

The job could not be started in time.

| Status | Type | Retryable |
|---|---|---|
| 503 | `internal` | Yes |

**Fix:** Retry the request with the same Idempotency-Key.

#### `internal`

Something went wrong on our side.

| Status | Type | Retryable |
|---|---|---|
| 500 | `internal` | Yes |

**Fix:** Retry. If it keeps happening, contact support with the `request_id`.

### Type: `warning`

Not an error: a note on a job that succeeded.

#### `output_not_produced`

One output of this job could not be produced; `field` names which.

| Status | Type | Retryable |
|---|---|---|
| 200 | `warning` | No |

**Fix:** Nothing is charged for it. Send a request for that output alone to try again.

#### `image_not_fetchable`

One of the images you sent could not be read, so it was left out; `field` names which.

| Status | Type | Retryable |
|---|---|---|
| 200 | `warning` | No |

**Fix:** Send image URLs that are public and reachable, or files from an earlier result.

#### `svg_needs_review`

Some shapes of the SVG are on layers whose names contain `Unassigned`.

| Status | Type | Retryable |
|---|---|---|
| 200 | `warning` | No |

**Fix:** Check those layers before cutting or printing; the rest is complete.

#### `already_4k`

The image is already 4K; nothing was charged.

| Status | Type | Retryable |
|---|---|---|
| 200 | `warning` | No |

**Fix:** Nothing to do: the file holds the image's own pixels as a new file of this job.

#### `item_duplicate`

This url is already in the kit; it was not added twice.

| Status | Type | Retryable |
|---|---|---|
| 200 | `warning` | No |

**Fix:** Nothing to do: the kit already holds the item. Send each url once.

#### `view_generated`

This view was newly made.

| Status | Type | Retryable |
|---|---|---|
| 200 | `warning` | No |

**Fix:** Nothing to do: the product had no saved copy of this view, so it was made as usual.
