Errors
Every error response from the Regulatory Snapshot API is a structured problem-details envelope, per RFC 9457Â . That means: a stable type URI you can branch on, a machine-readable code, and a human-readable detail, plus a request_id you can quote when something goes wrong.
Every response — error or success — carries an X-Request-Id header. Always include it when filing a bug.
This page walks through the shape, the status-code map, the common codes you’ll see in the wild, and the recovery pattern for each.
The shape
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json; charset=utf-8
X-Request-Id: req_lxr5sm9k2pq8aaa…{
"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_lxr5sm9k2pq8aaa…",
"errors": [
{ "path": "horizon", "code": "invalid", "message": "horizon must be one of 3, 6, 12, 18, 24, 36" }
]
}| Field | Type | Always present? | Notes |
|---|---|---|---|
type | URI string | yes | Stable identifier for the class of problem. Branch on this if you want, but code is shorter. |
title | string | yes | Short, human-readable summary. |
status | integer | yes | The HTTP status. Mirrors the response line — useful when your HTTP client buries the status. |
detail | string | yes | Specific, actionable message. |
instance | string | yes | The request path. |
code | string | yes | Machine-readable error code. This is what you switch on. |
request_id | string | yes | Same value as the X-Request-Id header. Include in bug reports. |
errors | array | sometimes | Per-field validation errors on 422s. |
[!TIP] Prefer
codeoverstatuswhen branching.404 not_foundand404 translation_not_availableare both404, but only one of them suggests the caller try a different language.
Status code map
| Status | Type slug | Default code | When it happens |
|---|---|---|---|
| 400 | invalid_request | invalid_request | Malformed request (JSON parse error, etc.) |
| 401 | auth | authentication_required / authentication_invalid / key_revoked | See Authentication. |
| 402 | budget_exhausted | budget_exhausted | Account budget pool empty. Top up. |
| 403 | forbidden | permission_denied | Reserved. |
| 404 | not_found | not_found / translation_not_available | Resource doesn’t exist or you don’t own it. |
| 409 | conflict | idempotency_key_in_use / idempotency_in_progress | See Idempotency. |
| 422 | validation | validation_error / invalid_idempotency_key / key_limit_exceeded / haiku_blocked_on_verifier | Field-level problems; see errors[]. |
| 429 | rate_limited | rate_limit_exceeded | See Rate limits. |
| 500 | internal | internal_error | Server bug. We get a Sentry alert; you get the request id. |
| 503 | transient | transient_error | Dependency down, retry in a moment. |
type is always https://api.regsn.app/problems/<slug>. The slug is stable; you can branch on type === 'https://api.regsn.app/problems/budget_exhausted' in code if you prefer URIs over codes.
Common errors and how to recover
401 authentication_required
You didn’t send an Authorization header. Add Authorization: Bearer regsn_live_….
401 authentication_invalid
The header was malformed, or the key isn’t recognised. Check for typos, trimmed whitespace, or accidentally including the prefix Bearer twice. If the key looks right, it may have been revoked from the dashboard.
401 key_revoked
The key matched a row but revoked_at is set. Create a new key in the dashboard .
402 budget_exhausted
Your account’s budget pool is empty. Top up in the dashboard; the limit lifts immediately on the next request. Reads (GETs) continue to work; only billable operations (POST /v1/scans, POST /v1/exports) fail.
[!WARNING] Don’t auto-retry
402s in a tight loop — you’ll hit the rate limiter and still not get charged. Surface the failure, alert your operations team, then retry once the budget is topped up.
404 not_found
The resource (scan_id, snapshot_id, export_job_id, key id) doesn’t exist, or it belongs to a different account. The API does not distinguish — same response either way — to avoid leaking the existence of other accounts’ resources.
404 translation_not_available
You asked for /executive-narrative?language=fr on a snapshot that doesn’t have a French translation. Either fall back to en, or re-run the scan with translation enabled.
409 idempotency_key_in_use
You reused an Idempotency-Key with a different request body. Generate a new key. See Idempotency.
409 idempotency_in_progress
You retried with an Idempotency-Key whose first request is still running. Wait a few seconds and retry. See Idempotency.
422 validation_error
One or more fields in your request body failed validation. Walk the errors[] array — each entry has a path (e.g. horizon), a code (e.g. invalid), and a message.
{
"code": "validation_error",
"detail": "horizon must be one of 3, 6, 12, 18, 24, 36",
"errors": [
{ "path": "horizon", "code": "invalid", "message": "horizon must be one of 3, 6, 12, 18, 24, 36" },
{ "path": "areas", "code": "required", "message": "areas is required and must be a non-empty array" }
]
}422 key_limit_exceeded
You already have 10 active API keys. Revoke one before creating another.
422 haiku_blocked_on_verifier
model: "haiku" isn’t supported on the verifier-aware engines (v4 with realist/auditor enabled, v4.5-alpha, v4.5-beta). Pick sonnet or opus.
429 rate_limit_exceeded
Hit a per-key rate-limit bucket. Wait Retry-After seconds and retry. See Rate limits.
500 internal_error
A server bug. The team gets a Sentry notification keyed to your request_id. Quote the request_id when you file a support ticket — it lets us walk the trace end-to-end.
503 transient_error
A dependency is briefly unavailable (database, engine, etc.). Retry with exponential backoff. Both SDKs do this automatically (3 attempts).
Branching in code
Python
from regsn import RegSn, RegSnError
client = RegSn()
try:
client.scans.create(jurisdictions=["UK"], areas=["AML"], horizon=12)
except RegSnError as e:
if e.code == "budget_exhausted":
notify_finance(e.request_id)
elif e.code == "rate_limit_exceeded":
# SDK already retried 3 times; defer to the queue.
enqueue_for_later()
else:
raiseJavaScript
import { RegSn, RegSnError } from '@regsn/api';
const client = new RegSn();
try {
await client.scans.create({ jurisdictions: ['UK'], areas: ['AML'], horizon: 12 });
} catch (err) {
if (err instanceof RegSnError && err.code === 'budget_exhausted') {
notifyFinance(err.requestId);
} else {
throw err;
}
}A note on 5xxs
A 5xx response always writes a Sentry event on our side, with the request id, the endpoint, the API key id, and the user id attached. By the time you’ve decided to file a bug, we’ve usually already seen it — but quoting your request_id is what lets us correlate your report to our trace.
See also
- Authentication — 401 codes in detail.
- Idempotency — 409 codes in detail.
- Rate limits — 429 handling.
- Scans — 422 validation rules on the scan body.
- Glossary — problem detail, request ID.