wozapidocs

Aan de slag

Van niets naar een WOZ-waarde in je eigen code. Reken op een paar minuten, waarvan het meeste het aanmaken van een account is.

1. Een sleutel aanmaken

Maak een gratis account op woz-api.nl en genereer een sleutel onder API sleutels. Je krijgt 10 credits, genoeg om de integratie af te maken voordat je iets betaalt.

Wil je eerst zien wat je terugkrijgt zonder account, dan staat er een live demo met een echte respons op de homepage, en de volledige OpenAPI-definitie in Swagger.

2. De eerste call

De sleutel gaat mee als header. Twee vormen werken:

HeaderWaarde
X-Api-Key{jouw sleutel}
AuthorizationApiKey {jouw sleutel}
curl "https://woz-api.nl/Api/Adres?adres=Spuistraat%2036C,%201012TT%20Amsterdam" \
  -H "X-Api-Key: JOUW_API_KEY"
Url-encodeer het adres. Een adres bevat spaties en vaak een komma. Zonder encoding kapt je shell of HTTP-client de parameter af bij de eerste spatie en krijg je een 400 terug op een adres dat gewoon bestaat.

3. De respons lezen

De waarden staan in de reeks woz, met per element een peildatum en een vastgesteldeWaarde. Een adres heeft doorgaans meerdere peildata. Wil je de meest recente waarde, sorteer dan op peildatum in plaats van blind het eerste element te pakken: de volgorde is geen contract.

import json, urllib.parse, urllib.request

url = "https://woz-api.nl/Api/Adres?" + urllib.parse.urlencode(
    {"adres": "Spuistraat 36C, 1012TT Amsterdam"})
verzoek = urllib.request.Request(url, headers={"X-Api-Key": "JOUW_API_KEY"})

with urllib.request.urlopen(verzoek) as antwoord:
    data = json.load(antwoord)

if data["wozStatus"] != "gevonden":
    print("Geen openbare WOZ-waarde:", data["wozStatus"])
else:
    recentste = max(data["woz"], key=lambda w: w["peildatum"])
    print(recentste["peildatum"], recentste["vastgesteldeWaarde"])

Niet elk adres heeft een openbare WOZ-waarde: alleen van woningen is die openbaar. Bij een kantoor, kerk of hotel krijg je een 200 met een lege woz en huidigeWaarde: null, en zegt wozStatus waarom: geen_woning als het pand volgens de BAG geen woonfunctie heeft, anders geen_waarde. Dat is een antwoord en geen storing. Probeer het niet opnieuw, het blijft hetzelfde, en het kost geen credit.

Voor Python, Node.js en .NET staat er een client die dit voor je doet, zie Clients.

4. Een lijst adressen

Een Excel of CSV met adressen vul je zonder code aan op WOZ-waarden bij een lijst adressen in Excel: een gratis proef met 5 adressen, de prijs vooraf, en hetzelfde bestand terug. Met code, ook als een AI-assistent het script schrijft, doe je per adres een aanroep. Een adres zonder WOZ-waarde kost geen credit, en een fout ook niet.

# pip install requests openpyxl
import time
import openpyxl
import requests

API = "https://woz-api.nl/Api/Adres"
SLEUTEL = "woz_..."            # je API-sleutel; altijd in de header, nooit in een URL
BESTAND = "adressen.xlsx"      # kolom A: straat en huisnummer, B: postcode, C: plaats

wb = openpyxl.load_workbook(BESTAND)
ws = wb.active
ws.cell(row=1, column=4, value="WOZ-waarde")
ws.cell(row=1, column=5, value="Peildatum")
ws.cell(row=1, column=6, value="Status")

s = requests.Session()
s.headers["X-Api-Key"] = SLEUTEL

def vraag(params):
    # Bij een storing (502, 503) of te snel (429) zegt Retry-After hoe lang je wacht. Sla het
    # adres niet over: een storing duurt meestal minuten, en opnieuw proberen kost niets.
    for poging in range(6):
        r = s.get(API, params=params, timeout=30)
        if r.status_code not in (429, 502, 503):
            return r
        time.sleep(int(r.headers.get("Retry-After", 10 * (poging + 1))))
    return r

