Concepts

# Errors

Every error response carries the same envelope, whatever the status: a short stable `type` naming the kind of failure, a human-readable message, and your echoed request id. On validation failures, `errors` lists one entry per field-level problem; on an idempotency conflict it holds one entry whose code says which kind, and `resourceId` carries the id the original request created; on every other kind of failure it is empty.

```json
{
  "type": "validation_failure",
  "message": "The request could not be processed.",
  "requestId": "d5f7a8c2-3e1b-4d9f-a6c0-8b2e4f1d7a3c",
  "errors": [
    {
      "code": "product_not_found",
      "message": "No product exists with that id.",
      "param": "lines[2].catalogue.product_id"
    }
  ]
}
```

## Reading it

- `type` is the first thing to branch on — `validation_failure`, `not_found`, `failed_precondition`, `conflict`, `rate_limited`. Both `validation_failure` and `failed_precondition` are HTTP 400, but the first means bad input and the second means the document is in a state that forbids the change. Treat an unrecognised type as a generic error.

- `param` points at the offending field in snake_case, indexed into arrays. Map it back to your own line numbers so an operator sees which row is wrong.

- `code` is stable and safe to branch on. The list is not exhaustive and will grow, so treat anything you do not recognise as a generic validation failure — never as a hard crash.

- All the problems in one request come back together. Show the operator the whole list, not just the first.

## Stable codes so far

| HTTP | Type | When |
| --- | --- | --- |
| 200 | — | The call succeeded. |
| 400 | validation_failure | Validation failed. The envelope's errors list has one entry per field-level problem. |
| 400 | failed_precondition | The document is in a state that forbids the change — editing a line on an invoiced order. |
| 401 | unauthenticated | Missing, invalid or expired bearer token. |
| 403 | permission_denied | The operation needs a company administrator. |
| 404 | not_found | No such id in your company — including a quote id on an order endpoint. |
| 409 | conflict | An idempotency key was repeated: the original request completed (resourceId in the error body carries the id it created), its outcome is unknown, or the body differed. Verify with a read, then use a fresh key. |
| 429 | rate_limited | Throttled. Wait for Retry-After, then retry. |
| 429 | resource_exhausted | A size limit was exceeded (request or response too large). Retrying unchanged will fail; send less or request a smaller page. |
| 501 | not_implemented | The input is not supported yet — adding a flashing line to an existing order, replacing or removing one, or creating an order or quote with companyName instead of customerId. |
| 503 | unavailable | The idempotency store is unavailable. Only requests carrying a requestId are rejected; requests without a key are unaffected. |

---

Source: https://developer.factory.app/errors · Factory Sales API v1
