# Errors

> One error shape for every failure: the status, a stable code, a message with the fact and the next step, details per field — every code, and how to debug.

Updated 2026-10-24 · HTML: https://layercrew.com/developers/errors · Markdown: https://layercrew.com/developers/errors.md · OpenAPI: https://layercrew.com/openapi.json

Every error is JSON in one shape: a stable `code` to branch on, a `message` that states the fact and the next step, and `details` when a field or an object is involved. The HTTP status tells you the class; the code tells you what to do.

_422 — two fields to fix_

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "two fields need a change — see details",
    "details": {
      "items.0.material": "pick a material from GET /v1/materials",
      "delivery.phone": "the courier needs a phone number"
    }
  }
}
```

_409 — the reference exists_

```json
{
  "error": {
    "code": "REFERENCE_TAKEN",
    "message": "an order with this reference already exists in this environment — see details.order_id",
    "details": {
      "order_id": "7a3e9c10-2b4d-4f61-9e8a-5c6d7e8f9a0b"
    }
  }
}
```

## Statuses

| Status | Class | What to do |
| --- | --- | --- |
| `401` | The key. | Fix the key — missing, malformed, unknown or revoked — then send again. |
| `403` | The account or the environment. | Read the code: the account is inactive, the key's prefix is wrong, or the route is test-only. |
| `404` | No such object in this environment. | Check the id and the environment of the key. |
| `409` | The request conflicts with the current state. | Read the code; most carry `details` naming the object in the way. |
| `413` | The body is over 256 KiB. | File bytes go to the signed upload URL, never into JSON. |
| `422` | A field is wrong. | Fix the fields named in `details` and send again. |
| `429` | Pace or a monthly cap. | Wait `Retry-After` seconds; see [Rate limits](https://layercrew.com/developers/rate-limits). |
| `5xx` | Our side. | Retry with backoff and jitter; quote `X-Request-ID` if it repeats. |

## Codes

### The key

Answered before the request is read. Fix the key, then send again.

| Code | Status | What it means and what to do |
| --- | --- | --- |
| `INVALID_API_KEY` | 401 | The API key is missing, malformed or unknown — send a live or test key as `Authorization: Bearer lc_live_…` or `lc_test_…`. |
| `API_KEY_REVOKED` | 401 | This API key was revoked or has expired — create a new one in your dashboard under Integrations → Keys. |
| `SCOPE_MISMATCH` | 403 | The key's prefix names a different environment than the key was issued for — use the key exactly as it was shown. |
| `PARTNER_INACTIVE` | 403 | This partner account is not open for this key yet — live keys need an active account with a countersigned agreement; your dashboard shows the next step. |
| `TEST_ONLY` | 403 | This route is for test keys — call it with your `lc_test_` key; a live order moves through its states as production happens. |

### The request

The request shape or pace. `error.details` names the field when there is one.

| Code | Status | What it means and what to do |
| --- | --- | --- |
| `RATE_LIMITED` | 429 | Too many requests for this key — wait the seconds in `Retry-After` and send again. |
| `QUOTA_EXCEEDED` | 429 | This month's cap for your plan is reached — it resets on the first of next month (UTC); write to hello@layercrew.com for a higher cap. |
| `VALIDATION_ERROR` | 422 | A field is wrong — `error.details` maps each field (`items.0.material`) to what to change. |
| `PAYLOAD_TOO_LARGE` | 413 | The request body is over 256 KiB — file bytes go to the signed upload URL, never into a JSON body. |
| `IDEMPOTENCY_KEY_REQUIRED` | 422 | Send an `Idempotency-Key` header (1 to 255 characters of letters, digits and `. _ : ~ -`) with every order so a retry never creates a second one. |
| `IDEMPOTENCY_KEY_REUSED` | 409 | This `Idempotency-Key` was already used for a different request — use a new key for a new request, or send the same request to get the same answer. |
| `IDEMPOTENCY_IN_PROGRESS` | 409 | A request with this `Idempotency-Key` is still being processed — wait the seconds in `Retry-After` and send the same request again. |
| `NOT_FOUND` | 404 | No such object in this environment — ids are per environment; a test key sees test objects only. |

### Files

Uploads and the files an order points at.

| Code | Status | What it means and what to do |
| --- | --- | --- |
| `FILE_REJECTED` | 422 | The uploaded bytes failed verification — `error.details.rejection_code` says why; upload a corrected file. |
| `FILE_NOT_READY` | 409 | The file is still uploading or was rejected — call `complete` and wait for `status: ready`, or upload a corrected file. |
| `FILE_IN_USE` | 409 | An open order, a pending quote or a running price job still needs this file — `error.details` names it; delete the file once that has finished. |

### Quotes and orders

Creating, pricing and cancelling.

| Code | Status | What it means and what to do |
| --- | --- | --- |
| `MATERIAL_UNAVAILABLE` | 422 | This material, nozzle or colour is outside your plan or out of stock for this run — pick one from `GET /v1/materials`. |
| `REFERENCE_TAKEN` | 409 | An order with this `reference` already exists in this environment — `error.details.order_id` names it; use a new reference for a new order. |
| `CANNOT_CANCEL` | 409 | Production has started on this order — write to us from the order page in your dashboard and we tell you what is still possible. |
| `ACCEPT_IN_PROGRESS` | 409 | This order is being accepted right now — read it again in a moment. |

### Webhooks

Endpoints and deliveries.

| Code | Status | What it means and what to do |
| --- | --- | --- |
| `ENDPOINT_LIMIT` | 409 | This environment already has the maximum number of webhook endpoints your plan allows — delete one, or ask us to raise the limit. |
| `ENDPOINT_URL_INVALID` | 422 | The webhook URL must be HTTPS on port 443 or 8443, without credentials, on a public host that resolves. |
| `EVENT_TYPE_INVALID` | 422 | One or more event types are outside the published list — use the names from the lifecycle page, or omit `event_types` to receive every type. |
| `ENDPOINT_DISABLED` | 409 | This webhook endpoint is disabled — enable it with `PATCH /v1/webhook-endpoints/{id}` and `{"enabled": true}`, then try again. |
| `DELIVERY_IN_FLIGHT` | 409 | This delivery is being attempted right now — wait for its outcome, then redeliver if needed. |

### Account and agreement

Met in the dashboard or when a key is issued, rarely on `/v1/*`.

| Code | Status | What it means and what to do |
| --- | --- | --- |
| `TERMS_REACCEPTANCE_REQUIRED` | 409 | A new version of the terms is in force — accept it in your dashboard under Integrations → Agreement to keep ordering. |
| `AGREEMENT_REQUIRED` | 409 | Accept the integration agreement and the data processing agreement in your dashboard first. |
| `AGREEMENT_NOT_COUNTERSIGNED` | 409 | Live keys are issued once LayerCrew has countersigned your integration and data processing agreements — test keys work meanwhile. |
| `TERMS_UNAVAILABLE` | 422 | Payment terms are set by your plan — orders are paid before production by default; ship-before-payment can be agreed instead. |
| `ORGANIZATION_ALREADY_REGISTERED` | 409 | This company already has a partner account — ask its account owner for an invitation. |
| `EMAIL_TAKEN` | 409 | This email already has a LayerCrew login — sign in and open Integrations to register your company. |

## Validation details

`422 VALIDATION_ERROR` maps field paths to messages: `items.0.material`, `delivery.phone`, `white_label.card_text`. Array positions are zero-based. Fix the named fields and send again; the message says what to change. A file-level problem names the file: `FILE_REJECTED` carries `details.rejection_code`, `FILE_IN_USE` the id of the order or quote that needs it.

## A 403 without JSON

> **The network edge.** A `403` with an HTML or empty body — no JSON envelope — comes from the network edge in front of the API, throttling your address. Back off 90 seconds, then resume at two requests a second. Never retry a create blindly after it: replay the `Idempotency-Key`, or look the order up by `reference`. See [Rate limits](https://layercrew.com/developers/rate-limits#edge).

## Debugging a call

Every response carries `X-Request-ID`. Your request log — `GET /v1/requests` with `from`, `to`, `status` and `route` filters, `GET /v1/requests/{request_id}`, and the Requests page of your dashboard — lists every call made with your keys in the last 90 days: route, status, duration, error code and the id. Bodies are never stored. Quote the id when you write to us at hello@layercrew.com.
