How to validate a Danish CVR number

A valid CVR number has to pass two checks: it must have the right format with a correct check digit, and it must belong to a company that exists and is still active. You do the first locally and the second with one API call.

1. Check the format with modulus 11

A CVR number has 8 digits and doesn't start with 0. Multiply each digit by the weights 2, 7, 6, 5, 4, 3, 2, 1 and add up the results. The sum must be divisible by 11.

For 10403782: 1·2 + 0·7 + 4·6 + 0·5 + 3·4 + 7·3 + 8·2 + 2·1 = 77, and 77 / 11 = 7, so the number is valid.

python
WEIGHTS = (2, 7, 6, 5, 4, 3, 2, 1)

def is_valid_cvr(cvr: str) -> bool:
    cvr = cvr.replace(" ", "")
    if len(cvr) != 8 or not cvr.isdigit() or cvr[0] == "0":
        return False
    return sum(int(d) * w for d, w in zip(cvr, WEIGHTS)) % 11 == 0

is_valid_cvr("10403782")  # True  (Vestas Wind Systems A/S)
is_valid_cvr("12345678")  # False

2. Confirm the company exists

A number can have a correct check digit without being assigned. Look it up:

bash
curl https://apicvr.dk/api/v1/10403782
json
{
  "vat": 10403782,
  "name": "VESTAS WIND SYSTEMS A/S",
  "status": "NORMAL",
  "startdate": "...",
  "enddate": null,
  "bankrupt": false,
  "companydesc": "Aktieselskab",
  ...
}

If the number doesn't exist, the API responds with HTTP 404:

json
{"detail": {"error": "NOT_FOUND", "status": 404, "message": null}}

3. Check that it's active

A company can exist in CVR even though it has ceased or gone bankrupt. Look at enddate (set once the company has ceased), bankrupt and status. Here's the whole flow in JavaScript:

javascript
const WEIGHTS = [2, 7, 6, 5, 4, 3, 2, 1];

function isValidCvr(input) {
  const cvr = String(input).replace(/\s/g, "");
  if (!/^[1-9]\d{7}$/.test(cvr)) return false;
  const sum = [...cvr].reduce((acc, d, i) => acc + Number(d) * WEIGHTS[i], 0);
  return sum % 11 === 0;
}

async function checkCvr(cvr) {
  if (!isValidCvr(cvr)) return { valid: false, reason: "checksum" };
  const res = await fetch(`https://apicvr.dk/api/v1/${cvr}`);
  if (res.status === 404) return { valid: false, reason: "not_found" };
  const company = await res.json();
  const active = !company.enddate && !company.bankrupt;
  return { valid: true, active, name: company.name };
}

Tips

Frequently asked questions

Is every CVR number that passes modulus 11 assigned?

No. Modulus 11 catches typos, but only a CVR lookup can confirm that the number belongs to a company.

Is a CVR number the same as a Danish VAT number?

For Danish companies, the VAT number is usually the CVR number prefixed with DK, e.g. DK10403782.

What does the API return for an invalid format?

If the number contains anything but digits, the API responds with HTTP 422. If it doesn't exist, it responds with HTTP 404 and the error code NOT_FOUND.