Skip to content

Errors

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

When a request fails, the Signal API answers with an HTTP error status and a JSON body in the standard problem format (RFC 9457). 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"
}
FieldWhat it holds
typeA 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.
titleA short summary of the kind of error. The same for every error of that type.
statusThe HTTP status code, the same as on the response.
detailWhat went wrong with this request, in one or two sentences you can show to a person.
instanceThe 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.

StatusTypeWhat it means
400invalid-requestThe 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.
400query_too_largeThe 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.
401unauthorizedThe sign-in token or API key is missing, expired or not valid. The answer is always the same, whatever the cause.
403forbiddenYour credential is valid, but it does not give access to what you asked for, or access is blocked for your organization or account.
404not-foundThe resource does not exist, or your credential does not give access to it. The two cases answer the same.
404no-such-routeNo endpoint has this path. Check the path against the API reference.
422validation-failedThe request does not match the contract: a field is missing, has the wrong type or an invalid value. See the errors field.
429rate_limitedToo many requests in a short time. Wait as long as Retry-After says, then try again.
500internal-errorSomething went wrong on our side. If it keeps happening, tell us the path and the time of the request.
503unavailableA service the request depends on did not answer, so the request was not run. Try again after Retry-After.
405 and othershttp-errorAny other HTTP error, such as a method the endpoint does not support. The detail says which.

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:

HeaderWhat it holds
X-RateLimit-LimitHow many requests the limit allows.
X-RateLimit-RemainingHow many you have left.
X-RateLimit-ResetSeconds until the limit resets.
X-RateLimit-ScopeWhich limit the numbers describe, such as queries.
Retry-AfterOn 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.

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.
  • On a 500, note the instance and the time and contact us if it keeps happening.

Last reviewed 9 November 2026

Errors · Odyssey Docs