for rij in range(2, ws.max_row + 1):
    straat, postcode, plaats = (ws.cell(rij, k).value for k in (1, 2, 3))
    if not straat or not (postcode or plaats):
        ws.cell(rij, 6, "overgeslagen: geen adres of geen postcode en plaats")
        continue
    adres = f"{straat}, {postcode or ''} {plaats or ''}".strip()
    r = vraag({"adres": adres})
    if r.status_code == 200:
        d = r.json()
        huidig = d.get("huidigeWaarde")
        if huidig:
            ws.cell(rij, 4, huidig["vastgesteldeWaarde"])
            ws.cell(rij, 5, huidig["peildatum"])
        ws.cell(rij, 6, d.get("wozStatus"))   # gevonden, geen_woning of geen_waarde
    elif r.status_code == 402:
        print("Saldo op. Opwaarderen:", r.json().get("detail", {}).get("opwaarderen"))
        break
    else:
        ws.cell(rij, 6, r.json().get("code", str(r.status_code)))   # AddressNotFound, of na een lange storing UpstreamUnavailable: draai die rijen later opnieuw
    time.sleep(0.2)

wb.save("adressen-met-woz.xlsx")

Elke rij heeft straat en huisnummer samen in een cel nodig, plus een postcode of een plaats; zonder die twee nemen we de eerste treffer in het land. Hetzelfde recept staat in de handleiding op woz-api.nl.

Wat er mis kan gaan

StatusBetekenisWat te doen
400Adres onleesbaar of parameter ontbreektControleer de url-encoding en of adres is meegestuurd
401Sleutel ontbreekt of is ongeldigHeader X-Api-Key controleren; een ingetrokken sleutel geeft ook 401
402Credits op (code InsufficientCredits)Opwaarderen; detail.opwaarderen geeft de link. Vang dit apart af, anders valt een batch halverwege om
401 SleutelOntbreektGeen sleutel meegestuurdVraag een gratis proefsleutel aan met POST /Api/Proef, of maak een account aan
402 ProefVerbruiktDe gratis proef is opGeef het e-mailadres van de gebruiker (POST /Api/Proef/Email) of ga verder met credits
404Adres niet gevonden (AddressNotFound)Controleer huisnummer, toevoeging en postcode of plaats
200, lege wozHet adres bestaat, maar heeft geen openbare WOZ-waarde (wozStatus is geen_woning of geen_waarde)Niet opnieuw proberen; kost geen credit
502 503Een bron achter ons hapert (PdokLookupFailed, UpstreamUnavailable)Opnieuw proberen met een oplopende wachttijd; dit kost geen credit

Een fout heeft altijd dezelfde vorm, zodat je er één handler voor kunt schrijven:

{
  "fout": "korte omschrijving",
  "code": "machineleesbare code",
  "status": 400,
  "detail": "wat er precies misging",
  "traceId": "id voor support"
}

Dat geldt ook voor een 400 door modelvalidatie; detail bevat dan per veld de meldingen. Match in code op code, niet op de tekst in fout: die tekst kan wijzigen, de codes niet. Alle codes staan in de referentie.

Credits en herhaalverkeer

1 credit is 1 uniek adres. Hetzelfde adres binnen 7 dagen opnieuw opvragen kost geen extra credit; dat is een kortingsregel en geen cache, de request wordt echt uitgevoerd. Een opvraging die op een fout eindigt kost niets, en een antwoord zonder WOZ-waarden (een lege woz) ook niet. Je saldo staat in de responseheader X-Credits-Remaining en is ook op te vragen via GET /Api/Credits.

Volgende stap

De referentie heeft alle endpoints en velden. Werk je liever zonder code, dan vult de Excel-wizard een bestand met adressen in één keer aan.