Skip to content

Error codes reference

Every error from the Spanlight API is a JSON document with the media type application/problem+json, as defined by RFC 9457. This page explains how to read one and lists the codes you meet most often. The API conventions page has the full catalogue, including the codes of single features such as prompts, datasets and experiments.

The gateway under /gw/ answers in the format of the provider API it imitates, not in problem+json. See Gateway errors for those.

{
"type": "about:blank",
"title": "Unprocessable Entity",
"status": 422,
"detail": "The request is invalid.",
"code": "VALIDATION_ERROR",
"request_id": "0b7c1f0e5d2a4c6e8f1a3b5c7d9e2f40",
"errors": [{ "field": "retention_days", "message": "Input should be less than or equal to 90" }]
}
Field Meaning
status The HTTP status code, repeated in the body.
title The standard name of the status.
detail A sentence for people. Do not parse it, because the wording can change.
code A stable name for the error. Branch on this in your code.
request_id The ID of the request. It is also in the X-Request-ID response header. Quote it when you report a problem.
errors Only on VALIDATION_ERROR. One entry for each field, with its dotted path and a message.

Some errors add more fields. A response can also carry a Retry-After header, in whole seconds. RATE_LIMITED and SERVICE_UNAVAILABLE always do.

  1. Look at the status first. A 4xx means the request is wrong or not allowed. Retrying it unchanged will not help, except for 429 and IDEMPOTENCY_IN_PROGRESS. A 5xx means the server could not finish the request. A GET is safe to retry. A write may have been applied in part, so retry it only if the route accepts an Idempotency-Key. Send the same key again. Spanlight never keeps the answer to a 5xx under a key, so the retry runs. Today the routes that accept the header create exports, run playground prompts, start experiment runs and send a test to an alert channel.
  2. Read the code to find the cause in the tables below.
  3. Read detail for the specifics, such as which setting to change or which field is wrong.
  4. For VALIDATION_ERROR, read each entry of errors.

Two things behave in a way you might not expect. A resource that exists but that you may not see answers 404 NOT_FOUND, not 403. And a status that has no specific code uses a generic one: METHOD_NOT_ALLOWED for 405, CONFLICT for 409 and ERROR for anything else.

