Dokumentation der Bodenrichtwert-API

Alles, was Sie für die Anbindung brauchen. Die Schnittstelle ist für jede Programmiersprache gleich: dieselbe Adresse, derselbe Schlüssel, dieselbe JSON-Antwort. Die Beispiele zeigen nur, wie man den Aufruf in der jeweiligen Sprache schreibt.

Version 1.1.1 Basis-URL https://www.online-bodenrichtwert.de/api/v1 openapi.json Testschlüssel holen

Schnellstart

Testschlüssel auf der API-Seite anfordern (50 Abfragen in 14 Tagen, kommt sofort per E-Mail), dann:

curl -G https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert \
  -H "Authorization: Bearer brw_test_••••••" \
  --data-urlencode "adresse=Musterstraße 1, 12345 Musterstadt"
$url = 'https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?' . http_build_query([
    'adresse' => 'Musterstraße 1, 12345 Musterstadt',
]);
$kontext = stream_context_create(['http' => [
    'header' => 'Authorization: Bearer brw_test_••••••',
]]);
$daten = json_decode(file_get_contents($url, false, $kontext), true);
import requests

antwort = requests.get(
    "https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert",
    params={"adresse": "Musterstraße 1, 12345 Musterstadt"},
    headers={"Authorization": "Bearer brw_test_••••••"},
)
daten = antwort.json()
const url = new URL("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert");
url.searchParams.set("adresse", "Musterstraße 1, 12345 Musterstadt");

const antwort = await fetch(url, {
  headers: { Authorization: "Bearer brw_test_••••••" },
});
const daten = await antwort.json();
var abfrage = "adresse=" + URLEncoder.encode("Musterstraße 1, 12345 Musterstadt", StandardCharsets.UTF_8);
var anfrage = HttpRequest.newBuilder()
    .uri(URI.create("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?" + abfrage))
    .header("Authorization", "Bearer brw_test_••••••")
    .build();
var antwort = HttpClient.newHttpClient()
    .send(anfrage, HttpResponse.BodyHandlers.ofString());

Authentifizierung

Jede Anfrage trägt den Schlüssel im Kopf Authorization: Bearer <Schlüssel>, alternativ X-Api-Key: <Schlüssel>. Testschlüssel beginnen mit brw_test_, gebuchte mit brw_live_. In der Adresszeile nimmt die API keinen Schlüssel an — dort landete er in Protokollen und Verläufen.

Wir speichern Schlüssel nur verschlüsselt. Geht einer verloren, fordern Sie einen neuen an; der alte wird damit ungültig.

Abfragen

Ein Endpunkt, drei Wege zum Ort: GET https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert. Pro Anfrage genau eine Abfrageart.

Per Adresse

Die amtliche Adresssuche findet den Punkt am Gebäude. Wie genau, steht in genauigkeit.

curl -G https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert \
  -H "Authorization: Bearer brw_test_••••••" \
  --data-urlencode "adresse=Musterstraße 1, 12345 Musterstadt"
$url = 'https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?' . http_build_query([
    'adresse' => 'Musterstraße 1, 12345 Musterstadt',
]);
$kontext = stream_context_create(['http' => [
    'header' => 'Authorization: Bearer brw_test_••••••',
]]);
$daten = json_decode(file_get_contents($url, false, $kontext), true);
import requests

antwort = requests.get(
    "https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert",
    params={"adresse": "Musterstraße 1, 12345 Musterstadt"},
    headers={"Authorization": "Bearer brw_test_••••••"},
)
daten = antwort.json()
const url = new URL("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert");
url.searchParams.set("adresse", "Musterstraße 1, 12345 Musterstadt");

const antwort = await fetch(url, {
  headers: { Authorization: "Bearer brw_test_••••••" },
});
const daten = await antwort.json();
var abfrage = "adresse=" + URLEncoder.encode("Musterstraße 1, 12345 Musterstadt", StandardCharsets.UTF_8);
var anfrage = HttpRequest.newBuilder()
    .uri(URI.create("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?" + abfrage))
    .header("Authorization", "Bearer brw_test_••••••")
    .build();
var antwort = HttpClient.newHttpClient()
    .send(anfrage, HttpResponse.BodyHandlers.ofString());

Per Koordinate

Für Karten und eigene Geokodierung: Breite und Länge in WGS84.

curl -G https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert \
  -H "Authorization: Bearer brw_test_••••••" \
  --data-urlencode "lat=50.9375" \
  --data-urlencode "lng=6.9603"
