Why switch?
- No daily quota: cvrapi.dk gives 50 free lookups per IP per day and then answers
QUOTA_EXCEEDED. apicvr.dk has no fixed quota, just fair use. - No User-Agent requirement: you won't get
INVALID_UAfor forgetting to set one. - More data: owners with ownership share and voting rights, management and fully liable participants.
- More searches: email, email domain, address and typo-tolerant name search.
The URLs
# cvrapi.dk
https://cvrapi.dk/api?search=24256790&country=dk
https://cvrapi.dk/api?name=novo%20nordisk&country=dk
https://cvrapi.dk/api?phone=44448888&country=dk
# apicvr.dk
https://apicvr.dk/api/v1/24256790
https://apicvr.dk/api/v1/search/company/novo%20nordisk?limit=1
https://apicvr.dk/api/v1/search/phone/44448888Note that name and phone search on apicvr.dk return a list. Take the first item if you only want one hit like on cvrapi.dk.
Fields with the same name
vat, name, address, zipcode, city, cityname, addressco, protected, phone, email, fax, startdate, enddate, employees, industrycode, industrydesc, companycode, companydesc and version.
Fields that differ
| cvrapi.dk | apicvr.dk | Note |
|---|---|---|
productionunits | p_units | The P-number is in p_number. |
owners | ejere | Each owner also has type, cvr, ownership share and voting rights. |
creditbankrupt | bankrupt | Same meaning. |
creditstatus, creditstartdate | status | E.g. NORMAL, UNDERKONKURS or OPHØRT. |
zipcode (string) | zipcode (number) | Cast to string if your code expects it. |
| – | direktion, fuldt_ansvarlige | New fields. |
Errors
cvrapi.dk puts the error in an error field. apicvr.dk uses HTTP status codes: 404 when the company doesn't exist and 502 when the CVR register doesn't answer. The code is in detail.error.
Drop-in function in PHP
The function fetches from apicvr.dk and returns the shape your existing code expects, so you can switch without touching the rest of your code.
<?php
// Drop-in: returns the same shape your cvrapi.dk code expects.
function cvr_lookup(string $cvr): ?array
{
$ch = curl_init("https://apicvr.dk/api/v1/" . urlencode($cvr));
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status === 404) {
return ['error' => 'NOT_FOUND'];
}
if ($status !== 200) {
return ['error' => 'INTERNAL_ERROR'];
}
$c = json_decode($body, true);
// Rename the fields that are named differently on apicvr.dk
$c['productionunits'] = $c['p_units'];
$c['owners'] = array_map(fn ($o) => ['name' => $o['name']], $c['ejere']);
$c['creditbankrupt'] = $c['bankrupt'];
$c['zipcode'] = (string) $c['zipcode'];
return $c;
}
$company = cvr_lookup('24256790');
echo $company['name'] . ', ' . $company['zipcode'] . ' ' . $company['city'];Drop-in function in JavaScript
async function cvrLookup(cvr) {
const res = await fetch(`https://apicvr.dk/api/v1/${cvr}`);
if (res.status === 404) return { error: "NOT_FOUND" };
if (!res.ok) return { error: "INTERNAL_ERROR" };
const c = await res.json();
return {
...c,
productionunits: c.p_units,
owners: c.ejere.map((o) => ({ name: o.name })),
creditbankrupt: c.bankrupt,
zipcode: String(c.zipcode),
};
}What apicvr.dk doesn't do
- Norwegian companies (
country=no). - XML. The response is always JSON.
- Direct lookup by P-number (
produ). Look up the company and find the unit inp_units.
Frequently asked questions
Do I need to set a User-Agent?
No, it's not required. You're welcome to send one that describes your project.
Can I keep my cvrapi.dk code?
Yes, if you use the drop-in function above. It renames the fields so the rest of your code works unchanged.
How many lookups can I make?
There's no fixed daily quota. Keep a reasonable pace, and email [email protected] if you need sustained heavy traffic.