# API reference

The official Datapublica Business Register API is JSON over HTTPS at `https://query.datapublica.dk`. Every endpoint below requires `Authorization: Bearer <personal-api-token>` and a paid Datapublica plan.

The endpoint list is exhaustive for public API v1. Anonymous endpoints used by the `datapublica.dk` website are not part of the official API.

## Shared conventions

- Query parameters use their exact camel-case or lowercase spelling shown below.
- Search pagination uses `limit` and `offset`.
- Timeline pagination uses `limit` and an opaque `cursor`; pass `next_cursor` unchanged.
- Entity identifiers must come from search results, except that company routes also accept a CVR number.
- JSON record objects can contain extra dataset fields. Use the documented top-level fields and explicitly named nested fields; do not infer a contract from an extra field.
- Entity detail and portfolio responses expose `build_id` as the dataset freshness cue.
- You can send a valid caller-generated `x-request-id` header; the server reflects it or generates one. Every response includes `x-request-id`. Usage-controlled responses can also include `X-Usage-*` headers.

Common errors are described in [Errors](/docs/errors). Endpoint-specific lists below identify expected client and availability failures; every endpoint can also return `500` for an unexpected server error.

## Companies

### `GET /api/companies/search`

**Authentication:** Required personal bearer token.

Searches companies and returns pagination metadata plus company row objects.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `q` | query | yes | Non-empty string after trimming. |
| `limit` | query | no | Integer, 1–100; default 20. |
| `offset` | query | no | Integer at least 0; default 0. |

Request:

```bash
curl --get 'https://query.datapublica.dk/api/companies/search' \
  --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  --data-urlencode 'q=chr' \
  --data-urlencode 'limit=1' \
  --data-urlencode 'offset=0'
```

Response `200`:

```json
{
  "query": "chr",
  "total": 40551,
  "limit": 1,
  "offset": 0,
  "rows": [
    {
      "cvr_number": "14748105",
      "company_name": "CHRISTOFFERSEN & KNUDSEN A/S",
      "registered_office": "RØDOVRE",
      "city": "Rødovre",
      "is_active": true
    }
  ]
}
```

Common errors: `400` invalid/missing query or pagination, `401` token failure, `403` suspended owner, `503` no active dataset.

### `GET /api/companies/{identifier}`

**Authentication:** Required personal bearer token. User-level company lookup limits apply.

Returns a company bundle. Use an eight-digit CVR number or an identifier resolved by company search.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `identifier` | path | yes | URL-encoded CVR number or recognized company identifier. |
| `financial_scope` | query | no | `entity` or `consolidated_group`; filters only the additive scoped financial arrays. |

Request:

```bash
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  'https://query.datapublica.dk/api/companies/14748105'
```

Response `200` shape and example:

```json
{
  "cvr": "14748105",
  "build_id": "build_...",
  "profile": {
    "cvr_number": "14748105",
    "company_name": "CHRISTOFFERSEN & KNUDSEN A/S",
    "city": "Rødovre",
    "is_active": true
  },
  "latestStatus": {},
  "financialsAnnual": [],
  "financialsAnnualScoped": [
    {
      "fiscal_year": 2024,
      "statement_scope": "consolidated_group",
      "reporting_currency": "EUR"
    }
  ],
  "annualReportPeriods": [
    {
      "filing_id": "filing_...",
      "reporting_period_start": "2024-01-01",
      "reporting_period_end": "2024-12-31",
      "financial_data_status": "pdf_only",
      "pdf_source_url": "https://regnskaber.cvrapi.dk/...pdf"
    }
  ],
  "financialScopes": {
    "available": ["consolidated_group"],
    "default": "consolidated_group",
    "hasUnknown": false
  },
  "growth": [],
  "growthScoped": [],
  "ownership": [],
  "companyRelations": {
    "legalOwners": [],
    "previousLegalOwners": [],
    "beneficialOwners": [],
    "previousBeneficialOwners": [],
    "childCompanies": [],
    "previousChildCompanies": [],
    "relatedCompanies": []
  },
  "contacts": [],
  "people": [],
  "auditors": [],
  "coverage": {},
  "financialDistressScore": null
}
```

