# Error responses you can act on

> An error code is a support conversation you do not have to have. Here is the shape we use.

Canonical: https://corpoapi.com/blog/error-responses-that-help

2026-02-20

## Key takeaways

- Errors use RFC 9457 problem+json, not a bespoke envelope.

- Every error carries a stable code your code can branch on.

- Rate-limit headers are on every response, not only on a 429.

Most API errors tell you that something failed. Good errors say what to do next. We treat every error response as part of the interface.

## The shape

Errors are `application/problem+json`, following RFC 9457:

```json
{
  "type": "https://corpoapi.com/errors/free_tier_exhausted",
  "title": "Free tier exhausted",
  "status": 402,
  "code": "free_tier_exhausted",
  "detail": "The 100 free starter calls are used up. Billing is not open yet.",
  "retryable": false
}
```

The `code` is stable. Branch on it, not on the human-readable `detail`, which can change wording.

## Codes worth branching on

- `invalid_identifier`: the number you passed failed the registry's check digit. Fix the input, do not retry.
- `company_not_found`: no company matched. Free, and not retryable without a different input.
- `free_tier_exhausted`: the starter allowance is used up and billing is not open yet. Retrying will not help.
- `rate_limited`: back off using `Retry-After`.
- `registry_unavailable` / `registry_budget_exhausted`: the upstream register is down or capped. A cached copy is served when we have one.

## Retries

Retries are the norm in production, so two things keep them safe. The same company is billed at most once per UTC day, so a retry that re-reads a company does not bill it twice. And rate limits are reported on every response through `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`, so a well-behaved client can pace itself instead of discovering the limit by hitting it.
