Errors
Every error the Evinor API returns is an RFC 9457
"problem detail" document. Branch on the stable type URI or the HTTP status
code — never on the human-readable title or detail, which may change.
Anatomy
Error responses use the application/problem+json content type and this shape:
{
"type": "https://docs.evinor.ai/problems/validation-failed",
"title": "Validation failed",
"status": 400,
"retryable": false,
"detail": "The request payload failed validation.",
"request_id": "8f0b2c1e-4a7d-4b93-9c2e-1d5a6f0b3c8e",
"errors": [{ "field": "filter.min_reports", "message": "Expected integer" }]
}
| Member | Always present | Meaning |
|---|---|---|
type | yes | Stable, versioned problem-type URI under https://docs.evinor.ai/problems/. Branch on this. |
title | yes | Short, constant, human-readable summary for the type. Not request-specific. |
status | yes | The HTTP status code, repeated in the body. |
retryable | yes | Whether retrying this request can succeed. See retryable. |
detail | usually | Human-readable explanation of this specific occurrence. |
request_id | yes | Correlation id for this request. Include it when contacting support. |
Some problem types add extension members:
validation-failedincludes anerrorsarray of{ field, message }objects, one per failed field. When a well-formed request is refused because of its content, it carries a stablecodestring instead. Today that isPOST /v1/reports; see Reporting events for the codes.not-entitledincludes acodenaming which entitlement check failed (FORBIDDEN,GRANT_FORBIDDEN, orGRANT_NOT_USABLE).missing-scopeincludes amissing_scopesarray naming the scope(s) the key lacks.insufficient-creditsincludesas_of, the timestamp of the balance snapshot the request was refused against.search-execution-expiredincludesexecution_idandexpired_at, naming the search execution the cursor pointed at.
retryable
Every error body carries a boolean retryable — the server's verdict on whether
retrying this request can ever succeed. It is a verdict, not a policy: it
does not tell you how long to wait, whether to reuse your Idempotency-Key, or
how many times to try. Two types carry qualifiers you must honor:
idempotency-in-flightis retryable only under the right key. On billed search (POST /v1/events/search), retry with a newIdempotency-Key— a billed execution claims its key non-reclaimably, so the same key cannot succeed for 24 hours. OnPOST /v1/reports, retry with the same key, so a retry can never file a second report.upstream-errorwarrants at most one retry. On a billed call made without anIdempotency-Key, do not retry automatically: the facade cannot prove whether the original was charged, and a repeat may charge again.
If retryable is absent (a response from an older server replica during a
deployment), use the Retryable column in the
catalog below, not the status code. A status-based rule
would call the 409 idempotency-in-flight permanent.
request_id
Every response — success or error — carries an x-request-id header, and every
error body repeats it as request_id. Log it. When you report a problem to
Evinor support, quoting the request_id lets us find the exact request in our
logs.
You may also send your own x-request-id on a request (up to 64 characters of
A-Za-z0-9_-) and Evinor will echo it back, so you can correlate a call with
your own tracing. Any value outside that charset/length is ignored and a fresh
id is generated instead.
Problem-type catalog
Each type URI resolves to a page on this site describing the problem and how
to handle it:
| Status | Problem type | Retryable | When it happens |
|---|---|---|---|
| 400 | validation-failed | no | The request payload, query, or path failed validation. |
| 400 | malformed-body | no | The request body is not valid JSON. |
| 400 | query-too-complex | no | The upstream service refused the operation as too complex. |
| 401 | invalid-api-key | no | The API key is missing, malformed, unknown, or revoked. |
| 402 | insufficient-credits | no | A billed endpoint was called with an exhausted balance. |
| 403 | missing-scope | no | The key lacks a scope the endpoint requires. |
| 403 | not-entitled | no | The key owner is not entitled to the operation. |
| 403 | quota-exceeded | no | A plan quota for the resource is reached. |
| 404 | not-found | no | The referenced resource doesn't exist or isn't yours. |
| 409 | idempotency-in-flight | yes, with qualifiers | Another request with the same Idempotency-Key is running. |
| 409 | report-already-submitted | no | A report with this submission id already exists. |
| 409 | state-conflict | no | The request conflicts with the resource's current state. |
| 410 | search-execution-expired | no | A pagination cursor's search execution has expired. |
| 413 | payload-too-large | no | The request body exceeds the 1 MB JSON body limit. |
| 422 | idempotency-key-conflict | no | An Idempotency-Key was reused for a different request. |
| 422 | entity-list-inaccessible | no | A referenced entity list doesn't exist or isn't usable. |
| 429 | rate-limited | yes | The per-key rate limit was exceeded. |
| 502 | upstream-error | yes, with qualifiers | An upstream dependency returned an unexpected error. |
| 503 | write-unavailable | yes | A write could not be accepted right now; safe to retry. |
| 500 | internal-error | yes | An unexpected server error. Not your fault; retry or report. |
Handling errors
Prefer the body's retryable member (and the qualifiers
above) over these status-code rules of thumb:
- 4xx generally means something about the request must change — fix the input, the key, or the scope before retrying.
- 429 and 503 are safe to retry after a backoff; both include a
Retry-Afterheader telling you how long to wait. - 502 and 500 indicate a server-side issue; retry with backoff and, if
it persists, contact support with the
request_id.