Errors
Every error response from the API is an RFC 9457 problem-details document with Content-Type: application/problem+json, and every response — success or error — carries an X-Request-Id header. The one exception is malformed transport: a request rejected before it reaches the API layer (unparseable JSON, a body over the 2 MB cap) gets a plain 400/413 with neither a problem body nor a request id.
The shape
{
"type": "https://api.regsn.app/problems/validation",
"title": "Validation error",
"status": 422,
"detail": "horizon must be one of 3, 6, 12, 18, 24, 36",
"instance": "/v1/scans",
"code": "validation_error",
"request_id": "req_m3k9f2ab8xk2p91q",
"errors": [
{ "path": "horizon", "code": "invalid", "message": "horizon must be one of 3, 6, 12, 18, 24, 36" }
]
}| Field | Meaning |
|---|---|
type | Stable problem URI: https://api.regsn.app/problems/<slug>. |
title | Human-readable category for the status. |
status | The HTTP status, repeated in the body. |
detail | What went wrong, specifically. Unexpected 5xx never leak internals — their detail is the generic "Internal server error"; deliberate 5xx (for example a 503 when the database is unavailable) keep a specific message. |
instance | The request path that produced the error. |
code | Machine-readable error code — branch on this, not on detail strings. |
request_id | Matches the X-Request-Id header. |
errors | Per-field problems on validation failures; empty otherwise. Each entry: { path, code, message }. |
Quote the request id. request_id (req_…) correlates your request with server-side traces end-to-end. Include it in any support message — it is the fastest route to an answer.
Error codes
The top-level code, by status:
| Status | code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request | Malformed request. Note unparseable JSON is rejected before the API layer, with a plain body rather than a problem document. | Fix the request. |
| 401 | authentication_required | No bearer token. | Send Authorization: Bearer regsn_live_…. |
| 401 | authentication_invalid | Token malformed or unknown. | Check the key value; create one if needed. |
| 401 | key_revoked | Key exists but was revoked. | Switch to an active key. |
| 402 | budget_exhausted | Account budget pool depleted. | Check GET /v1/usage/budget; contact your workspace administrator to raise the cap. |
| 403 | permission_denied | Authenticated but not allowed. | Verify the resource belongs to your account. |
| 404 | not_found | Resource missing, or owned by another account. | Check the id. |
| 404 | translation_not_available | No translation for the requested language on /executive-narrative. | Request en, or re-scan with translationLanguages. |
| 409 | conflict | Generic state conflict. | Re-read the resource. |
| 409 | invalid_state | Scan already terminal on /cancel. | Nothing — it is already finished. |
| 409 | not_ready | Export not completed on /download. | Keep polling, then download. |
| 409 | idempotency_key_in_use | Same Idempotency-Key, different body. | Use a fresh key for a genuinely new request. See Idempotency. |
| 409 | idempotency_in_progress | Original request with this key still running. | Wait, then retry with the same key. |
| 410 | gone | Export completed but artefact bytes no longer retained. | Re-run the export. |
| 416 | range_not_satisfiable | Range header outside the resource. | Fix or drop the Range. |
| 422 | validation_error | One or more fields invalid — see errors[]. | Fix each listed field. |
| 422 | unknown_parameter | A removed or unrecognised query parameter — currently starred on GET /v1/snapshots (snapshot starring is retired). | Drop the parameter. |
| 422 | key_limit_exceeded | Already at 10 active API keys. | Revoke one first. |
| 422 | invalid_idempotency_key | Idempotency-Key not 1–255 printable ASCII characters. | Fix the header value. |
| 429 | rate_limit_exceeded | Bucket exhausted. | Honour Retry-After. See Rate limits. |
| 500 | internal_error | Unexpected server error. | Retry with backoff; report the request_id if it persists. |
| 503 | transient_error | Temporarily unavailable (e.g. database). | Retry with backoff. Safe with the same Idempotency-Key. |
| 503 | engine_unavailable | Requested scan engine not loaded. | Use an engine from GET /v1/meta/engines. |
Field-level codes
Inside errors[] on a 422, path names the offending field (e.g. horizon, options.styleSlug) and code is one of:
errors[].code | Meaning |
|---|---|
required / missing | Required field absent. |
invalid | Value outside the allowed shape or vocabulary. |
invalid_range | from after to on /v1/usage. |
incompatible | Field valid only in combination with another setting (e.g. fetchProvider without engine: "v4.5-beta"). |
engine_deprecated | A retired engine id (v1, v2, v3, admiral). |
haiku_blocked_on_verifier | model: "haiku" on a verifier-aware config. |
unknown | Unknown value (e.g. an unregistered provider:artifact_type pair or export status filter). |
unknown_option | Export option name not in the type’s schema — the message includes a did-you-mean hint and the valid list. |
invalid_value | Export option value outside the enum — message lists valid values. |
invalid_type | Export option value not a string. |
too_long | String over its maxLength. |
Retry guidance
- Retry on
429(afterRetry-After),500, and503, with exponential backoff. - Do not retry
4xxother than429without changing the request — the outcome will not change. - Always pair POST retries with an
Idempotency-Keyso a retry can never duplicate a scan or export. Failed (non-2xx) attempts are not cached, so the same key retries cleanly.
Both SDKs implement exactly this policy and surface errors as a typed exception carrying status, code, request_id, and the full body.