# Errors

> One error envelope, eight codes, and the request id to quote when you ask about one.

Source: https://mockflow.com/developers/concepts/errors

Every failure answers with the same envelope and the HTTP status that matches it. There is no second error shape anywhere in the API, so one handler covers every call you make.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "The request body does not match the schema for a kanban item.",
    "details": [
      {
        "path": "/data/columns/0/title",
        "message": "must be a string"
      }
    ],
    "docsUrl": "https://mockflow.com/developers/concepts/errors#validation_failed"
  },
  "requestId": "req_45d49816999bac40"
}
```

### Fields

- `error.code` (enum<string>, required): A stable machine name. Switch on this, never on the message.
- `error.message` (string, required): One sentence for a person, written to be shown to the caller. It can change between releases, so do not match on it.
- `error.details` (object[]): Present on `validation_failed`. One entry per problem, each with the JSON path of the field that was refused.
- `error.docsUrl` (string): The page explaining this code.
- `error.upgradeUrl` (string): Present on `plan_limit`, where the fix is a plan rather than a payload.
- `requestId` (string, required): Quote this when you ask us about a request. It is also returned on success, in the `MockFlow-Request-Id` header, and it is how we find the call in the log.

### Codes

- 401 `unauthorized`: No token, or a token that is expired, revoked or not a MockFlow token. Check the `Authorization: Bearer` header.
- 403 `forbidden`: The token is good but is not allowed to do this: either the key lacks the scope the operation requires, or the account behind it cannot reach that board. The message says which.
- 404 `not_found`: No such space, board, item, skill or job, or one the caller cannot see. The two are deliberately the same answer, so that probing ids tells you nothing.
- 422 `validation_failed`: The body does not match the schema. `details` carries a JSON path per problem, so the field that is wrong can be pointed at rather than searched for.
- 409 `conflict`: The request cannot apply to the current state: a member who is already a member, an Idempotency-Key reused with a different body.
- 402 `plan_limit`: The workspace's plan does not allow this, such as one more board or an AI generation with no credits left. `upgradeUrl` goes to the plan page.
- 429 `rate_limited`: Too many requests in the window. `Retry-After` says how many seconds to wait.
- 500 `internal`: Our fault. Safe to retry with a delay. Quote the `requestId` if it persists.

## Retrying

`rate_limited` and `internal` are worth retrying, with a delay that doubles each time. `rate_limited` says how long to wait in `Retry-After`. Nothing else is worth retrying: the same request will be refused the same way.

> **Note** Retrying a write safely means sending an Idempotency-Key with the first attempt, not only with the retry.
