> ## Documentation index
> Fetch the complete documentation index at: https://docs.odysseysignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> How the Signal API reports errors: the problem format, every error type, refusals and rate-limit headers, and how to handle them.

Last reviewed 9 November 2026

When a request fails, the Signal API answers with an HTTP error status and a JSON body in the standard problem format ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)). The content type is `application/problem+json`. Every error has the same shape, so your code can handle them all in one place.

## The shape of an error

This is the answer to a request without a valid sign-in or API key:

```json
{
  "type": "https://api.odysseysignal.app/problems/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Authentication failed.",
  "instance": "/v1/organizations"
}
```

| Field | What it holds |
| --- | --- |
| type | A URL that names the kind of error. Use it, not the title or detail, to decide what your code does. It identifies the error; it is not a page to open. |
| title | A short summary of the kind of error. The same for every error of that type. |
| status | The HTTP status code, the same as on the response. |
| detail | What went wrong with this request, in one or two sentences you can show to a person. |
| instance | The path of the request that failed. |

Some errors add fields of their own, such as `errors` for an invalid request or `reason` for a refusal. Ignore fields you do not know: new ones can be added without a new API version.

## Error types

Every type starts with `https://api.odysseysignal.app/problems/`. The table lists the end of the URL.

| Status | Type | What it means |
| --- | --- | --- |
| 400 | invalid-request | The request is well formed, but it asks for something that cannot be answered, for example a metric or dimension the DataSet does not have. The detail names what was asked for and what is available. |
| 400 | query_too_large | The query would read more data than one query may read, so it was not run. Ask a narrower question: a shorter period, or fewer markets, metrics or breakdowns. |
| 401 | unauthorized | The sign-in token or API key is missing, expired or not valid. The answer is always the same, whatever the cause. |
| 403 | forbidden | Your credential is valid, but it does not give access to what you asked for, or access is blocked for your organization or account. |
| 404 | not-found | The resource does not exist, or your credential does not give access to it. The two cases answer the same. |
| 404 | no-such-route | No endpoint has this path. Check the path against the API reference. |
| 422 | validation-failed | The request does not match the contract: a field is missing, has the wrong type or an invalid value. See the errors field. |
| 429 | rate_limited | Too many requests in a short time. Wait as long as Retry-After says, then try again. |
| 500 | internal-error | Something went wrong on our side. If it keeps happening, tell us the path and the time of the request. |
| 503 | unavailable | A service the request depends on did not answer, so the request was not run. Try again after Retry-After. |
| 405 and others | http-error | Any other HTTP error, such as a method the endpoint does not support. The detail says which. |

> [!NOTE]
> **Types are stable**
>
> A type keeps its meaning within an API version. New types can be added, so treat an unknown type by its HTTP status. See [API versions and changes](https://docs.odysseysignal.com/api/api-versioning).

## Invalid requests (422)

A `validation-failed` error lists every invalid field in `errors`. Each entry has the `location` of the value, a `message` and a machine-readable `type`:

```json
{
  "type": "https://api.odysseysignal.app/problems/validation-failed",
  "title": "Request does not match the contract",
  "status": 422,
  "detail": "One or more fields are invalid. See `errors` for each.",
  "instance": "/v1/...",
  "errors": [
    {
      "location": ["body", "metrics", 0],
      "message": "Field required",
      "type": "missing"
    }
  ]
}
```

`location` is the path to the value: where it is (`body`, `query` or `path`), then the field names and list positions inside it.

## Refusals (403 and 429)

When a request is refused even though your credential is valid, the error adds two fields for your code:

- `reason`: which kind of refusal it is, such as `rate_limit` or `organization_blocked`.
- `opens_at`: when the request can succeed again, as an ISO 8601 time in UTC, or `null` when it does not open again by itself.

The `detail` says what happened, when it opens again and who can change it, usually your organization's administrator.

## Rate limits

Requests that count against a rate limit carry these headers on every answer, not only when you are refused, so you can slow down before you reach the limit:

| Header | What it holds |
| --- | --- |
| X-RateLimit-Limit | How many requests the limit allows. |
| X-RateLimit-Remaining | How many you have left. |
| X-RateLimit-Reset | Seconds until the limit resets. |
| X-RateLimit-Scope | Which limit the numbers describe, such as queries. |
| Retry-After | On a 429 or 503 only: seconds to wait before you try again. |

A request counts against the limit even when it fails, for example with a 400 or 422.

Which requests count against which limit, and how to handle limits in your code: see [Rate limiting](https://docs.odysseysignal.com/api/rate-limiting).

## Handling errors

- Decide by `status` and `type`. Show `detail` to a person; do not parse it, because its wording can change.
- Retry only a 429 or 503, and only after the time in `Retry-After`. Retrying any other error gives the same answer.
- Fix the request on a 400 or 422: the error names the field or value that is wrong.
- On a 401, get a new sign-in token or check the API key. The answer does not say which part was wrong. See [Authentication](https://docs.odysseysignal.com/api/authentication).
- On a 500, note the `instance` and the time and contact us if it keeps happening.