`financialsAnnual` and `growth` remain entity-only compatibility series and never fall back to group rows. `annualReportPeriods` is a display-oriented filing index; `financial_data_status: "pdf_only"` identifies an annual report whose official PDF is available at `pdf_source_url` but whose numeric facts are unavailable. Do not use those rows as numeric observations. Scoped rows carry `statement_scope` and `reporting_currency`; values are reported amounts and are not FX-converted. `profile`, `latestStatus`, and `coverage` can be `null`. Array items are dataset records. Common errors: `400` missing identifier or invalid scope, `401` token failure, `403` suspended owner or usage block, `404` company not found, `503` no active dataset or usage-decision service unavailable.

### `GET /api/companies/{identifier}/financial-report-sections`

**Authentication:** Required personal bearer token. User-level company lookup limits apply under the `company_lookup` bucket and `companies.detail` endpoint key.

Returns curated XBRL annual-report text blocks for one filing. With no selectors, the endpoint uses the latest annual filing for the company bundle's `financialScopes.default` scope.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `identifier` | path | yes | URL-encoded CVR number or recognized company identifier. |
| `filing_id` | query | no | Non-empty string, at most 256 characters; defaults to the latest filing for the resolved scope. |
| `financial_scope` | query | no | `entity` or `consolidated_group`; defaults to the company bundle's `financialScopes.default`. |
| `reporting_period_start` | query | no | ISO date used to disambiguate filings that share a `filing_id`. |
| `reporting_period_end` | query | no | ISO date used to disambiguate filings that share a `filing_id`. |
| `x-company-lookup-request-id` | header | no | Request id from a successful company lookup for the same CVR. Compatible reuse avoids a second company lookup charge. |

Request using the defaults:

```bash
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  'https://query.datapublica.dk/api/companies/27626661/financial-report-sections'
```

Response `200` shape:

```json
{
  "cvr": "27626661",
  "filingId": "filing_5edd0c9f0f29570143db",
  "reportExternalId": "...",
  "reportingPeriodStart": "2023-01-01T00:00:00.000Z",
  "reportingPeriodEnd": "2023-12-31T00:00:00.000Z",
  "fiscalYear": 2023,
  "financialScope": "entity",
  "sourceReportUrl": null,
  "sections": [
    {
      "sectionId": "...",
      "canonicalSectionKey": "primary_activities",
      "sectionGroup": "business_and_development",
      "displayOrder": 10,
      "financialScope": "report_wide",
      "contentPlain": "...",
      "blocks": [{ "kind": "paragraph", "content": "..." }],
      "language": "da",
      "isTruncated": false
    }
  ],
  "availableReports": [
    {
      "filingId": "filing_5edd0c9f0f29570143db",
      "reportExternalId": "...",
      "reportingPeriodStart": "2023-01-01T00:00:00.000Z",
      "reportingPeriodEnd": "2023-12-31T00:00:00.000Z",
      "fiscalYear": 2023,
      "sourceReportUrl": null,
      "hasSections": true,
      "locked": false
    }
  ]
}
```

These are thirteen curated canonical keys in the `business_and_development`, `risks_and_material_matters`, and `commitments_and_security` groups, not the complete annual report. `contentPlain` is capped at 20,000 characters per section; `isTruncated: true` marks an excerpt, and there is no pagination. Boilerplate is suppressed and content duplicated across scopes is deduplicated, so a missing section is not evidence that the filer wrote nothing. `language` is per section and usually `da`. A section's `financialScope` can be `report_wide` even for an `entity` or `consolidated_group` request. `sourceReportUrl` can be `null`. `availableReports[].locked` is a UI compatibility field and is always `false` for token-authenticated calls. A PDF-only filing returns `sections: []` and remains represented in `availableReports`. If a company has no scoped numeric annual filing from which to derive `financialScopes.default`, supply `financial_scope` explicitly; omitting it returns `400` rather than guessing a scope.

Successful responses include the `X-Usage-*` headers. Common errors: `400` invalid parameters or no scoped annual filing, `401` token failure, `403` suspended owner or usage block, `404` filing not aligned to the company and scope, `503` no active dataset or usage-decision service unavailable.

### `GET /api/companies/{identifier}/timeline`

**Authentication:** Required personal bearer token.

Returns company events ordered for cursor pagination.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `identifier` | path | yes | URL-encoded CVR number or recognized company identifier. |
| `limit` | query | no | Integer, 1–100; default 50. |
| `cursor` | query | no | Opaque `next_cursor` value from the previous response. |

Request:

```bash
curl --get \
  --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  --data-urlencode 'limit=1' \
  'https://query.datapublica.dk/api/companies/14748105/timeline'
```

Response `200`:

