# Errors

Source: https://docs.mirafive.io/rest-api/errors

> Every error status of the MIRA FIVE REST API, its problem details body, what the message says and how to fix the request.

The REST API answers every error as RFC 9457 problem details with `Content-Type: application/problem+json`. Decide by the status: retry `429` and `503` after `Retry-After`, fix the request for every other `4xx`.

## Error body

```json title="401 Unauthorized"
{
  "type": "about:blank",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Send a personal API key or an OAuth access token as a Bearer token.",
  "instance": "/api/v1/projects"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `type` | string | `about:blank` for a plain HTTP problem. MIRA FIVE's own problems name a section of the docs, e.g. `https://docs.mirafive.io/docs/api#query-too-large`. |
| `title` | string | The status text, or the name of MIRA FIVE's own problem: `Query too large`, `Analytics unavailable`. |
| `status` | integer | The HTTP status. |
| `detail` | string | What went wrong, in a sentence. Left out on `500`. |
| `instance` | string | The path that was asked for. |
| `errors` | object | `422` validation only: each parameter with its messages. |

Error bodies carry the same security headers as successful ones.

## Status codes

| Status | `detail` | Cause | Fix |
| --- | --- | --- | --- |
| `401` | `Send a personal API key or an OAuth access token as a Bearer token.` | No `Authorization: Bearer` header. Comes with `WWW-Authenticate: Bearer realm="api"`. | Send an [API key](https://docs.mirafive.io/keys#create-an-api-key) as a bearer token. |
| `401` | `The Bearer token is not valid: it is unknown, revoked or expired, or its account cannot sign in.` | Unknown or malformed token, expired OAuth token, or an API key whose account is blocked or has not confirmed its email address. | Check the key. Renew an OAuth token with its refresh token. |
| `401` | `This API key was revoked or has expired. Create a new one on the API & MCP page.` | The API key was revoked, or its lifetime ended. | Create a new key. |
| `403` | `Your email address is not verified.` | OAuth token of an account that has not confirmed its email address. | Confirm the address, then call again. |
| `403` | `This account is blocked.` | OAuth token of a blocked account. | Contact hello@mirafive.io. |
| `404` | `The requested resource does not exist, or you may not see it.` | Unknown path, malformed id (a slug instead of a UUID), a record that does not exist, or a project outside your organizations. An action without a goal role on `/goals/{goal_id}` answers the same. | Take ids from the list endpoints. |
| `404` | `Nobody with this ref within what the plan keeps, or they were erased.` | [Get a person](https://docs.mirafive.io/rest-api/people#get-a-person) with a ref the project does not know. | Take the ref from the People list or an event. |
| `405` | `This endpoint answers GET, HEAD only. The API is read-only.` | Any method but `GET` or `HEAD`. Comes with `Allow: GET, HEAD`. | The REST API cannot write. Changes go through the dashboard or the [MCP server](https://docs.mirafive.io/mcp). |
| `422` | `The request has invalid parameters.` | A parameter the endpoint cannot use. `errors` names it. See [Validation errors](#validation-errors). | Fix the parameter named in `errors`. |
| `422` | `This question reads more data than one request may. Shorten the period or narrow it with filters.` | See [Query too large](#query-too-large). | Ask for less. |
| `429` | `The limit is 120 requests per minute. Try again in N seconds.` | Your per-person budget is spent. | Wait `Retry-After` seconds. |
| `429` | `Too many refused tokens came from this network. Try again in N seconds.` | More than 60 refused tokens in a minute from your network. | Fix the token, then wait `Retry-After` seconds. |
| `500` | none | An error on MIRA FIVE's side. | Retry later. If it persists, write to hello@mirafive.io with the time and path. |
| `503` | `The analytics warehouse did not answer. Try again shortly.` | See [Warehouse unavailable](#warehouse-unavailable). | Wait `Retry-After` seconds. |

The two `401` answers to a token that was sent and refused also come with `WWW-Authenticate: Bearer realm="api", error="invalid_token", error_description="…"`, where the description repeats `detail`.

## Validation errors

A `422` names each parameter it refuses in `errors`, with the reason. A closed set of values is listed in the message:

```bash
curl -G https://app.mirafive.io/api/v1/projects/01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f/overview \
  -H "Authorization: Bearer $MIRAFIVE_API_KEY" \
  --data-urlencode 'period=forever' \
  --data-urlencode 'filters[0][field]=country' \
  --data-urlencode 'filters[0][op]=gt' \
  --data-urlencode 'filters[0][value]=DE'
```

```json title="422 Unprocessable Content"
{
  "type": "about:blank",
  "title": "Unprocessable Content",
  "status": 422,
  "detail": "The request has invalid parameters.",
  "instance": "/api/v1/projects/01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f/overview",
  "errors": {
    "period": ["The period must be one of: 1h, 24h, 7d, 30d, 90d, 12m, custom."],
    "filters.0.op": ["This field cannot be compared that way."]
  }
}
```

Messages you meet most:

| Parameter | Message | Fix |
| --- | --- | --- |
| `period`, `compare`, `view`, `sort`, `window`, `basis`, `touch` | `The <parameter> must be one of: …`, e.g. `The touch must be one of: first, last.` | Use one of the values listed. |
| `from`, `to` | `from only applies to period=custom. Leave it out, or set period=custom.` | Add `period=custom`, or drop the dates. |
| `from`, `to` | `This endpoint takes no custom period. Leave out from and to.` | Events take presets only. |
| `to` | `to must be on or after from.` | Swap the days. |
| `from` | `from is after today in the project’s time zone (Europe/Berlin).` | Start on or before today. |
| `to` | `to is before 2025-09-26, the first day the plan keeps.` | Ask for days the plan keeps (`retentionDays` on the [project](https://docs.mirafive.io/rest-api/projects)). |
| `compare` | `This endpoint does not compare periods. Leave out compare.` | Only the overview, a goal report and search compare. |
| `cursor` | `The cursor is not one this list handed out; follow links.next.` | Use `links.next` of the same list. On People, the cursor also belongs to one `sort`. |
| `filters.N.field` | `Choose a field to filter by.` | Use a field from [Filters](https://docs.mirafive.io/rest-api#fields). |
| `filters.N.field` | `This project collects without consent, so it is not filtered by channel.` | Drop the channel filter on this project. |
| `filters.N.field` | `A trait filters a segment, not the People list.` | Filter by a segment that uses the trait. |
| `filters.N.op` | `This field cannot be compared that way.` | Use an operator the field takes. |
| `filters.N.value` | `Enter a value.`, `Enter a number.`, `Choose a channel.`, `Choose a country.`, `Choose one of the values offered.` | Send a value of the right kind. |

## Query too large

```json title="422 Unprocessable Content"
{
  "type": "https://docs.mirafive.io/docs/api#query-too-large",
  "title": "Query too large",
  "status": 422,
  "detail": "This question reads more data than one request may. Shorten the period or narrow it with filters.",
  "instance": "/api/v1/projects/01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f/overview"
}
```

Each request may read a limited amount of data. A long period over a busy project, or a filter that has to read many events, can pass it. Sending the same request again gets the same answer. Ask for less instead:

- Shorten the period, for example `90d` instead of `12m`, or split a long custom range into shorter ones. Counts of events add up across ranges; counts of people do not.
- Add filters that narrow the events early: `event_name`, `page_path`, `country`.
- On People, leave out `window=all` and `sort=first_seen` or `sort=value`, which read each person's whole history.

## Warehouse unavailable

```json title="503 Service Unavailable"
{
  "type": "https://docs.mirafive.io/docs/api#warehouse-unavailable",
  "title": "Analytics unavailable",
  "status": 503,
  "detail": "The analytics warehouse did not answer. Try again shortly.",
  "instance": "/api/v1/projects/01932c4e-8a7b-7c3d-9e2f-4b5a6c7d8e9f/overview"
}
```

The store that holds events did not answer. Nothing is wrong with your request. The answer carries `Retry-After: 30`: wait that many seconds and send it again. Endpoints that do not read events, such as the [projects](https://docs.mirafive.io/rest-api/projects) list, keep working.

## Retry policy

- Retry `429` and `503` after the `Retry-After` seconds. Retry `500` and network errors with a growing delay, for example 1, 2, 4 and 8 seconds.
- Do not retry any other `4xx`. The same request gets the same answer.
- Every request only reads, so a retry never changes anything.
