Errors
Error envelope, common codes, and how to handle them.
All Cerrtify errors return a JSON envelope:
{ "error": "error_code", "detail": "human-readable message" }The error field is a stable machine-readable code; detail is a human-readable message that may change.
Common codes
| Code | HTTP | Meaning |
|---|---|---|
bad_json | 400 | Request body is not valid JSON |
invalid_request | 400 | Required field missing or malformed |
invalid_ip | 400 | endUserIp is not a valid IPv4/IPv6 address |
missing_orderRef | 400 | orderRef field required but missing |
invalid_orgnr | 400 | orgnr is not a valid 10-digit organisationsnummer |
query_too_short | 400 | Company search q is under 2 characters |
query_too_long | 400 | Company search q is over 100 characters |
missing_job_id | 400 | No job id in the path — call /service/jobs/{id} |
read_body | 400 | The request body could not be read to the end (dropped connection, wrong Content-Length) |
no_active_qr_session | 404 | The order has no active QR session (already complete or canceled) |
missing_api_key | 401 | X-API-Key header missing |
invalid_api_key | 401 | Key does not exist or wrong format |
unauthorized | 401 | Generic auth failure |
insufficient_credits | 402 | Your organisation is out of credits (production only) — top up in the dashboard. Note: this error uses a message field instead of detail |
api_key_revoked | 403 | Key was revoked in the dashboard |
api_key_expired | 403 | Key passed its expires_at |
organisation_inactive | 403 | Your organisation is no longer active |
insufficient_scope | 403 | The key lacks the scope this endpoint requires (bankid, persondata, or company) |
spar_not_configured | 403 | Production: no SPAR certificate is configured for your organisation — upload one in the dashboard under Certifikat → SPAR |
ip_not_allowed | 403 | Request IP is not in your organisation's IP allowlist |
origin_not_allowed | 403 | Browser Origin not in your domain allowlist |
invalid_origin | 403 | Browser Origin header is malformed |
job_not_found | 404 | Async job ID does not exist or belongs to another organisation |
company_not_found | 404 | Sandbox only: the org.nr is not one of the sandbox test companies |
rate_limited | 429 | Per-key rate limit exceeded — retry after the Retry-After header |
search_failed | 500 | The company register search failed server-side — retry, then report it if it persists |
internal | 500 | Unexpected server error while reading an async job |
upstream_unavailable | 502 | The upstream provider is down or failing right now — see Upstream outages |
bankid_response_parse | 502 | BankID answered, but with a body we could not parse |
upstream_parse_error | 502 | SPAR answered, but with a body we could not parse |
enqueue_failed | 503 | Async job queue is unavailable |
audit_unavailable | 503 | A regulated call (persondata, company) could not be audit-logged, so it was refused |
service_unavailable | 503 | A required upstream (SPAR, BankID, Bolagsverket) is misconfigured server-side |
Upstream outages
When an upstream provider is unreachable or returning server errors, the API responds 502 with a standardized envelope naming the affected service:
{
"error": "upstream_unavailable",
"service": "bankid",
"message": "There is currently a problem with BankID. Please try again shortly."
}service is one of bankid, persondata, or company (more may be added as services launch). The problem is on the provider's side — retry with backoff or surface the message to your user.
This only covers genuine outages. Provider-level 4xx protocol errors (e.g. an invalid BankID orderRef) still pass through unchanged with the provider's own response body, so you keep full error fidelity.
Two other 502s are not part of that envelope: bankid_response_parse and upstream_parse_error. There the provider did answer, but with something we could not parse, so there is no service or message field — only the standard error (and sometimes detail). Treat them like upstream_unavailable: retry with backoff.