```json
{
  "events": [
    {
      "event_id": "registration_notice_timeline_...",
      "cvr_number": "14748105",
      "category": "registry",
      "field_key": "capital",
      "occurred_at": "2026-03-25T10:30:00.280Z",
      "source": "registration_text_payload",
      "metadata": {}
    }
  ],
  "next_cursor": "1"
}
```

`truncated_by_plan: true` can be present when the owner's plan restricts timeline depth. Common errors: `400` invalid identifier/pagination, `401` token failure, `403` suspended owner, `404` company not found, `503` no active dataset.

### `GET /api/companies/{identifier}/portfolio`

**Authentication:** Required personal bearer token.

Returns the initial company ownership graph.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `identifier` | path | yes | URL-encoded CVR number or recognized company identifier. |

Request:

```bash
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  'https://query.datapublica.dk/api/companies/14748105/portfolio'
```

Response `200`:

```json
{
  "root": "company:14748105",
  "build_id": "build_...",
  "truncated": false,
  "nodes": [
    {
      "nodeId": "company:14748105",
      "entityType": "company",
      "displayName": "CHRISTOFFERSEN & KNUDSEN A/S",
      "companyCvrNumber": "14748105",
      "personId": null,
      "participantId": null,
      "isRoot": true,
      "expandUpCount": 1,
      "expandDownCount": 0
    }
  ],
  "edges": [
    {
      "edgeId": "company:37933562->company:14748105:legal_owner",
      "sourceNodeId": "company:37933562",
      "targetNodeId": "company:14748105",
      "ownershipLabel": "66.67-89.99%",
      "relationshipKind": "legal_owner"
    }
  ]
}
```

Node objects also include slug/display labels and expansion state; edges include ownership precision and lower/upper percentages. Common errors: `400` missing identifier, `401` token failure, `403` suspended owner, `404` company not found, `503` no active dataset.

## People

### `GET /api/people/search`

**Authentication:** Required personal bearer token.

Searches person identities and returns pagination metadata plus person row objects.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `q` | query | yes | Non-empty string after trimming. |
| `limit` | query | no | Integer, 1–100; default 20. |
| `offset` | query | no | Integer at least 0; default 0. |

Request:

```bash
curl --get 'https://query.datapublica.dk/api/people/search' \
  --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  --data-urlencode 'q=ole' \
  --data-urlencode 'limit=1'
```

Response `200`:

```json
{
  "query": "ole",
  "total": 26727,
  "limit": 1,
  "offset": 0,
  "rows": [
    {
      "person_id": "pid_entity_4000350926",
      "display_name": "Ole Bjørvig Pedersen",
      "slug": "ole-bj-rvig-pedersen",
      "identity_confidence": "high",
      "company_count": 173
    }
  ]
}
```

Common errors: `400` invalid/missing query or pagination, `401` token failure, `403` suspended owner, `503` no active dataset.

### `GET /api/people/{identifier}`

**Authentication:** Required personal bearer token.

Returns the person bundle for an identifier from people search.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `identifier` | path | yes | URL-encoded person identifier. |

Request:

```bash
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  'https://query.datapublica.dk/api/people/pid_entity_4000350926'
```

Response `200`:

```json
{
  "person_id": "pid_entity_4000350926",
  "build_id": "build_...",
  "profile": {
    "person_id": "pid_entity_4000350926",
    "display_name": "Ole Bjørvig Pedersen",
    "identity_confidence": "high",
    "company_count": 173
  },
  "currentRoles": [],
  "roleHistory": [],
  "ownership": [],
  "auditorHistory": [],
  "contacts": [],
  "relatedCompanies": []
}
```

`profile` can be `null`; array items are dataset records. Common errors: `400` missing identifier, `401` token failure, `403` suspended owner, `404` person not found, `503` no active dataset.

### `GET /api/people/{identifier}/timeline`

**Authentication:** Required personal bearer token.

Returns person events ordered for cursor pagination.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `identifier` | path | yes | URL-encoded person identifier. |
| `limit` | query | no | Integer, 1–100; default 50. |
| `cursor` | query | no | Opaque `next_cursor` value from the previous response. |

Request:

```bash
curl --get \
  --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  --data-urlencode 'limit=1' \
  'https://query.datapublica.dk/api/people/pid_entity_4000350926/timeline'
```

Response `200`:

