# Errors

The Layer REST API uses conventional HTTP status codes and returns a consistent, machine-readable error body following [RFC 7807 Problem Details](https://datatracker.ietf.org/doc/html/rfc7807).

## Error shape

Errors are returned with the `application/problem+json` content type and this shape:

```json
{
  "type": "https://api.layer.ai/errors/WORKSPACE_NOT_FOUND",
  "title": "Workspace not found",
  "status": 404,
  "detail": "Workspace 2489e9ad-… doesn't exist or you're not a member.",
  "error_code": "WORKSPACE_NOT_FOUND"
}
```

* **`error_code`**: the machine-readable error code. Branch on this, not on the human-readable `title` or `detail`.
* **`type`**: a stable URI ending in the same code.
* **`status`**: mirrors the HTTP status.
* **`detail`**: a human-readable explanation. For `4xx` errors this is specific; for `5xx` errors it’s scrubbed to a generic message.

## Error codes

| Code                  | Status | Meaning                                                                          |
| --------------------- | ------ | -------------------------------------------------------------------------------- |
| UNAUTHENTICATED       | 401    | Missing or invalid bearer token. See [Authentication](/docs/authentication).     |
| FORBIDDEN             | 403    | The authenticated user lacks permission for this action or workspace.            |
| INSUFFICIENT\_BALANCE | 402    | Not enough Creative Units. See [Creative Units & billing](/docs/creative-units). |
| RATE\_LIMITED         | 429    | Too many requests. See [Rate limits](/docs/rate-limits).                         |
| WORKSPACE\_NOT\_FOUND | 404    | Workspace doesn’t exist or you’re not a member.                                  |
| MODEL\_NOT\_FOUND     | 404    | Model not found or not available in this workspace.                              |
| WORKFLOW\_NOT\_FOUND  | 404    | Workflow not found or not linked to this workspace.                              |
| INFERENCE\_NOT\_FOUND | 404    | Inference (forge) run doesn’t exist.                                             |
| RUN\_NOT\_FOUND       | 404    | Workflow run doesn’t exist.                                                      |
| INVALID\_INPUTS       | 422    | Malformed parameters, UUIDs, or an invalid pagination cursor.                    |
| UPLOAD\_FAILED        | 502    | A file could not be downloaded or stored.                                        |
| INTERNAL\_ERROR       | 500    | An unexpected server error. Retry later; contact support if it persists.         |

## Asynchronous failures

Generation is [asynchronous](/docs/async-jobs), and **not every failure surfaces as an HTTP error**. In particular, your Creative Unit balance is **not** checked when you submit a run: an underfunded run is accepted with `202` and later reaches a terminal `failed`/`failure` state carrying `error_code: INSUFFICIENT_BALANCE`.

Caution

Always check the terminal status and `error_code` of a run when you poll it: a `202` on submit does not guarantee success. [Estimate](/docs/creative-units) before you execute to avoid this.

## Refused generations

Guardrails and provider content policies judge a generation after it is accepted, so a refusal never comes back as a `4xx`. The run fails with one of these `error_code` values, and carries a `refusal` object that says why:

| Code                           | Meaning                                                                                             |
| ------------------------------ | --------------------------------------------------------------------------------------------------- |
| INPUT\_GUARDRAIL\_VIOLATION    | A guardrail your workspace or project set blocked the request. Nothing was sent to the provider.    |
| CONTENT\_POLICY\_VIOLATION     | The model provider refused the request under its content policy.                                    |
| PROMPT\_INTELLECTUAL\_PROPERTY | The model provider refused the request because it asked for protected intellectual property.        |
| NO\_MEDIA\_GENERATED           | The provider returned no output and gave no reason. This is usually a silent refusal of the prompt. |

```json
{
  "inference_id": "6f1c…",
  "status": "failed",
  "error": "This request was blocked by your workspace's “No Nintendo IP” guardrail. The prompt names Mario.",
  "error_code": "INPUT_GUARDRAIL_VIOLATION",
  "refusal": {
    "inference_id": "6f1c…",
    "code": "INPUT_GUARDRAIL_VIOLATION",
    "guardrails": [
      {
        "guardrail_id": "0b6e…",
        "name": "No Nintendo IP",
        "category": "custom",
        "reason": "The prompt names Mario."
      }
    ]
  }
}
```

* **`guardrails`**: for `INPUT_GUARDRAIL_VIOLATION`, every guardrail that blocked the request, with its id, its name, the reason it gave, and its `category`: `copyright_infringement`, `ip_trademark_infringement` or `artist_style_mimicry` for the guardrails Layer ships, `custom` for a rule your workspace wrote. Empty for every other code.

`refusal` is `null` on every other outcome. A workflow run that a refused generation stopped fails with `BlueprintInvalidRuntimeValueError` and lists every refused generation in `refusals`, oldest first, in the same shape. Webhook deliveries carry the same fields, since their `data` is the matching `GET` body.

Resubmitting the same inputs is refused the same way. Change the prompt or the reference images, or ask a workspace admin about the guardrail it names.

## Handling errors

* Retry `429` after the `Retry-After` interval, and `5xx` with exponential backoff.
* Do **not** blindly retry `4xx` errors other than `429`. Fix the request first.
* A retry of an _execute_ request that actually succeeded server-side creates a **duplicate run** unless you sent an [idempotency key](/docs/integration-notes) with it. Send one on every submit, or prefer polling an existing run over re-submitting.