$url = 'https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?' . http_build_query([
    'lat' => '50.9375',
    'lng' => '6.9603',
]);
$kontext = stream_context_create(['http' => [
    'header' => 'Authorization: Bearer brw_test_••••••',
]]);
$daten = json_decode(file_get_contents($url, false, $kontext), true);
import requests

antwort = requests.get(
    "https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert",
    params={"lat": "50.9375", "lng": "6.9603"},
    headers={"Authorization": "Bearer brw_test_••••••"},
)
daten = antwort.json()
const url = new URL("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert");
url.searchParams.set("lat", "50.9375");
url.searchParams.set("lng", "6.9603");

const antwort = await fetch(url, {
  headers: { Authorization: "Bearer brw_test_••••••" },
});
const daten = await antwort.json();
var abfrage = "lat=" + URLEncoder.encode("50.9375", StandardCharsets.UTF_8)
    + "&" + "lng=" + URLEncoder.encode("6.9603", StandardCharsets.UTF_8);
var anfrage = HttpRequest.newBuilder()
    .uri(URI.create("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?" + abfrage))
    .header("Authorization", "Bearer brw_test_••••••")
    .build();
var antwort = HttpClient.newHttpClient()
    .send(anfrage, HttpResponse.BodyHandlers.ofString());

Per Flurstück

Mit amtlicher Fläche aus dem Kataster und dem Bodenwert, wenn das Flurstück in genau einer Zone liegt.

curl -G https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert \
  -H "Authorization: Bearer brw_test_••••••" \
  --data-urlencode "land=NW" \
  --data-urlencode "gemarkung=Niederwermelskirchen" \
  --data-urlencode "flur=26" \
  --data-urlencode "flurstueck=86"
$url = 'https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?' . http_build_query([
    'land' => 'NW',
    'gemarkung' => 'Niederwermelskirchen',
    'flur' => '26',
    'flurstueck' => '86',
]);
$kontext = stream_context_create(['http' => [
    'header' => 'Authorization: Bearer brw_test_••••••',
]]);
$daten = json_decode(file_get_contents($url, false, $kontext), true);
import requests

antwort = requests.get(
    "https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert",
    params={"land": "NW", "gemarkung": "Niederwermelskirchen", "flur": "26", "flurstueck": "86"},
    headers={"Authorization": "Bearer brw_test_••••••"},
)
daten = antwort.json()
const url = new URL("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert");
url.searchParams.set("land", "NW");
url.searchParams.set("gemarkung", "Niederwermelskirchen");
url.searchParams.set("flur", "26");
url.searchParams.set("flurstueck", "86");

const antwort = await fetch(url, {
  headers: { Authorization: "Bearer brw_test_••••••" },
});
const daten = await antwort.json();
var abfrage = "land=" + URLEncoder.encode("NW", StandardCharsets.UTF_8)
    + "&" + "gemarkung=" + URLEncoder.encode("Niederwermelskirchen", StandardCharsets.UTF_8)
    + "&" + "flur=" + URLEncoder.encode("26", StandardCharsets.UTF_8)
    + "&" + "flurstueck=" + URLEncoder.encode("86", StandardCharsets.UTF_8);
var anfrage = HttpRequest.newBuilder()
    .uri(URI.create("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?" + abfrage))
    .header("Authorization", "Bearer brw_test_••••••")
    .build();
var antwort = HttpClient.newHttpClient()
    .send(anfrage, HttpResponse.BodyHandlers.ofString());

Früherer Stichtag

Etwa zum 01.01.2022 für die Grundsteuer — überall, wo das Land diesen Stichtag veröffentlicht hat.

curl -G https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert \
  -H "Authorization: Bearer brw_test_••••••" \
  --data-urlencode "adresse=Musterstraße 1, 12345 Musterstadt" \
  --data-urlencode "stichtag=2022-01-01"
$url = 'https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?' . http_build_query([
    'adresse' => 'Musterstraße 1, 12345 Musterstadt',
    'stichtag' => '2022-01-01',
]);
$kontext = stream_context_create(['http' => [
    'header' => 'Authorization: Bearer brw_test_••••••',
]]);
$daten = json_decode(file_get_contents($url, false, $kontext), true);
import requests