```json
{
  "events": [
    {
      "event_id": "person_timeline_role_...",
      "person_id": "pid_entity_4000350926",
      "category": "role",
      "field_key": "executive_board",
      "occurred_at": "2026-06-08T00:00:00.000Z",
      "source": "registry_participant",
      "metadata": {
        "event_type": "person_joined_management"
      }
    }
  ],
  "next_cursor": "1"
}
```

Common errors: `400` invalid identifier/pagination, `401` token failure, `403` suspended owner, `404` person not found, `503` no active dataset.

### `GET /api/people/{identifier}/portfolio`

**Authentication:** Required personal bearer token.

Returns the initial ownership graph rooted at a person.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `identifier` | path | yes | URL-encoded person identifier. |

Request:

```bash
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  'https://query.datapublica.dk/api/people/pid_entity_4000350926/portfolio'
```

Response `200`:

```json
{
  "root": "person:pid_entity_4000350926",
  "build_id": "build_...",
  "truncated": false,
  "nodes": [
    {
      "nodeId": "person:pid_entity_4000350926",
      "entityType": "person",
      "displayName": "Ole Bjørvig Pedersen",
      "personId": "pid_entity_4000350926",
      "isRoot": true
    }
  ],
  "edges": [
    {
      "sourceNodeId": "person:pid_entity_4000350926",
      "targetNodeId": "company:15273747",
      "ownershipLabel": "100%",
      "relationshipKind": "legal_owner"
    }
  ]
}
```

The complete node and edge shape is the same as the company portfolio response. Common errors: `400` missing identifier, `401` token failure, `403` suspended owner, `404` person not found, `503` no active dataset.

## Other participants

Participants represent registry actors that are not resolved as a company or person, such as foreign legal entities.

### `GET /api/participants/search`

**Authentication:** Required personal bearer token.

Searches other participants and returns pagination metadata plus participant row objects.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `q` | query | yes | Non-empty string after trimming. |
| `limit` | query | no | Integer, 1–100; default 20. |
| `offset` | query | no | Integer at least 0; default 0. |

Request:

```bash
curl --get 'https://query.datapublica.dk/api/participants/search' \
  --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  --data-urlencode 'q=maersk' \
  --data-urlencode 'limit=2'
```

Response `200`:

```json
{
  "query": "maersk",
  "total": 1,
  "limit": 2,
  "offset": 0,
  "rows": [
    {
      "participant_id": "pid_owner_enhed_4006813524",
      "display_name": "Maersk Broker Asia Limited",
      "slug": "maersk-broker-asia-limited",
      "display_hint": "foreign_legal_entity",
      "country_code": "HK"
    }
  ]
}
```

Common errors: `400` invalid/missing query or pagination, `401` token failure, `403` suspended owner, `503` no active dataset.

### `GET /api/participants/{identifier}`

**Authentication:** Required personal bearer token.

Returns the participant bundle for an identifier from participant search.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `identifier` | path | yes | URL-encoded participant identifier. |

Request:

```bash
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  'https://query.datapublica.dk/api/participants/pid_owner_enhed_4006813524'
```

Response `200`:

```json
{
  "participant_id": "pid_owner_enhed_4006813524",
  "build_id": "build_...",
  "profile": {
    "participant_id": "pid_owner_enhed_4006813524",
    "display_name": "Maersk Broker Asia Limited",
    "display_hint": "foreign_legal_entity",
    "country_code": "HK"
  },
  "currentRoles": [],
  "roleHistory": [],
  "ownership": [],
  "relatedCompanies": []
}
```

`profile` can be `null`; array items are dataset records. Common errors: `400` missing identifier, `401` token failure, `403` suspended owner, `404` participant not found, `503` no active dataset.

### `GET /api/participants/{identifier}/timeline`

**Authentication:** Required personal bearer token.

Returns participant events ordered for cursor pagination.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `identifier` | path | yes | URL-encoded participant identifier. |
| `limit` | query | no | Integer, 1–100; default 50. |
| `cursor` | query | no | Opaque `next_cursor` value from the previous response. |

Request:

```bash
curl --get \
  --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  --data-urlencode 'limit=1' \
  'https://query.datapublica.dk/api/participants/pid_owner_enhed_4006813524/timeline'
```

Response `200`:

```json
{
  "events": [
    {
      "event_id": "other_participant_timeline_...",
      "participant_id": "pid_owner_enhed_4006813524",
      "category": "ownership",
      "field_key": "legal_owner",
      "occurred_at": "2017-02-23T00:00:00.000Z",
      "source": "company_payload",
      "metadata": {}
    }
  ],
  "next_cursor": null
}
```

