Canonical
CVR (Denmark)
Danish company register — Erhvervsstyrelsen CVR Distribution: companies, roles, and plain-XBRL annual financials.
Source: Erhvervsstyrelsen (Danish Business Authority), CVR Distribution Data: Danish companies (CVR-permanent index) + filings (offentliggoerelser index) License: Free public-sector data. Erhvervsstyrelsen requires that data be redistributed with an attribution to CVR; no resale restriction. Attribution required: Yes — credit "CVR" or "Erhvervsstyrelsen" for company data and financial filings. Link: https://distribution.virk.dk (data API), https://virk.dk (public web) Update cadence:
dk-companiesruns weekly Mondays at 03:30 UTC against the seed list.dk-financialsruns daily at 04:30 UTC and walks the full DK universe in cursor batches (500 per run).
What it is
CVR (Det Centrale Virksomhedsregister) is Denmark's authoritative business register. The CVR Distribution endpoint at distribution.virk.dk is an Elasticsearch 1.7.4 cluster that exposes the full register over HTTP Basic auth (credentials issued by cvrselvbetjening@erst.dk). Two indexes carry the data Orakel needs:
cvr-permanent/virksomhed— one document per company, with participants and production units embedded underVrvirksomhed.deltagerRelation[]andVrvirksomhed.penheder[]. Searched via term query onVrvirksomhed.cvrNummer.offentliggoerelser— one document per filing (annual reports, change notices, financial statements). Each filing has adokumenter[]array with URLs to the source files: XHTML (iXBRL), XML (plain XBRL instance), and ZIP (full package).
CVR-numbers are 8 digits (e.g. Maersk = 22756214), distinct from NO 9-digit, FI 7+1 dashed, and SE 10-digit. The composite (country, orgNumber) key on the Company model handles all four uniformly.
Fields provided
Rows are stored under country = "DK". Source code in lib/cvr/companies.ts.
| Field | Type | Notes |
|---|---|---|
orgNumber |
string (8 digits, zero-padded) | From Vrvirksomhed.cvrNummer. Part of composite key. |
country |
string | Always "DK". |
name |
string | From virksomhedMetadata.nyesteNavn.navn. |
orgFormCode |
string | From virksomhedMetadata.nyesteVirksomhedsform.virksomhedsformkode (numeric). E.g. 60 = A/S, 80 = ApS. |
orgFormDescription |
string | Long Danish description (langBeskrivelse), falling back to short form. |
naceCode1 / naceCode2 / naceCode3 |
string | From nyesteHovedbranche.branchekode + nyesteBibranche{1,2,3}. NACE Rev. 2 codes. |
naceDescription1 ... |
string | Danish NACE descriptions from the same blocks. |
employeeCount |
int | From nyesteAarsbeskaeftigelse.antalAnsatte. May be stale (Maersk fixture is 2018). |
businessAddressStreet |
string | Composed: vejnavn + husnummerFra[-husnummerTil] + optional etage + sidedoer. |
businessAddressZip |
string | From postnummer. |
businessAddressCity |
string | From postdistrikt. |
businessAddressMuni / businessAddressMuniNo |
string | From kommune.kommuneNavn / kommune.kommuneKode. |
phone |
string | First non-secret phone in telefonNummer[]. |
website |
string | First entry in hjemmeside[] (often empty for listed entities). |
foundingDate / registrationDate |
date | From stiftelsesDato, fallback to earliest livsforloeb[].periode.gyldigFra. |
isBankrupt |
bool | True when sammensatStatus ∈ {OPLØST, OPLOST}. |
isBeingDissolved |
bool | True when sammensatStatus ∈ {OPLØSNINGSPROCES, TVANGSOPLOSE}. |
Roles are co-located in the same response under deltagerRelation[] and emitted to the Role table via lib/cvr/roles.ts:
LEDELSESORGAN+ organisationsNavn"Bestyrelse"→roleTypeCode = "BOARD_MEMBER"LEDELSESORGAN+ organisationsNavn"Direktion"→roleTypeCode = "EXECUTIVE"LEDELSESORGAN+ organisationsNavn"Tilsynsråd"→roleTypeCode = "SUPERVISORY_BOARD"REVISION→roleTypeCode = "AUDITOR"(firm or individual)REGISTER+"Fuldt ansvarlige deltagere"→roleTypeCode = "OWNER"(partnerships only)
Endpoints that surface this data
GET /api/companies/:orgNumber?country=DK— full company record for an 8-digit CVR-number.GET /api/companies?country=DK— search / list Danish companies.GET /api/companies/:orgNumber/group?country=DK— corporate hierarchy.
Annual financial statements
Filings come from the offentliggoerelser index, filtered to offentliggoerelsestype = "regnskab". Pipeline (lib/cvr/financials.ts):
- Query CVR for the company's filings, sorted newest-first.
- For each
application/xmldocument URL in the latest filing'sdokumenter[], download the XBRL instance. - Parse it with
lib/cvr/xbrl.ts— extracts contexts, units, and numeric facts across both DCCAfsa:and IFRS-full taxonomies. - Pick the latest fiscal-year duration period; map concepts to
Financialcolumns:Revenue←fsa:Revenue/ifrs-full:RevenueProfitLossFromOperatingActivities←fsa:ProfitLossFromOrdinaryOperatingActivities/ifrs-full:ProfitLossFromOperatingActivitiesProfitLossBeforeTax←fsa:ProfitLossFromOrdinaryActivitiesBeforeTax/ifrs-full:ProfitLossBeforeTaxProfitLoss←fsa:ProfitLoss/ifrs-full:ProfitLossAssets←fsa:Assets/ifrs-full:AssetsEquity←fsa:Equity/ifrs-full:EquityLiabilities←fsa:Liabilities/ifrs-full:Liabilities
- Upsert into
Financialwithcountry = "DK",regnskapstype = "ANNUAL", currency from the matched XBRL unit (DKK / EUR / USD — listed entities like Maersk report in USD).
Limitations
The KNOWN_GAPS.DK entries from lib/countries.ts, verbatim:
beneficial ownership — post-Sept 2025 6AMLD requires legitimate-interest justification; skipped by default
Other current limitations:
- First-pass extracts the latest year only. Multi-year history accumulates passively across
dk-financialsruns as more periods get filed; deep history requires walking olderoffentliggoerelserdocuments which is a deferred follow-up. - Concept coverage in
lib/cvr/financials.tsis intentionally minimal. Only revenue, operating result, pre-tax profit, net result, total assets, equity, and liabilities are mapped today. Operating costs and net financials are left null pending real fixtures from a Danish DCCAfsa:filer (Maersk's IFRS taxonomy doesn't directly expose them). - Operating costs and net financials are null on every DK row. Both DCCA
fsa:and IFRS-full encode these via dimensional facts that the current parser doesn't decompose. - CVR Distribution is plain HTTP, not HTTPS. Auth credentials cross the wire unencrypted. This is by design at
distribution.virk.dk— Erhvervsstyrelsen's published interface. Don't use these credentials anywhere else.
Gotchas
- Two coexisting taxonomies. Listed entities file under IFRS-full (
ifrs-full:Revenue); SMEs file under Danish DCCA (fsa:Revenue). The same numeric Financial fields are populated either way; the parser tries both. - Multiple XML files per filing. A
regnskabfiling typically contains 1 XHTML + 2 XML + 1 ZIP. One of the XMLs is the financial statements; the other is the audit report (onlyarr:*andcmn:*concepts, no money). The financials pipeline tries each XML in order and keeps the first one that yields a non-null extraction. - Filings download as gzip despite
Content-Type: text/xml. Node'sfetch()decompresses transparently; curl needs--compressed. - Embedded participants > separate role index. CVR doesn't have a "list-roles-by-company" endpoint. We harvest roles from the company document's
deltagerRelation[]on eachdk-companiesrun. - Maersk's revenue is in USD. Listed Danish entities reporting under IFRS often pick a non-DKK functional currency. The XBRL
<xbrli:unit>declares the currency; we propagate it toFinancial.currency. - Stale employee counts. Some
nyesteAarsbeskaeftigelseblocks point at filings several years old. The "year" field on the block reveals freshness.