antwort = requests.get(
    "https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert",
    params={"adresse": "Musterstraße 1, 12345 Musterstadt", "stichtag": "2022-01-01"},
    headers={"Authorization": "Bearer brw_test_••••••"},
)
daten = antwort.json()
const url = new URL("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert");
url.searchParams.set("adresse", "Musterstraße 1, 12345 Musterstadt");
url.searchParams.set("stichtag", "2022-01-01");

const antwort = await fetch(url, {
  headers: { Authorization: "Bearer brw_test_••••••" },
});
const daten = await antwort.json();
var abfrage = "adresse=" + URLEncoder.encode("Musterstraße 1, 12345 Musterstadt", StandardCharsets.UTF_8)
    + "&" + "stichtag=" + URLEncoder.encode("2022-01-01", StandardCharsets.UTF_8);
var anfrage = HttpRequest.newBuilder()
    .uri(URI.create("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?" + abfrage))
    .header("Authorization", "Bearer brw_test_••••••")
    .build();
var antwort = HttpClient.newHttpClient()
    .send(anfrage, HttpResponse.BodyHandlers.ofString());

Mit Zonenumriss

umriss=1 liefert je Zone ihre Fläche als GeoJSON — zum Einzeichnen auf einer Karte.

curl -G https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert \
  -H "Authorization: Bearer brw_test_••••••" \
  --data-urlencode "lat=50.9375" \
  --data-urlencode "lng=6.9603" \
  --data-urlencode "umriss=1"
$url = 'https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?' . http_build_query([
    'lat' => '50.9375',
    'lng' => '6.9603',
    'umriss' => '1',
]);
$kontext = stream_context_create(['http' => [
    'header' => 'Authorization: Bearer brw_test_••••••',
]]);
$daten = json_decode(file_get_contents($url, false, $kontext), true);
import requests

antwort = requests.get(
    "https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert",
    params={"lat": "50.9375", "lng": "6.9603", "umriss": "1"},
    headers={"Authorization": "Bearer brw_test_••••••"},
)
daten = antwort.json()
const url = new URL("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert");
url.searchParams.set("lat", "50.9375");
url.searchParams.set("lng", "6.9603");
url.searchParams.set("umriss", "1");

const antwort = await fetch(url, {
  headers: { Authorization: "Bearer brw_test_••••••" },
});
const daten = await antwort.json();
var abfrage = "lat=" + URLEncoder.encode("50.9375", StandardCharsets.UTF_8)
    + "&" + "lng=" + URLEncoder.encode("6.9603", StandardCharsets.UTF_8)
    + "&" + "umriss=" + URLEncoder.encode("1", StandardCharsets.UTF_8);
var anfrage = HttpRequest.newBuilder()
    .uri(URI.create("https://www.online-bodenrichtwert.de/api/v1/bodenrichtwert?" + abfrage))
    .header("Authorization", "Bearer brw_test_••••••")
    .build();
var antwort = HttpClient.newHttpClient()
    .send(anfrage, HttpResponse.BodyHandlers.ofString());

Parameter

ParameterTypPflichtBedeutung
adresse string eine der drei Abfragearten Vollständige Anschrift in einer Zeile: Straße Hausnummer, PLZ Ort.
Musterstraße 1, 12345 Musterstadt
strasse, hausnummer, plz, ort string statt adresse Die Anschrift in Teilen — sicherer bei ungewöhnlichen Hausnummern.
strasse=Musterstraße&hausnummer=1&plz=12345&ort=Musterstadt
lat, lng number eine der drei Abfragearten Breite und Länge in Dezimalgrad (WGS84), innerhalb Deutschlands.
lat=50.9375&lng=6.9603
land, gemarkung, flur, flurstueck string eine der drei Abfragearten (flur optional) Länderkürzel (z. B. NW), Gemarkung als Name oder Nummer (mit oder ohne Landeskennung, etwa 4936 oder 054936), Flur und Flurstücksnummer (Zähler/Nenner, z. B. 123/4). Liefert zusätzlich die amtliche Fläche und — liegt das Flurstück in genau einer Zone — den Bodenwert.
land=NW&gemarkung=Niederwermelskirchen&flur=26&flurstueck=86
stichtag date optional Früherer Stichtag im Format JJJJ-MM-TT. Ohne Angabe gilt der neueste an dieser Stelle.
2022-01-01
umriss boolean optional Mit 1 kommt je Zone ihr Umriss als GeoJSON-Geometrie mit — zum Einzeichnen auf einer Karte.
1

Antwort

Immer JSON in UTF-8. Das Beispiel zeigt eine Adressabfrage; die Werte sind erfunden.