Common errors: `400` invalid identifier/pagination, `401` token failure, `403` suspended owner, `404` participant not found, `503` no active dataset.

### `GET /api/participants/{identifier}/portfolio`

**Authentication:** Required personal bearer token.

Returns the initial ownership graph rooted at another participant.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `identifier` | path | yes | URL-encoded participant identifier. |

Request:

```bash
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  'https://query.datapublica.dk/api/participants/pid_owner_enhed_4006813524/portfolio'
```

Response `200`:

```json
{
  "root": "participant:pid_owner_enhed_4006813524",
  "build_id": "build_...",
  "truncated": false,
  "nodes": [
    {
      "nodeId": "participant:pid_owner_enhed_4006813524",
      "entityType": "participant",
      "displayName": "Maersk Broker Asia Limited",
      "participantId": "pid_owner_enhed_4006813524",
      "displayHint": "foreign_legal_entity",
      "isRoot": true
    }
  ],
  "edges": []
}
```

The complete node and edge shape is the same as the company portfolio response. Common errors: `400` missing identifier, `401` token failure, `403` suspended owner, `404` participant not found, `503` no active dataset.

## Ownership relation expansion

### `GET /api/portfolio/entities/{entityType}/{id}/relations`

**Authentication:** Required personal bearer token.

Expands relations around one node without refetching the whole portfolio.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `entityType` | path | yes | `company`, `person`, or `participant`. |
| `id` | path | yes | URL-encoded entity identifier without the type prefix. |
| `direction` | query | no | `up`, `down`, or `both`; default `both`. |
| `visibleNodeIds` | query | no | Comma-separated graph node IDs already visible; duplicates/empty values are ignored and up to 200 distinct IDs are used. |

Request:

```bash
curl --get \
  --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  --data-urlencode 'direction=both' \
  --data-urlencode 'visibleNodeIds=company:14748105' \
  'https://query.datapublica.dk/api/portfolio/entities/company/14748105/relations'
```

Response `200`:

```json
{
  "targetNodeId": "company:14748105",
  "truncated": false,
  "nodes": [
    {
      "nodeId": "company:37933562",
      "entityType": "company",
      "displayName": "Example Holding A/S",
      "companyCvrNumber": "37933562",
      "isRoot": false
    }
  ],
  "edges": [
    {
      "sourceNodeId": "company:37933562",
      "targetNodeId": "company:14748105",
      "ownershipLabel": "66.67-89.99%",
      "relationshipKind": "legal_owner"
    }
  ],
  "updatedNode": {
    "nodeId": "company:14748105",
    "isExpandedUp": true,
    "isExpandedDown": true
  }
}
```

`updatedNode` can be `null`. Common errors: `400` invalid type/parameters or missing ID, `401` token failure, `403` suspended owner, `404` entity not found, `503` no active dataset.

## SQL

