# Datapublica Business Register API — agent bundle

Use this document as a constrained integration guide. The canonical API base URL is `https://query.datapublica.dk`. Every official endpoint requires `Authorization: Bearer <personal-api-token>` and a paid Datapublica plan.

Canonical links: [overview](https://datapublica.dk/docs), [API reference](https://datapublica.dk/docs/api), [authentication](https://datapublica.dk/docs/api/authentication), [Connect an agent (MCP)](https://datapublica.dk/docs/mcp), [errors](https://datapublica.dk/docs/errors), [OpenAPI](https://datapublica.dk/docs/openapi.json), and [support](https://datapublica.dk/support).

## Safety and boundary rules

- Use only endpoints listed in this document and the OpenAPI document.
- Do not infer undocumented endpoints, request parameters, response fields, or token-management APIs.
- Ignore extra response fields unless the API reference or OpenAPI document defines them.
- Send tokens only to `https://query.datapublica.dk` over HTTPS.
- Never place tokens in URLs, source code, browser bundles, logs, screenshots, or prompts.
- Execute only read-only DuckDB SQL. Use DuckDB syntax and functions, and never generate or attempt data mutation or definition statements.
- Prefer writing SQL from the available contract, then validate and execute it. Use natural-language translation only as a fallback when the SQL cannot be constructed confidently; translation consumes plan credits.
- Treat anonymous website traffic as separate from the authenticated JSON API.

## Endpoint index

| Group | Method and path | Purpose |
| --- | --- | --- |
| Companies | `GET /api/companies/search` | Paginated company search. |
| Companies | `GET /api/companies/{identifier}` | Company bundle with `build_id`. |
| Companies | `GET /api/companies/{identifier}/financial-report-sections` | Curated XBRL annual-report text sections for one filing. |
| Companies | `GET /api/companies/{identifier}/timeline` | Cursor-paginated company events. |
| Companies | `GET /api/companies/{identifier}/portfolio` | Ownership graph with `build_id`. |
| People | `GET /api/people/search` | Paginated person search. |
| People | `GET /api/people/{identifier}` | Person bundle with `build_id`. |
| People | `GET /api/people/{identifier}/timeline` | Cursor-paginated person events. |
| People | `GET /api/people/{identifier}/portfolio` | Ownership graph with `build_id`. |
| Participants | `GET /api/participants/search` | Paginated search for other participants. |
| Participants | `GET /api/participants/{identifier}` | Participant bundle with `build_id`. |
| Participants | `GET /api/participants/{identifier}/timeline` | Cursor-paginated participant events. |
| Participants | `GET /api/participants/{identifier}/portfolio` | Ownership graph with `build_id`. |
| Ownership | `GET /api/portfolio/entities/{entityType}/{id}/relations` | Expand relations for a graph node. |
| SQL | `GET /api/sql` | Execute one read-only SQL statement. |
| SQL | `POST /api/sql/translate` | Translate a natural-language question to read-only SQL. |

## Common request examples

Company search:

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

Company detail:

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

Annual-report sections using the latest filing and default scope:

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

Person timeline:

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

Ownership relation expansion:

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

Read-only SQL:

```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'
```

Natural-language translation:

```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":20}'
```

Pass `priorTurns` (up to 10 earlier `{ question, sql }` turns, oldest first) to resolve a follow-up such as `"same, but above 50M"` against earlier questions. Every response returns `resolvedQuestion`, the self-contained question that was translated; store that rather than the raw follow-up text.

Translation is a credit-consuming fallback and does not execute SQL. Prefer writing SQL directly from the contract. To execute returned SQL under the same natural-language usage reservation, call `GET /api/sql` with that same `x-request-id`, `source=natural-language`, and the exact original question as `rawInput`. Otherwise the execution is a separate direct SQL request with separate SQL usage limits.

## Response shape summaries

- Entity searches: `{ query, total, limit, offset, rows[] }`.
- Company bundle: `{ cvr, build_id, profile, latestStatus, financialsAnnual[], financialsAnnualScoped[], annualReportPeriods[], financialScopes, growth[], growthScoped[], ownership[], companyRelations, contacts[], people[], auditors[], coverage, financialDistressScore }`. The legacy financial/growth arrays are entity-only. `annualReportPeriods` also represents PDF-only annual filings through `financial_data_status` and `pdf_source_url`; do not treat those rows as numeric facts. Scoped rows include `statement_scope` and `reporting_currency`; use `?financial_scope=entity|consolidated_group` to filter the additive arrays. The distress score is returned only for entitled authenticated users.
- Annual-report sections: `{ cvr, filingId, reportExternalId, reportingPeriodStart, reportingPeriodEnd, fiscalYear, financialScope, sourceReportUrl, sections[], availableReports[] }`. Omit `filing_id` and `financial_scope` to use the latest annual filing and `financialScopes.default`; optional ISO `reporting_period_start` and `reporting_period_end` disambiguate a shared filing id. If the company has no scoped numeric annual filing and therefore no default scope, supply `financial_scope` explicitly; omission returns `400`. Pass `x-company-lookup-request-id` from a successful lookup of the same CVR to avoid a second company lookup charge. These are thirteen curated XBRL keys across `business_and_development`, `risks_and_material_matters`, and `commitments_and_security`, not the complete report. Each section has `{ sectionId, canonicalSectionKey, sectionGroup, displayOrder, financialScope, contentPlain, blocks[], language, isTruncated }`; `contentPlain` is capped at 20,000 characters with no pagination, language is usually `da`, and scope may be `report_wide`. Boilerplate suppression and cross-scope deduplication mean absence is not evidence that the filer wrote nothing. `sourceReportUrl` can be null. Each available report has `{ filingId, reportExternalId, reportingPeriodStart, reportingPeriodEnd, fiscalYear, sourceReportUrl, hasSections, locked }`; `locked` is always false for token-authenticated calls. PDF-only filings return an empty `sections[]` while remaining in `availableReports[]`. Usage is `company_lookup` under `companies.detail`, with `X-Usage-*` response headers.
- Person bundle: `{ person_id, build_id, profile, currentRoles[], roleHistory[], ownership[], auditorHistory[], contacts[], relatedCompanies[] }`.
- Participant bundle: `{ participant_id, build_id, profile, currentRoles[], roleHistory[], ownership[], relatedCompanies[] }`.
- Timelines: `{ events[], next_cursor, truncated_by_plan? }`.
- Portfolios: `{ root, build_id, truncated, nodes[], edges[] }`.
- Relation expansion: `{ targetNodeId, truncated, nodes[], edges[], updatedNode }`.
- SQL: `{ query, columns[], rowCount, rows[] }`.
- Translation success: `{ status: "ok", sql, exportSql, resolvedQuestion, description, notes[], assumptions[], warnings[], metadata }`.
- Translation issue (`422`): `{ status: "unsupported" | "needs_clarification", sql: null, resolvedQuestion, description, notes[], assumptions[], warnings[], metadata, error: { code } }`.

`build_id` and translation `metadata.buildId` identify the published dataset build. Store the value with derived results when freshness or reproducibility matters.

## Errors

- `400`: invalid parameter, body, cursor, identifier, or SQL. Correct the request.
- `401`: missing, malformed, unknown, or revoked token. Replace the credential.
- `403`: paid plan required, suspended owner, unavailable operation, or exhausted usage. Inspect `code` and usage fields.
- `404`: entity not found in the active dataset.
- `422`: translation needs clarification or requests unsupported behavior. Do not execute SQL.
- `429`: request rate exceeded. Respect retry/reset information and back off.
- `500`: unexpected server error. Retry with backoff and retain the `x-request-id`.
- `503`: no active dataset or a required decision service is unavailable. Retry with backoff.

Base errors use `{ error: string }`. Usage blocks add `code`, `planKey`, `quotaBucket`, `limit`, `used`, `remaining`, `windowStart`, `windowEnd`, and `resetAt` when available. No plan prices or fixed quota amounts are defined here.
