Dokumentation

One endpoint, one token, JSON or XML. Sourced from Erhvervsstyrelsen and Brønnøysundregistrene directly, never proxied.

This reference is in English. The rest of the site speaks Danish, Norwegian and English.

Getting started

  1. 1

    Create an account

    Pick a plan on the pricing page, or start on the free tier. Signing up needs an email address and a password — no card.

  2. 2

    Verify your email address

    We send you a link. Your API token is issued when you click it, not when you register: an unverified address never gets a working key.

  3. 3

    Copy your token

    It is shown exactly once, on the dashboard, immediately after verification. We store only a SHA-256 hash, so nobody — including us — can recover it. Lost it? Roll a new one.

  4. 4

    Call the API

    Send the token as a Bearer credential on every request. There is no anonymous tier.

  5. 5

    Upgrade when you need more

    Add your billing country and accept the data processing agreement, then pay by card. Your quota rises as soon as the payment settles.

Authentication

Every request carries your token in the Authorization header. A request without one is rejected before it touches the register, and before it costs you any quota.

curl 'https://cvrfinder.webkonsulenter.dk/api?vat=12345678&country=dk' \
  -H 'Authorization: Bearer cvr_your_token_here'

The endpoint

GET https://cvrfinder.webkonsulenter.dk/api

There is one. It returns a single company. country is required, and exactly one search parameter must be supplied.

Query parameters

Parameter Required Meaning
country yes dk or no. Which register to search.
vat one of these Registration number. 8 digits in Denmark, 9 in Norway.
name one of these Company name. Fuzzy, accent-insensitive.
produ one of these Production-unit number.
phone one of these Phone number, 8 digits.
ean one of these E-invoicing identifier (Peppol / EAN). Resolves to the registered company.
address one of these Street address. Returns the best-matching company at that address.
search one of these Any of the above. The type is detected from the value.
include no Comma-separated: owners, production_units, einvoicing.
format no json (default) or xml.

Registration numbers are strings.

A leading zero is significant. 01234567 and 1234567 are different companies. Never store one as an integer.

How search decides

If you pass search instead of a named parameter, the type is inferred: a number of the register's own length is a registration number, ten digits in Denmark is a production unit, eight digits is a phone number, and anything else is a name. When you know what you are looking for, name it — the inference is a convenience, not a contract.

Typed endpoints

Every search type is also its own endpoint, taking the term as q. Same response, same country/include/format parameters, but nothing is ever inferred — /api/name?q=12345678 searches names, not registration numbers.

GET https://cvrfinder.webkonsulenter.dk/api/vat?q=12345678&country=dk
GET https://cvrfinder.webkonsulenter.dk/api/name?q=b%C3%B8gh&country=dk
GET https://cvrfinder.webkonsulenter.dk/api/produ?q=1234567890&country=dk
GET https://cvrfinder.webkonsulenter.dk/api/phone?q=33112233&country=dk
GET https://cvrfinder.webkonsulenter.dk/api/ean?q=5798009811578&country=dk
GET https://cvrfinder.webkonsulenter.dk/api/address?q=Bredgade+12,+8000+Aarhus&country=dk

Includes

Related records are opt-in, comma-separated.

Value Adds Plan
production_units A production_units array: branches and sites. All plans
einvoicing An einvoicing object: whether the company is registered in the Peppol Directory to receive e-invoices, and the document types it accepts. All plans
owners A participants array: directors, board and beneficial owners. Business

Note the asymmetry: you ask for owners and receive a key named participants, because a director is not an owner. Asking for it on a plan that does not carry it returns UPGRADE_REQUIRED, never a silently empty list.

One caveat on einvoicing: publishing to the Peppol Directory is optional, so registered: false means "not found in the Peppol Directory", not "cannot be invoiced electronically".

We never return a date of birth.

Both registries publish one for every person. We do not ingest it, do not store it, and cannot return it.

The response

{
  "registration_number": "810034882",
  "country": "no",
  "name": "SANDNES ELEKTRISKE AS",
  "status": "aktiv",
  "legal_form": { "code": "AS", "text": "Aksjeselskap" },
  "industry":   { "code": "43.210", "text": "Elektrisk installasjonsarbeid" },
  "address": {
    "street": "Strandgata 21",
    "postal_code": "4307",
    "city": "SANDNES",
    "municipality_code": "1108",
    "municipality_name": "SANDNES",
    "line": "Strandgata 21, 4307 SANDNES"
  },
  "email": "[email protected]",
  "phone": "51 68 57 00",
  "website": null,
  "employees": 10,
  "started_at": "1995-02-19",
  "ended_at": null,
  "bankrupt": false,
  "ad_protected": true,
  "ad_protection_known": false,
  "synced_at": "2026-07-10T06:50:18+00:00"
}

Add format=xml for the same document as XML, rooted at <company>.

Advertising protection

Two fields, always both present, and the second is the one that matters.

ad_protected — may this company be contacted for marketing?

ad_protection_known — do we actually know?

Read both, or you will break the law.

Denmark publishes a reklamebeskyttelse flag. Norway does not: its opt-out lives in a separate reservation register we do not consume. So every Norwegian company comes back as ad_protected: true, ad_protection_known: false — protected, because we do not know otherwise. Treating an unknown as permission is the one mistake this field exists to prevent.

Errors

Every failure is a JSON object with error and message. Switch on error; the message is for humans and may change.

{ "error": "NOT_FOUND", "message": "No company matched that query." }
error HTTP When
MISSING_TOKEN 401 No Authorization header.
INVALID_TOKEN 401 The token is unknown or has been revoked.
UPGRADE_REQUIRED 403 This plan may not request include=owners.
NOT_FOUND 404 Nothing in the register matched.
INVALID_REQUEST 422 A parameter is missing or malformed.
INVALID_VAT 422 The registration number has the wrong shape for that country.
QUOTA_EXCEEDED 429 The daily quota is spent.
MONTHLY_QUOTA_EXCEEDED 429 The monthly quota is spent.

Quotas

Counted per token. A daily quota resets at midnight; a monthly one on the first.

Plan Per day Per month
Gratis 50
Hobby 2.000
Business 6.000

A dash means no cap of that kind. Every response on a plan with a daily cap carries X-RateLimit-Limit and X-RateLimit-Remaining. When the quota is spent you get 429; back off and retry after the reset.

How fresh is this?

We ingest the registers into our own database rather than proxying them, so we are never slower or less available than the upstream and can search names the upstream index cannot. Every response carries synced_at: the moment that specific company was last read from its register.