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
limitandoffset. - Timeline pagination uses
limitand an opaquecursor; passnext_cursorunchanged. - 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_idas the dataset freshness cue. - You can send a valid caller-generated
x-request-idheader; the server reflects it or generates one. Every response includesx-request-id. Usage-controlled responses can also includeX-Usage-*headers.
Common errors are described in 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:
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:
{
"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.
POST /api/companies/similar
Authentication: Required personal bearer token.
Ranks active companies against one to five known companies. Seed companies are excluded. The semantic dimensions are activity, products (products and services), and customers (customer types).
| Field | Required | Type and rules |
|---|---|---|
seedCvrNumbers | yes | 1–5 unique eight-digit CVR numbers. |
mode | no | shared rewards matches across all seeds; either uses the strongest seed match. Default shared. |
priorities | no | Each dimension is off, normal, or high. Defaults: activity normal, products high, customers off. At least one must be enabled. |
filters.regionCode | no | Danish region code 1081–1085. |
filters.revenueOrGrossProfitDkk | no | Inclusive { "min": number, "max": number } range. |
filters.profitAfterTaxDkk | no | Inclusive DKK range. |
filters.equityDkk | no | Inclusive DKK range. |
filters.employees | no | Inclusive registry-first legal-entity headcount range, with entity annual-average fallback. |
limit | no | Integer, 1–40; default 40. |
curl 'https://query.datapublica.dk/api/companies/similar' \
--request POST \
--header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"seedCvrNumbers":["33156898"],"priorities":{"activity":"normal","products":"high","customers":"off"}}'
Financial filters use dated approximate DKK conversion where the source currency differs. Returned financial amounts remain in their native currency and are not converted.
Financial values come from one latest numeric row of the queried CVR's consolidated accounts when published, otherwise entity accounts. Missing group metrics remain missing. statementScope, reportingPeriodEnd, currency, latestReportPeriodEnd and availability identify that row and any newer unsupported report. Generic size filtering uses employment.count: the latest usable numeric CVR registry headcount, including monthly series and zero, then a usable entity annual-report average. Employment source, period and legal-entity scope are independent of the financial scope. Interval-only registry observations are retained without inventing exact counts.
POST /api/companies/semantic-search
Authentication: Required personal bearer token.
Discovers companies from a description of company type, business activity, products, services, or customer types.
| Field | Required | Type and rules |
|---|---|---|
query | yes | String, 3–500 characters after trimming. |
limit | no | Integer, 1–200; default 50. |
Do not put geography, financials, size, or status in query. For a request such as “SaaS companies in Copenhagen,” send {"query":"SaaS companies","limit":200} and filter the returned city, regionCode, latestFinancials, and isActive fields locally. This post-filtering only narrows the returned candidate pool; it cannot recover relevant companies outside that pool, so request a larger limit when deterministic filtering matters.
curl 'https://query.datapublica.dk/api/companies/semantic-search' \
--request POST \
--header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"query":"SaaS companies","limit":200}'
Both semantic endpoints return the same compact response shape:
{
"buildId": "build_...",
"results": [
{
"rank": 1,
"cvrNumber": "12345678",
"name": "Example A/S",
"isActive": true,
"city": "København K",
"regionCode": "1084",
"regionName": "Region Hovedstaden",
"industryCode": "620100",
"industry": "Computer programming activities",
"latestFinancials": {
"revenueOrGrossProfit": 12000000,
"revenueOrGrossProfitSource": "gross_profit",
"profitAfterTax": 1000000,
"equity": 3000000,
"averageEmployees": 18,
"currency": "DKK",
"reportingPeriodEnd": "2025-12-31",
"statementScope": "consolidated_group",
"latestReportPeriodEnd": "2025-12-31",
"availability": "numeric"
},
"employment": {
"count": 22,
"source": "cvr_registry",
"periodEnd": "2026-07-31",
"scope": "legal_entity",
"registryObservation": {
"headcount": 22,
"headcountInterval": null,
"fte": null,
"fteInterval": null,
"periodStart": "2026-07-01",
"periodEnd": "2026-07-31",
"frequency": "monthly",
"source": "maanedsbeskaeftigelse",
"updatedAt": null
}
},
"matchedCharacteristics": ["activity", "products"],
"sourceUrl": "https://example.dk",
"enrichedAt": "2026-09-21T10:00:00.000Z"
}
]
}
latestFinancials.averageEmployees is the selected annual-report average. Use employment for generic employees; its annual_report_average fallback is explicitly labeled as an average. Explicit annual averages, group employees and FTE are separate metrics available through the scoped financial/registry contracts. Never sum a group's consolidated values with its subsidiaries.
Seed-based results also include matchedSeedCvrNumbers. rank is the only ranking measure. No distance, percentage similarity, fusion score, embedding, model, or provider detail is exposed. Results are ranked candidates rather than an exhaustive list of relevant companies.
Common errors: 400 invalid JSON/body fields, 401 token failure, 403 suspended owner or usage block, 429 short-term rate limit, 503 no active dataset or semantic index unavailable.
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:
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
'https://query.datapublica.dk/api/companies/14748105'
Response 200 shape and example:
{
"cvr": "14748105",
"build_id": "build_...",
"profile": {
"cvr_number": "14748105",
"company_name": "CHRISTOFFERSEN & KNUDSEN A/S",
"city": "Rødovre",
"is_active": true,
"vat_registered": true,
"vat_checked_at": "2026-11-05T19:11:42.966Z"
},
"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",
"source_report_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 filed only as a PDF, without numeric facts. source_report_url links the official report document: the filed PDF, or the inline XBRL XHTML when no PDF was filed. 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.vat_registered states whether Skattestyrelsen listed the company as VAT registered on its own CVR number when checked at profile.vat_checked_at. false can mean coverage by a joint VAT registration; both are null until the company has been checked. A missing or week-old status is looked up during the request, adding up to three seconds. 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:
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
'https://query.datapublica.dk/api/companies/27626661/financial-report-sections'
Response 200 shape:
{
"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:
curl --get \
--header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
--data-urlencode 'limit=1' \
'https://query.datapublica.dk/api/companies/14748105/timeline'
Response 200:
{
"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. |
includeProperties | query | no | true adds property nodes; see Properties in ownership graphs. |
Request:
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
'https://query.datapublica.dk/api/companies/14748105/portfolio'
Response 200:
{
"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:
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:
{
"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:
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
'https://query.datapublica.dk/api/people/pid_entity_4000350926'
Response 200:
{
"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": [],
"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:
curl --get \
--header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
--data-urlencode 'limit=1' \
'https://query.datapublica.dk/api/people/pid_entity_4000350926/timeline'
Response 200:
{
"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. |
includeProperties | query | no | true adds property nodes; see Properties in ownership graphs. |
Request:
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
'https://query.datapublica.dk/api/people/pid_entity_4000350926/portfolio'
Response 200:
{
"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:
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:
{
"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:
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
'https://query.datapublica.dk/api/participants/pid_owner_enhed_4006813524'
Response 200:
{
"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:
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:
{
"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:
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
'https://query.datapublica.dk/api/participants/pid_owner_enhed_4006813524/portfolio'
Response 200:
{
"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. |
includeProperties | query | no | true adds property nodes; see Properties in ownership graphs. |
Request:
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:
{
"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.
Properties
Danish property (ejendom) data from MAT, BBR, DAR, EBR, VUR, and EJF (Ejerfortegnelsen, including trade information). A property is identified by its BFE number. Coordinates are WGS84: points are { longitude, latitude } and shapes are GeoJSON geometries. A property's kind is sfe (samlet fast ejendom), condominium (ejerlejlighed), or bpfg (bygning på fremmed grund).
Property responses carry a PropertyRef ({ bfe, kind, label, slug, address }) wherever they name a property. Owners, buyers, and sellers are parties with type person, company, other, or protected; a person party's personId is a Datapublica person ID or ejf_<uuid without dashes> for an owner known only from Ejerfortegnelsen, and either form works in the person property routes. The OpenAPI document has the complete schemas.
Every property endpoint can return 503 when property data is not available. Common errors are otherwise 401 token failure and 403 suspended owner.
GET /api/properties/search
Authentication: Required personal bearer token.
Finds properties by address or BFE number. The query matches an exact BFE number or every word of an address (street, house number, floor and door, postal code, city). Danish letters also match their ASCII spellings (ae, oe, aa), and misspelled street and town words are corrected when nothing matches.
| Parameter | In | Required | Type and rules |
|---|---|---|---|
q | query | no | Address text or BFE number. An empty query returns no rows. |
limit | query | no | Integer, 1–50; default 10. Larger values are capped at 50. |
Request:
curl --get 'https://query.datapublica.dk/api/properties/search' \
--header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
--data-urlencode 'q=Rådhuspladsen 1 1550' \
--data-urlencode 'limit=1'
Response 200:
{
"query": "Rådhuspladsen 1 1550",
"rows": [
{
"property": {
"bfe": "6033528",
"kind": "sfe",
"label": "Rådhuspladsen 1, 1550 København V",
"slug": "raadhuspladsen-1-1550-koebenhavn-v",
"address": {
"darId": null,
"street": "Rådhuspladsen",
"houseNumber": "1",
"floor": null,
"door": null,
"postalCode": "1550",
"city": "København V",
"text": "Rådhuspladsen 1, 1550 København V"
}
},
"mainUsage": "Bygning til kontor",
"ownerNames": ["Københavns Kommune"],
"ownerCount": 1
}
]
}
ownerNames lists up to two current owners, largest share first; ownerCount counts all distinct current owners.
GET /api/properties/{bfe}
Authentication: Required personal bearer token.
Returns the full property: facts and plot ratio, geometry, current and previous owners, parcels, BBR buildings, units and technical installations, public valuations, trades, and a history of trade, valuation, and building events. A condominium links its motherProperty; a main property divided into condominiums lists up to 2,000 condominiums, and condominiumsTruncated marks more.
| Parameter | In | Required | Type and rules |
|---|---|---|---|
bfe | path | yes | BFE number, 1–12 digits. |
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
'https://query.datapublica.dk/api/properties/6033528'
Response fields: { property, esrNumber, municipality, municipalityCode, mainUsage, mainUsageCode, dividedIntoCondominiums, facts, geometry, motherProperty, condominiumNumber, condominiumShare, condominiums[], condominiumsTruncated, owners[], previousOwners[], parcels[], buildings[], units[], technicalInstallations[], valuations[], trades[], history[] }. Each trade has priceTotal for the whole transaction and propertyCount for the number of properties sold in it. Common errors: 404 property not found.
GET /api/properties/{bfe}/ownership
Authentication: Required personal bearer token.
Returns the ownership graph upward from a property: its person and CVR company owners and the CVR owners above them. Other and protected owners appear only in the property detail. The response uses the portfolio graph shape, rooted at the property:<bfe> node.
| Parameter | In | Required | Type and rules |
|---|---|---|---|
bfe | path | yes | BFE number, 1–12 digits. |
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
'https://query.datapublica.dk/api/properties/6033528/ownership'
truncated marks a graph cut at server limits. Common errors: 404 property not found.
GET /api/properties/{bfe}/companies
Authentication: Required personal bearer token.
Lists companies registered in CVR at the property's addresses, 100 rows per page. current holds open registrations of active companies; previous holds ended registrations and those of companies that are no longer active.
| Parameter | In | Required | Type and rules |
|---|---|---|---|
bfe | path | yes | BFE number, 1–12 digits. |
tab | query | no | current or previous; default current. |
cursor | query | no | nextCursor from the previous response. |
curl --get \
--header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
--data-urlencode 'tab=current' \
'https://query.datapublica.dk/api/properties/6033528/companies'
Response fields: { bfe, tab, counts: { current, previous }, rows[], nextCursor }. Each row has { cvrNumber, companyName, operatingStatusLabel, operatingStatusKind, primaryBranch, validFrom, validTo, unit, group, subProperty }. Rows are ordered by house number, and consecutive rows with the same group.key share a DAR house number. subProperty names the condominium or building on leased land holding the address when it is not the requested property. Common errors: 400 invalid cursor, 404 property not found.
GET /api/properties/at/{darId}
Authentication: Required personal bearer token.
Resolves a DAR address (adresse) or house number (husnummer) to the property holding it and returns its PropertyRef. A condominium or building on leased land holding the address is preferred over the main property.
| Parameter | In | Required | Type and rules |
|---|---|---|---|
darId | path | yes | DAR address or house-number UUID, case-insensitive. |
unitHash | query | no | 20 lowercase hex characters selecting a floor and door of a house number: the <unitHash> part of a dar-<darId>-unit-<unitHash> registered-address identity in company data. |
Common errors: 400 invalid DAR identifier or unitHash, 404 no property found at the address.
GET /api/properties/map/parcels
Authentication: Required personal bearer token.
Returns MAT parcels whose bounding box intersects a map area, each with its property's PropertyRef, up to eight current owners, owner class, usage, land and floor area, plot ratio, latest trade, valuation, and owner age. Property-level values repeat on every parcel of a multi-parcel property. At most 2,000 parcels are returned; truncated: true means more intersect the area.
| Parameter | In | Required | Type and rules |
|---|---|---|---|
bbox | query | yes | west,south,east,north in WGS84, with west < east and south < north, inside longitude 2–20 and latitude 53–59. The area can be at most 0.02 square degrees (about 0.13° × 0.15°). |
curl --get \
--header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
--data-urlencode 'bbox=12.566,55.674,12.572,55.677' \
'https://query.datapublica.dk/api/properties/map/parcels'
Response fields: { parcels[], truncated }. Common errors: 400 invalid bbox.
GET /api/properties/map/owner-age-grid
Authentication: Required personal bearer token.
Returns Danmarks Statistik grid cells (kvadratnet) intersecting a map area with the median age of the living private owners placed in each cell.
| Parameter | In | Required | Type and rules |
|---|---|---|---|
bbox | query | yes | west,south,east,north in WGS84 with the same rules as the parcels map. The area can be at most 40 square degrees at resolution 1000 and 0.25 at resolution 100. |
resolution | query | yes | Cell size in metres: 1000 or 100. |
Response fields: { resolution, cells[], truncated }; each cell has { cellId, west, south, east, north, medianAge, ownerCount }. At most 60,000 cells are returned at resolution 1000 and 25,000 at resolution 100. Common errors: 400 invalid bbox or resolution.
GET /api/companies/{cvr}/properties
Authentication: Required personal bearer token.
Returns the properties a company owns directly or through companies it owns, with the company's effective share, plus its former holdings.
| Parameter | In | Required | Type and rules |
|---|---|---|---|
cvr | path | yes | Eight-digit CVR number. |
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
'https://query.datapublica.dk/api/companies/64942212/properties'
Response fields: { root, truncated, totals, properties[], formerProperties[] }. Each property row has the ownerOfRecord, the ownedVia companies between the company and that owner, effectiveShareLower/effectiveShareUpper (0–1, a range when CVR ownership is reported in bands), areas, units, the latest valuation and trade, and acquiredOn. Rows sharing a valuationId carry the same public assessment. Lists are capped at 5,000 properties; truncated: true means the list or ownership chain was cut and totals cover only the returned properties. Common errors: 404 company not found.
GET /api/companies/{cvr}/properties/counts
Authentication: Required personal bearer token.
Returns { current, former, trades, vitals }: the number of current and former properties in the company's portfolio and of its direct trade rows. vitals is null for companies. Common errors: 404 company not found.
GET /api/companies/{cvr}/properties/trades
Authentication: Required personal bearer token.
Returns { trades[], truncated }: registered trades in which the company itself was buyer or seller, newest first, with one row per side as { trade, property, side, entityShare }. At most 500 rows are returned; truncated: true means more exist. Common errors: 404 company not found.
GET /api/people/{personId}/properties
Authentication: Required personal bearer token.
Returns the properties a person owns directly or through companies they own, in the same shape as the company property portfolio. root.vitals and root.address are present for owners known from Ejerfortegnelsen.
| Parameter | In | Required | Type and rules |
|---|---|---|---|
personId | path | yes | URL-encoded Datapublica person ID, or ejf_<uuid without dashes> from a property party's personId. |
curl --header "Authorization: Bearer $DATAPUBLICA_API_TOKEN" \
'https://query.datapublica.dk/api/people/pid_entity_4000350926/properties'
Common errors: 400 malformed identifier encoding, 404 person not found.
GET /api/people/{personId}/properties/counts
Authentication: Required personal bearer token.
Returns { current, former, trades, vitals } for a person. vitals holds birth and death dates from Ejerfortegnelsen and is null when the person is not linked to exactly one owner identity. Common errors: 400 malformed identifier encoding, 404 person not found.
GET /api/people/{personId}/properties/trades
Authentication: Required personal bearer token.
Returns { trades[], truncated } for registered trades in which the person was buyer or seller, in the same shape and with the same 500-row limit as the company route. Common errors: 400 malformed identifier encoding, 404 person not found.
Properties in ownership graphs
GET /api/companies/{identifier}/portfolio, GET /api/people/{identifier}/portfolio, and GET /api/portfolio/entities/{entityType}/{id}/relations accept includeProperties=true. The graph then also contains the properties owned by its people and companies as nodes with entityType: "property", a property:<bfe> node ID, and the BFE number in displayHint, linked by legal_owner edges from their owners. An owner of six or more properties gets one property-group:<ownerNodeId> node instead, whose displayHint is group:<ownerNodeId>. Without the parameter, graphs contain no property nodes.
SQL
Queries run on DuckDB, 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:
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:
{
"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:
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:
{
"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:
{
"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:
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 for retry guidance and Authentication for the one-active-token model.