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.
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
| Parameter | Typ | Pflicht | Bedeutung |
|---|---|---|---|
| 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& |
| lat, lng | number | eine der drei Abfragearten | Breite und Länge in Dezimalgrad (WGS84), innerhalb Deutschlands. lat=50.9375& |
| 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& |
| 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
| Feld | Typ | Bedeutung |
|---|---|---|
| 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.
| Status | fehler | Bedeutung |
|---|---|---|
| 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.
| Bundesland | Neuester Stichtag | Frü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?