{
    "abfrage_id": 12345,
    "eingabe": {
        "strasse": "Musterstraße",
        "hausnummer": "1",
        "plz": "12345",
        "ort": "Musterstadt"
    },
    "adresse": {
        "strasse": "Musterstraße",
        "hausnummer": "1",
        "plz": "12345",
        "ort": "Musterstadt",
        "bundesland": "NW"
    },
    "koordinate": {
        "lat": 50.9375,
        "lng": 6.9603
    },
    "genauigkeit": "hausnummer",
    "stichtag": "2026-01-01",
    "zonen": [
        {
            "bodenrichtwert": 420,
            "einheit": "EUR/m²",
            "stichtag": "2026-01-01",
            "zonennummer": "11101062",
            "nutzungsart": "Wohnbaufläche",
            "nutzungsart_kuerzel": "W",
            "entwicklungszustand": "Baureifes Land",
            "beitragszustand": "beitragsfrei",
            "gemeinde": "Musterstadt",
            "bundesland": "NW",
            "gutachterausschuss": "Gutachterausschuss Musterstadt",
            "quelle": {
                "namensnennung": "Gutachterausschuss Musterstadt",
                "lizenz": "Datenlizenz Deutschland – Namensnennung – Version 2.0",
                "lizenz_url": "https://www.govdata.de/dl-de/by-2-0"
            },
            "verlauf": [
                {
                    "stichtag": "2026-01-01",
                    "bodenrichtwert": 420,
                    "veraenderung_prozent": 5
                },
                {
                    "stichtag": "2025-01-01",
                    "bodenrichtwert": 400,
                    "veraenderung_prozent": 0
                },
                {
                    "stichtag": "2024-01-01",
                    "bodenrichtwert": 400
                }
            ]
        }
    ],
    "stichtage_verfuegbar": [
        "2026-01-01",
        "2025-01-01",
        "2024-01-01"
    ],
    "berechnet": true
}

Antwortfelder

FeldTypBedeutung
abfrage_id integer Kennung dieser Abfrage — bei Rückfragen angeben.
eingabe object Was angekommen ist, nach dem Zerlegen der Adresse.
adresse object Die Adresse in amtlicher Schreibweise, mit Bundesland.
flurstueck object gemarkung, flur, flurstueck, kennzeichen und die amtliche Fläche flaeche_qm — nur bei der Flurstücksabfrage.
koordinate object Der Punkt, an dem abgefragt wurde (lat, lng, WGS84).
genauigkeit string hausnummer, strasse oder ort — wie genau die Adresse getroffen wurde. Bei strasse oder ort kann die Zone vom Grundstück abweichen.
stichtag date Stichtag, zu dem die Zonen gelten.
zonen[] array Alle Bodenrichtwertzonen an der Stelle. Mehrere, wenn Zonen sich überlagern (etwa Wohnen und Gewerbe).
zonen[].bodenrichtwert number Bodenrichtwert in Euro je Quadratmeter.
zonen[].einheit string Immer EUR/m².
zonen[].stichtag date Stichtag dieser Zone — gleich dem stichtag der Antwort.
zonen[].zonennummer string Nummer der Zone beim Gutachterausschuss.
zonen[].nutzungsart string Art der Nutzung, ausgeschrieben (z. B. Wohnbaufläche); das amtliche Kürzel steht in nutzungsart_kuerzel.
zonen[].entwicklungszustand string Baureifes Land, Rohbauland, Bauerwartungsland, Fläche der Land- und Forstwirtschaft oder Sonstige Fläche.
zonen[].beitragszustand string beitragsfrei, beitragspflichtig oder teilweise erschlossen — ob Erschließungsbeiträge im Wert enthalten sind.
zonen[].gfz, grz number Geschossflächen- und Grundflächenzahl des Richtwertgrundstücks — nur, wo der Gutachterausschuss sie angibt. Steht dort keine Zahl, sondern Text, kommt der Text.
zonen[].tiefe_m, breite_m number Tiefe und Breite des Richtwertgrundstücks in Metern — nur, wo angegeben.
zonen[].gemeinde, ortsteil, bundesland string Lage der Zone.
zonen[].gutachterausschuss string Der zuständige Gutachterausschuss.
zonen[].bodenwert_eur integer Bodenrichtwert mal amtliche Fläche — nur bei der Flurstücksabfrage und genau einer Zone.
zonen[].verlauf[] array Wertentwicklung an dieser Stelle je Stichtag, neuester zuerst: stichtag, bodenrichtwert, veraenderung_prozent (zum vorigen Stichtag).
zonen[].umriss GeoJSON Umriss der Zone als GeoJSON-Geometrie (Polygon oder MultiPolygon) — nur mit umriss=1.
zonen[].quelle object namensnennung, lizenz, lizenz_url — anzuzeigen, wenn Sie den Wert weitergeben.
stichtage_verfuegbar array Alle Stichtage, die es an dieser Stelle gibt.
berechnet boolean true bei einem Treffer. Im Test zählen nur Treffer aufs Kontingent.
hinweis string Erklärung, wenn an der Stelle (oder zum Stichtag) keine Zone liegt — dann mit Status 404 und berechnet: false.
fehler, meldung string Nur bei Fehlern: fester Code und lesbarer Text; dazu abfrage_id und berechnet: false. Bei 409 zusätzlich vorschlaege[] mit gemarkung, flur, flurstueck.

