Adressefelt med autocomplete i din checkout
Et adressefelt, der foreslår rigtige danske adresser, fjerner de tastefejl, der senere bliver til returpakker og manuelle rettelser. Her er hele vejen: et debounced felt i frontend, et proxy-endpoint der holder nøglen på serveren, og ét kald der giver dig både visningsteksten og de felter, formularen skal udfyldes med.
Ét kald — ikke to
De fleste adresse-felter slår forslaget op to gange: først for at vise listen, og så igen for at hente de strukturerede felter, når brugeren har valgt. /v1/adresse/search returnerer begge dele på én gang, så valget kan udfylde formularen med det samme:
{ "results": [ { "betegnelse": "Rentemestervej 10, 2400 København NV", "adgangsadresse_id": "0a3f507a-e17e-32b8-e044-0003ba298018", "bfe_nummer": 6022755, "vejnavn": "Rentemestervej", "husnr": "10", "postnr": "2400", "postnrnavn": "København NV", "kommunekode": "0101", "koordinat": { "lon": 12.5347335, "lat": 55.7050242 } } ] }
Bemærk adgangsadresse_id og bfe_nummer. Gem dem sammen med ordren i stedet for kun den fritekst, kunden så: id'et er stabilt, og bfe_nummer er nøglen til resten af ejendommen, hvis du senere får brug for den.
Frontend: felt med forslag
De to detaljer, der afgør, om feltet føles rigtigt: debounce, så hvert tastetryk ikke bliver et kald — og afbrydelse af det forrige kald, så et langsomt svar ikke overskriver et nyere.
// Adressefelt med forslag. Debounce, afbryd det forrige kald, og // udfyld de øvrige felter ud fra det valgte forslag. const input = document.querySelector('#adresse'); const liste = document.querySelector('#forslag'); let inflight; let timer; input.addEventListener('input', () => { clearTimeout(timer); timer = setTimeout(async () => { const q = input.value.trim(); if (q.length < 2) return (liste.innerHTML = ''); // Afbryd det forrige kald, så et langsomt svar ikke overskriver et nyere. inflight?.abort(); inflight = new AbortController(); // Kald dit eget backend-endpoint — nøglen må aldrig ligge i browseren. const res = await fetch('/api/adresse?q=' + encodeURIComponent(q), { signal: inflight.signal, }); const { results } = await res.json(); liste.innerHTML = ''; for (const r of results) { const li = document.createElement('li'); li.textContent = r.betegnelse; li.addEventListener('click', () => { input.value = r.betegnelse; // Felterne kommer med i samme svar — ingen ekstra opslag. form.vejnavn.value = r.vejnavn ?? ''; form.husnr.value = r.husnr ?? ''; form.postnr.value = r.postnr ?? ''; form.by.value = r.postnrnavn ?? ''; // Gem nøglerne: adgangsadresse_id er stabil, bfe_nummer peger på ejendommen. form.adresse_id.value = r.adgangsadresse_id; liste.innerHTML = ''; }); liste.append(li); } }, 150); });
Backend: hold nøglen på serveren
En API-nøgle i browseren kan læses af enhver besøgende og bruges på din regning. Læg et lille proxy-endpoint i din egen backend — så bliver nøglen, hvor den hører til, og du kan cache og rate-limite, før kaldene rammer os.
// Proxy i din backend. Nøglen bliver på serveren, og du kan // cache + rate-limite, før kaldene rammer os. app.get('/api/adresse', async (req, res) => { const q = String(req.query.q ?? ''); if (q.length < 2) return res.json({ results: [] }); const upstream = await fetch( 'https://api.grundfast.dk/v1/adresse/search?per_side=8&q=' + encodeURIComponent(q), { headers: { Authorization: 'Bearer ' + process.env.GRUNDFAST_KEY } }, ); if (!upstream.ok) return res.status(502).json({ results: [] }); res.set('Cache-Control', 'private, max-age=60'); res.json(await upstream.json()); });
De adresser, du allerede har
Et nyt felt retter kun fremtidige ordrer. Kartoteket bagud kan vaskes mod DAR i stedet for at blive tastet om — du får rangerede kandidater med A/B/C-konfidensbånd og bfe_nummer, så det sikre kan rettes automatisk og resten sendes til et menneske.
# Har du allerede en liste med adresser — fx et gammelt kundekartotek — # så vask dem i stedet for at få dem tastet igen. curl -sS "https://api.grundfast.dk/v1/datavask/adgangsadresser?betegnelse=rentemestervej%2010%2C%202400%20kbh%20nv" \ -H "Authorization: Bearer $GRUNDFAST_KEY"
Byg det på en test-nøgle
En gf_test_-nøgle giver rigtige svar, faktureres aldrig og har 25.000 kald/md. Byg feltet færdigt, og skift nøgle når du går i produktion.
Se også
- Adresse-autocomplete i WooCommerceKlassisk checkout og checkout-blokken
- Adresse-autocomplete i ShopifyAddress Autocomplete API (Plus)
- Adresse-autocomplete-APIEndpointet bag feltet, i detaljer
- Datavask af kundelisterVask de adresser, du allerede har
- Adresse-API (DAR)Opslag på UUID og broen til BFE
- DAWA lukker 1. oktober 2026Migrér en eksisterende adresse-integration
Ofte stillede spørgsmål
Skal jeg bruge /v1/autocomplete eller /v1/adresse/search?
Til et checkout-felt: /v1/adresse/search. Den returnerer de strukturerede felter (vejnavn, husnr, postnr, postnrnavn, kommunekode, koordinat og bfe_nummer) direkte i svaret, så du kan udfylde formularen uden et ekstra opslag. /v1/autocomplete giver tekst + vaerdi og er tænkt som DAWA-kompatibel drop-in, hvor du selv slår værdien op bagefter.
Må API-nøglen ligge i frontend-koden?
Nej. En nøgle i browseren kan læses af enhver besøgende og bruges på din regning. Læg et lille proxy-endpoint i din egen backend, som sætter Authorization-headeren — så kan du samtidig cache og rate-limite, før kaldene rammer os.
Hvor mange kald bliver et checkout-felt til?
Med 150 ms debounce og afbrydelse af det forrige kald lander en typisk adresse-indtastning på nogle få kald. Free-planen er 5.000 kald/md, hvilket rækker til et mindre checkout-flow; derover fortsætter betalte planer automatisk til 1 kr/1.000 ekstra kald.
Kan jeg teste uden at betale?
Ja. En gf_test_-nøgle giver rigtige svar, faktureres aldrig og har 25.000 kald/md. Byg feltet færdigt på test-nøglen, og skift til en gf_live_-nøgle, når du går i produktion.
Hvad gør jeg med de adresser, jeg allerede har liggende?
Vask dem mod DAR med /v1/datavask/adgangsadresser. Du får rangerede kandidater med A/B/C-konfidensbånd og bfe_nummer, så du kan rette de sikre automatisk og lade et menneske se på resten. Har du mange, tager :batch-varianten dem i ét kald.