orakel
Docs navigation

Canonical

CVR (Denmark)

Danish company register — Erhvervsstyrelsen CVR Distribution: companies, roles, and plain-XBRL annual financials.

Updated 2026-04-27

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-companies runs weekly Mondays at 03:30 UTC against the seed list. dk-financials runs 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 under Vrvirksomhed.deltagerRelation[] and Vrvirksomhed.penheder[]. Searched via term query on Vrvirksomhed.cvrNummer.
  • offentliggoerelser — one document per filing (annual reports, change notices, financial statements). Each filing has a dokumenter[] 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"
  • REVISIONroleTypeCode = "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):

  1. Query CVR for the company's filings, sorted newest-first.
  2. For each application/xml document URL in the latest filing's dokumenter[], download the XBRL instance.
  3. Parse it with lib/cvr/xbrl.ts — extracts contexts, units, and numeric facts across both DCCA fsa: and IFRS-full taxonomies.
  4. Pick the latest fiscal-year duration period; map concepts to Financial columns:
    • Revenuefsa:Revenue / ifrs-full:Revenue
    • ProfitLossFromOperatingActivitiesfsa:ProfitLossFromOrdinaryOperatingActivities / ifrs-full:ProfitLossFromOperatingActivities
    • ProfitLossBeforeTaxfsa:ProfitLossFromOrdinaryActivitiesBeforeTax / ifrs-full:ProfitLossBeforeTax
    • ProfitLossfsa:ProfitLoss / ifrs-full:ProfitLoss
    • Assetsfsa:Assets / ifrs-full:Assets
    • Equityfsa:Equity / ifrs-full:Equity
    • Liabilitiesfsa:Liabilities / ifrs-full:Liabilities
  5. Upsert into Financial with country = "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-financials runs as more periods get filed; deep history requires walking older offentliggoerelser documents which is a deferred follow-up.
  • Concept coverage in lib/cvr/financials.ts is 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 DCCA fsa: 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 regnskab filing typically contains 1 XHTML + 2 XML + 1 ZIP. One of the XMLs is the financial statements; the other is the audit report (only arr:* and cmn:* 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's fetch() 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 each dk-companies run.
  • 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 to Financial.currency.
  • Stale employee counts. Some nyesteAarsbeskaeftigelse blocks point at filings several years old. The "year" field on the block reveals freshness.