Felder ohne Wert fehlen in der Antwort, statt leer zu erscheinen.

Fehler

Fehler kommen mit HTTP-Status, einem festen Code in fehler und einer lesbaren meldung. Werten Sie den Code aus, nicht den Text.

StatusfehlerBedeutung
401 schluessel_ungueltig Schlüssel fehlt, ist falsch oder gesperrt.
403 testzeitraum_abgelaufen Der Testzeitraum ist vorbei.
404 adresse_nicht_gefunden · flurstueck_nicht_gefunden Nicht im amtlichen Verzeichnis gefunden. Ohne Fehlercode: gefunden, aber keine Zone an der Stelle.
409 flurstueck_mehrdeutig Die Angabe passt auf mehrere Flurstücke; vorschlaege nennt sie.
422 eingabe_fehlt · koordinate_ungueltig · stichtag_ungueltig · flurstueck_unvollstaendig Eingabe fehlt oder hat das falsche Format.
429 zu_viele_anfragen · kontingent_erschoepft Tempo überschritten (Retry-After beachten) oder Testabfragen verbraucht.
503 dienst_fehler Ein amtlicher Dienst (Adresse, Kataster) antwortet gerade nicht. Später erneut versuchen.

Tempo und Kontingent

Bis zu 5 Anfragen pro Sekunde je Schlüssel; darüber antwortet die API mit 429 und dem Kopf Retry-After. Gebuchte Zugänge haben keine Monatsgrenze. Im Test gelten 50 Treffer in 14 Tagen; nur Treffer zählen.

  • X-Treffer — Treffer bisher (im Test seit Beginn, sonst im Monat)
  • X-Kontingent, X-Kontingent-Rest — nur im Test
  • X-Test-Bis — letzter Testtag

Abdeckung

Maschinenlesbar und ohne Schlüssel: GET https://www.online-bodenrichtwert.de/api/v1/abdeckung.

BundeslandNeuester StichtagFrühere Stichtage ab
Brandenburg 01.01.2026 2026
Berlin 01.01.2026 2011
Baden-Württemberg 01.01.2026 2025
Bayern in Vorbereitung —
Bremen 01.01.2023 2023
Hessen 01.01.2026 2020
Hamburg 01.01.2026 2011
Mecklenburg-Vorpommern 01.01.2025 2022
Niedersachsen 01.01.2026 2011
Nordrhein-Westfalen 01.01.2026 2011
Rheinland-Pfalz 01.01.2026 2026
Schleswig-Holstein in Vorbereitung —
Saarland 01.01.2024 2022
Sachsen in Vorbereitung —
Sachsen-Anhalt 01.01.2026 2026
Thüringen 01.01.2026 2026

Versionen

Innerhalb von /v1 kommen nur Felder hinzu; bestehende Felder ändern weder Namen noch Bedeutung. Was das bricht, erscheint als /v2, und /v1 läuft mit Vorlauf weiter.

  • 1.1.1 30.09.2026 Gemarkung auch als Nummer ohne Landeskennung (4936 statt 054936); Wertentwicklung über Änderungen der Nutzungskürzel hinweg (MK → M).
  • 1.1.0 30.09.2026 Wertentwicklung je Zone (verlauf mit Veränderung in Prozent) und Zonenumriss als GeoJSON (umriss=1).
  • 1.0.0 30.09.2026 Erste Fassung: Abfrage per Adresse, Koordinate oder Flurstück, frühere Stichtage, Abdeckung.

Bereit für die erste Abfrage?