Code Status Meaning What to do
BAD_REQUEST 400 The request could not be understood. On ingestion this is a body that could not be decompressed or was not fully sent. Check the Content-Encoding header and send the whole body.
INVALID_JSON 400 The ingestion body is not valid JSON. Fix the JSON.
INVALID_OTLP 400 The OTLP body could not be decoded. Send OTLP JSON or protobuf with the matching Content-Type.
VALIDATION_ERROR 422 A field is missing, has the wrong type or is out of range. For a refused cost CSV, each entry of errors has field file with the row and column at fault (up to 20), and nothing was stored. Read errors and fix each field, or each row of the file.
PAYLOAD_TOO_LARGE 413 The body is over its size limit, or an ingestion batch has too many spans. A dataset file or a billing source’s cost CSV is over 5 MB. Send smaller bodies and batches, or split the file. See the Limits reference.
UNSUPPORTED_MEDIA_TYPE 415 The Content-Type or Content-Encoding is not supported. A billing source’s cost CSV was not sent as multipart/form-data. Use application/json or application/x-protobuf, and gzip or deflate encoding. Send a CSV upload as multipart/form-data with the file in a field named file.
SEARCH_TOO_BROAD 422 A trace search (q) matched too much to finish within 5 seconds, usually a common word over a long time range. Narrow the time range or search for more specific text.
CONFIRMATION_MISMATCH 422 A deletion’s typed confirm is not the slug of what it deletes. Send the exact slug.
IDEMPOTENCY_MISMATCH 422 The Idempotency-Key was first used for a different request. Send a new key for a different request.
IDEMPOTENCY_IN_PROGRESS 409 The first request with this Idempotency-Key has not answered yet. Wait a moment and send the same request again.
Code Status Meaning What to do
UNAUTHORIZED 401 No valid session or credential came with the request. Sign in, or send Authorization: Bearer <key or token>.
INVALID_CREDENTIALS 401 The email or password is wrong. Check both. Repeated failures are throttled.
KEY_EXPIRED 401 The API key is past its expiry. Create a new key.
TOKEN_EXPIRED 401 The personal access token is past its expiry. Create a new token.
FORBIDDEN 403 You are signed in, but your role does not allow this. Ask an admin or owner. See the roles reference.
KEY_SCOPE 403 The API key does not have the scope this route needs. Create a key with the scope. A key cannot change its scopes.
TOKEN_SCOPE 403 A read-only access token tried to change something. Use a write token.
SESSION_REQUIRED 403 The route needs a signed-in browser session, not a token. Use the dashboard.
TWO_FACTOR_REQUIRED 403 The organization requires two-factor authentication, and you have not turned it on. Turn it on in your account settings.
CSRF_FAILED 403 The CSRF token is missing or wrong. Reload the dashboard. Scripts should use a bearer token, which skips this check.
ORIGIN_NOT_ALLOWED 403 The Origin header is missing or not on the allowed list. Call from the dashboard origin, or use a bearer token.
NOT_FOUND 404 The resource does not exist, or you cannot see it. Check the ID and your membership.
Code Status Meaning What to do
NOT_CONFIGURED 409 The request needs an optional integration that the operator has not set up. detail names the setting. For example, a billing source that fetches from a provider needs CREDENTIALS_KEYS, because its admin key is stored encrypted, and refreshing one needs BILLING_SOURCES_ENABLED on. Set it. See Configuration. A billing source uploaded as CSV holds no key and works without it.
LAST_OWNER 409 The change would leave the organization without an owner. Make someone else an owner first.
EMAIL_TAKEN 409 An account with this email already exists. Sign in instead.
SLUG_UNAVAILABLE 409 Spanlight could not find a free slug for the name. Try another name.
BILLING_SOURCE_NAME_TAKEN 409 Another billing source of the organization has this name. Choose another name.
BILLING_SOURCE_PROJECT_TAKEN 409 A project you mapped is already compared with another billing source of the same provider. detail names that source, and errors points at project_ids. Remove the project from the other source first, or leave it out.
BILLING_SOURCE_DISABLED 409 You asked to refresh a billing source that is turned off. Turn the source on, then refresh it. Its report stays readable while it is off.
BILLING_FETCH_IN_PROGRESS 409 A fetch or upload of the billing source is still running. Wait for it to finish, then try again. A fetch still running after 15 minutes no longer blocks a new one.
RATE_LIMITED 429 You sent too many requests. The Retry-After header says how long to wait. A billing source can be refreshed once every 5 minutes, with bursts of two. Wait that long, then retry. Send larger batches in fewer requests.
INTERNAL_ERROR 500 An unexpected error on the server. Retry a GET once. Retry a write only with the same Idempotency-Key, on a route that accepts one (see step 1 of How to read an error). If it persists, report it with the request_id.
SERVICE_UNAVAILABLE 503 The database could not take the request in time. Retry-After is 5 seconds. Retry a GET after the delay. Retry a write only with the same Idempotency-Key, on a route that accepts one (see step 1 of How to read an error). The Python SDK retries trace ingestion by itself.
Code Status Meaning What to do
QUEUE_LACKS_FAILURE_CATEGORY 422 The queue of a review policy does not ask for the failure_category score, or that config is not categorical. Add the categorical failure_category config to the queue, or pick a queue that has it. See Daily review.
NOTHING_SELECTED 422 Every reason of the review policy is off and its random share is 0, so it would never pick a trace. Turn on a reason or set a random share.
DAY_NOT_COMPLETE 422 A manual selection asked for today or a later day. Pick a finished UTC day.
DAY_TOO_OLD 422 A manual selection asked for a day more than 7 days back. Pick a day from yesterday back to 7 days.
ALREADY_SELECTED 409 The day has been selected already, by the nightly run or by hand. Nothing to do: its items are in the queue.
QUEUE_IN_USE 409 A review policy fills the queue you tried to delete. Delete the policy, or point it at another queue, first.
REPLAY_BUDGET_EXCEEDED 409 The project’s monthly replay budget cannot cover the replay, so nothing was sent. The body adds spent_usd, reserved_usd, budget_usd and month_start. Wait for the next UTC month, or ask an operator to raise REPLAY_MONTHLY_BUDGET_USD.

A replay that cannot be sent as it was stored answers 422 with one of eight codes: REPLAY_NOT_LLM, REPLAY_NO_PAYLOAD, REPLAY_PAYLOAD_TRUNCATED, REPLAY_PAYLOAD_REDACTED, REPLAY_UNSUPPORTED_INPUT, REPLAY_PROVIDER_MISMATCH, REPLAY_MODEL_UNPRICED and REPLAY_BODY_TOO_LARGE. When a replay is refused says what each means and what to do. A replay with REPLAY_MONTHLY_BUDGET_USD set to 0 answers 409 NOT_CONFIGURED.