# Errors and freshness

The API uses HTTP status codes and JSON response bodies. Inspect both the status and body before deciding whether to retry.

## Standard error body

Validation, authentication, authorization, not-found, and dataset-availability failures use this base shape:

```ts
type ErrorResponse = {
  error: string;
};
```

Example:

```json
{
  "error": "Company not found"
}
```

## Status codes

| Status | Meaning | Client action |
| --- | --- | --- |
| `400` | A query parameter, JSON body, identifier, cursor, or SQL statement is invalid. | Correct the request before retrying. |
| `401` | The bearer token is missing, malformed, unknown, or revoked. | Supply or replace the token. |
| `403` | A paid plan is required, the account is suspended, an operation is unavailable to the owner, or a usage limit is exhausted. | Inspect `code` and usage fields; upgrade when `code` is `paid_plan_required`; otherwise do not immediately retry. |
| `404` | The requested entity was not found in the active dataset. | Check the identifier or search for the entity. |
| `422` | Natural-language translation needs clarification or requests unsupported behavior. | Inspect the structured translation body and change the question. |
| `429` | Requests are arriving too quickly. | Respect `retry-after` or `resetAt` when present, then retry with backoff. |
| `500` | An unexpected server error prevented the request from completing. | Retry with backoff; report a persistent failure with the `x-request-id`. |
| `503` | No active dataset is loaded, or a required usage-decision service is unavailable. | Retry with backoff and report a persistent failure. |

## Usage and quota errors

A plan/capability block, exhausted quota, or rate limit adds a stable code and usage window fields:

```ts
type UsageErrorResponse = {
  error: string;
  code: "paid_plan_required" | "quota_exceeded" | "plan_limit_exceeded" | "rate_limit_exceeded" | string;
  planKey?: string;
  quotaBucket?: string | null;
  limit?: number | null;
  used?: number;
  remaining?: number | null;
  windowStart?: string | null;
  windowEnd?: string | null;
  resetAt?: string | null;
};
```

When available, the response also includes `X-Usage-Bucket`, `X-Usage-Limit`, `X-Usage-Remaining`, and `X-Usage-Reset` headers. Treat the response values as authoritative. Datapublica does not publish plan prices or fixed quota amounts in the API docs.

A Free-plan API request returns `403` with `code: "paid_plan_required"`; the public API requires a paid plan. Quota exhaustion can return `403` with `code: "quota_exceeded"`. A short-term request-rate decision can return `429` with `code: "rate_limit_exceeded"`. Both use the same usage field names.

## Natural-language translation `422`

`POST /api/sql/translate` returns a structured body when no safe SQL should be produced:

```json
{
  "status": "unsupported",
  "sql": null,
  "description": "Deleting inactive companies is a mutating and unsafe operation, which is not supported.",
  "notes": ["Only read-only SQL queries can be generated."],
  "assumptions": [],
  "warnings": ["No data was deleted."],
  "metadata": {
    "provider": "openai",
    "model": "model-name",
    "buildId": "build_...",
    "schemaVersion": "sha256:...",
    "referencedTables": []
  },
  "error": {
    "code": "unsafe_request"
  }
}
```

The other translation outcome is `status: "needs_clarification"` with `error.code: "needs_clarification"`. Treat both as handled outcomes: revise the question instead of executing SQL.

## Dataset unavailable `503`

Entity, search, timeline, portfolio, SQL, and translation endpoints require an active CVR dataset:

```json
{
  "error": "No active dataset loaded"
}
```

Retry with exponential backoff. A persistent `503` should be reported through [support](https://datapublica.dk/support) with the `x-request-id` header.

## Freshness and `build_id`

Entity detail and ownership portfolio responses include a `build_id`, for example `build_...`. Translation metadata exposes the same concept as `metadata.buildId`.

Use the build identifier as the freshness cue:

- Store it with derived results when reproducibility matters.
- Compare it across related responses to determine whether they came from the same published dataset build.
- Refresh cached derived data after observing a new build identifier.

Timeline and search responses do not always include a build identifier in their body. Fetch the related detail or portfolio resource when an explicit freshness cue is required.

## Request IDs

Responses include an `x-request-id` header. Record it with the URL, method, status, and timestamp when diagnosing a failure. Do not record the bearer token.