Queries run on [DuckDB](https://duckdb.org/docs/stable/sql/introduction), so use DuckDB SQL syntax and functions.

### `GET /api/sql`

**Authentication:** Required personal bearer token. SQL usage limits apply.

Executes one read-only SQL statement against the active dataset. Mutating statements, data-definition statements, multiple statements, and statements outside the read-only guard are rejected.

| Parameter | In | Required | Type and rules |
| --- | --- | --- | --- |
| `query` | query | conditionally | SQL string, 1–20,000 characters. Supply `query` or the alias `sql`. |
| `sql` | query | conditionally | Alias for `query`; do not send both. |
| `source` | query | no | `direct` or `natural-language`; default `direct`. |
| `rawInput` | query | conditional | Original question, 1–1,000 characters; required when `source=natural-language`. |
| `x-request-id` | header | no | Caller-generated identifier, up to 128 allowed characters. Reuse the translation request ID when executing translated SQL. |

Request:

```bash
curl --get 'https://query.datapublica.dk/api/sql' \
  --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  --data-urlencode 'query=SELECT cvr_number, company_name FROM gold.mart_company_profile ORDER BY company_name LIMIT 1'
```

Response `200`:

```json
{
  "query": "SELECT cvr_number, company_name FROM gold.mart_company_profile ORDER BY company_name LIMIT 1",
  "columns": ["cvr_number", "company_name"],
  "rowCount": 1,
  "rows": [
    {
      "cvr_number": "43854968",
      "company_name": "!Act A/S"
    }
  ]
}
```

Common errors: `400` missing/invalid/non-read-only SQL, `401` token failure, `403` suspended owner or usage block, `429` short-term rate limit, `503` no active dataset or usage-decision service unavailable.

### `POST /api/sql/translate`

**Authentication:** Required personal bearer token. Natural-language usage limits apply.

Translates an English or Danish question into one read-only SQL statement. Translation does not execute the SQL. To execute an accepted `sql` value against the same natural-language usage reservation, call `GET /api/sql` with the same `x-request-id`, `source=natural-language`, and the exact original question as `rawInput`. Without that correlation, execution is treated as a separate direct SQL request and SQL usage limits apply separately.

| Field | In | Required | Type and rules |
| --- | --- | --- | --- |
| `query` | JSON body | yes | String, 1–1,000 characters after trimming. |
| `locale` | JSON body | no | `en` or `da`; default `en`. This documentation is English-only, but the request supports both values. |
| `maxRows` | JSON body | no | Integer, 1–100; default 100. |
| `priorTurns` | JSON body | no | Array of at most 10 earlier turns, oldest first, each `{"question": string, "sql": string \| null}`. Supplying them lets a follow-up such as `"same, but above 50M"` resolve against the earlier questions. |

Every response carries `resolvedQuestion`: the self-contained question that was actually translated. It equals the trimmed `query` unless follow-up phrasing was resolved against `priorTurns`. Store it rather than the raw follow-up text when you keep a history of questions, so each entry still reads correctly on its own.

Request:

```bash
REQUEST_ID="$(uuidgen)"

curl 'https://query.datapublica.dk/api/sql/translate' \
  --request POST \
  --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  --header "x-request-id: $REQUEST_ID" \
  --header 'Content-Type: application/json' \
  --data '{"query":"Companies with the highest revenue in 2025","locale":"en","maxRows":1}'
```

Response `200`:

```json
{
  "status": "ok",
  "sql": "SELECT ... FROM gold.mart_company_financials_annual ... LIMIT 1",
  "exportSql": "SELECT ... FROM gold.mart_company_financials_annual ... LIMIT 1",
  "resolvedQuestion": "Companies with the highest revenue in 2025",
  "description": "Returns the active company with the highest reported revenue for the 2025 reporting-period end year.",
  "notes": ["Revenue is ranked using the exact annual revenue field, with no fallback metric."],
  "assumptions": ["‘In 2025’ is interpreted as reporting-period end year 2025."],
  "warnings": [],
  "metadata": {
    "provider": "openai",
    "model": "model-name",
    "buildId": "build_...",
    "schemaVersion": "sha256:...",
    "referencedTables": [
      "gold.mart_company_financials_annual",
      "gold.mart_company_latest_status"
    ],
    "limit": {
      "kind": "default_preview",
      "rowCount": 1
    }
  }
}
```

Response `422` when clarification is required or the behavior is unsupported:

```json
{
  "status": "unsupported",
  "sql": null,
  "resolvedQuestion": "Delete all inactive companies",
  "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 alternate `422` outcome has `status: "needs_clarification"` and `error.code: "needs_clarification"`. Common errors: `400` invalid JSON/body fields, `401` token failure, `403` suspended owner or usage block, `422` handled translation issue, `429` short-term rate limit, `503` no active dataset or usage-decision service unavailable.

For a successful translation, copy the returned `sql` value into `TRANSLATED_SQL` and execute it with the correlated request metadata:

```bash
TRANSLATED_SQL='SELECT ... FROM gold.mart_company_financials_annual ... LIMIT 1'

curl --get 'https://query.datapublica.dk/api/sql' \
  --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
  --header "x-request-id: $REQUEST_ID" \
  --data-urlencode 'source=natural-language' \
  --data-urlencode 'rawInput=Companies with the highest revenue in 2025' \
  --data-urlencode "query=$TRANSLATED_SQL"
```

## Error bodies and usage headers

Standard errors use `{ "error": "message" }`. Usage blocks add `code`, `planKey`, `quotaBucket`, `limit`, `used`, `remaining`, `windowStart`, `windowEnd`, and `resetAt` when available. Usage responses can include `X-Usage-Bucket`, `X-Usage-Limit`, `X-Usage-Remaining`, and `X-Usage-Reset`.

See [Errors](/docs/errors) for retry guidance and [Authentication](/docs/api/authentication) for the one-active-token model.
