The two endpoints
https://apicvr.dk/api/v1/search/fuzzy/{name}finds companies even with typos. Perfect for autocomplete.https://apicvr.dk/api/v1/{cvr}fetches the full profile once the user has picked a company or typed a CVR number.
GET https://apicvr.dk/api/v1/search/fuzzy/[company name]
→ [{ "name": "...", "cvr_number": 12345678, "industrycode": ..., "industrytext": "..." }, ...]
1. The autocomplete component
The component waits 300 ms after the last keystroke (debounce) and cancels stale requests with AbortController, so responses never arrive out of order.
import { useEffect, useState } from "react";
const API = "https://apicvr.dk/api/v1";
export function CompanyAutocomplete({ onSelect }) {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
useEffect(() => {
const q = query.trim();
if (q.length < 2) {
setResults([]);
return;
}
const controller = new AbortController();
const timer = setTimeout(async () => {
try {
const res = await fetch(`${API}/search/fuzzy/${encodeURIComponent(q)}`, {
signal: controller.signal,
});
if (res.ok) setResults((await res.json()).slice(0, 8));
} catch (err) {
if (err.name !== "AbortError") setResults([]);
}
}, 300); // debounce: wait until the user pauses typing
return () => {
clearTimeout(timer);
controller.abort();
};
}, [query]);
return (
<div className="company-autocomplete">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Company name"
autoComplete="off"
/>
{results.length > 0 && (
<ul role="listbox">
{results.map((c) => (
<li key={c.cvr_number} role="option" aria-selected="false">
<button type="button" onClick={() => { onSelect(c.cvr_number); setResults([]); }}>
{c.name} <small>CVR {c.cvr_number} · {c.industrytext}</small>
</button>
</li>
))}
</ul>
)}
</div>
);
}
2. The form that gets filled in
The form looks up the company when the user picks from the list, or when the CVR field contains 8 digits.
import { useState } from "react";
import { CompanyAutocomplete } from "./CompanyAutocomplete";
const API = "https://apicvr.dk/api/v1";
const EMPTY = { cvr: "", name: "", address: "", zipcode: "", city: "" };
export function CompanyForm() {
const [form, setForm] = useState(EMPTY);
const [error, setError] = useState(null);
async function fillFromCvr(cvr) {
setError(null);
const res = await fetch(`${API}/${cvr}`);
if (!res.ok) {
setError(res.status === 404 ? "Unknown CVR number" : "Lookup failed");
return;
}
const c = await res.json();
setForm({
cvr: String(c.vat),
name: c.name ?? "",
address: c.address ?? "",
zipcode: c.zipcode ? String(c.zipcode) : "",
city: c.city ?? "",
});
}
const field = (key) => ({
value: form[key],
onChange: (e) => {
const value = e.target.value;
setForm((f) => ({ ...f, [key]: value }));
if (key === "cvr" && /^\d{8}$/.test(value)) fillFromCvr(value);
},
});
return (
<form>
<CompanyAutocomplete onSelect={fillFromCvr} />
<input {...field("cvr")} placeholder="CVR" inputMode="numeric" maxLength={8} />
<input {...field("name")} placeholder="Name" />
<input {...field("address")} placeholder="Address" />
<input {...field("zipcode")} placeholder="Postcode" />
<input {...field("city")} placeholder="City" />
{error && <p role="alert">{error}</p>}
</form>
);
}
Access-Control-Allow-Origin: *, so you can call it straight from the browser in Vite, Next.js, Create React App and similar, with no proxy and no key.Storing data for marketing?
Then check the protected field in the full lookup. See marketing protection.
Frequently asked questions
Does it work with Next.js?
Yes. The components are ordinary client components. In the Next.js App Router, start the file with "use client".
How many requests can I make?
Fair-use rate limits apply. With a 300 ms debounce, the autocomplete only sends one request when the user pauses typing.