Przewodnik integracji
Ten przewodnik prowadzi od zera do działającej integracji na darmowym REST API teryt24.pl: autouzupełnianie adresu w formularzu, walidacja i geokodowanie, kody pocztowe dla dostaw i punkty na mapie. Bez klucza i rejestracji — pierwsze zapytanie w 10 sekund.
Każdy przykład jest żywy: skopiowany do terminala lub edytora odpowie dokładnie tak, jak tu widać. Pełne listy parametrów i schematy odpowiedzi mają osobne strony — każdy krok linkuje do swojej.
Pierwsze zapytanie w 10 sekund
Trzy endpointy, które obsługują większość zastosowań: miejscowości, walidacja adresu, kody pocztowe.
curl "https://teryt24.pl/v1/localities?q=warsz&limit=5"
curl "https://teryt24.pl/v1/geocode?address=Warszawa,%20ul.%20Anilinowa%202"
curl "https://teryt24.pl/v1/postal-codes?q=00-002"Parametry i odpowiedzi opisują strony rodzin: miejscowości, geokodowanie, kody pocztowe.
Autouzupełnianie w formularzu
Jedno zapytanie na pauzę w pisaniu: miasta, miejscowości, ulice i kody naraz, z gotowym opisem do wyświetlenia.
const input = document.querySelector("#city");
let timer = null;
input.addEventListener("input", () => {
clearTimeout(timer);
timer = setTimeout(async () => {
const url = "https://teryt24.pl/v1/autosuggest?q=" +
encodeURIComponent(input.value) + "&layers=city,locality&limit=8";
const res = await fetch(url);
const data = await res.json();
// data.suggestions: [{ type, id, label, ... }] — label is display-ready,
// id (SIMC / SYM_UL / postal code) is the key for the follow-up call
showSuggestions(data.suggestions.map((s) => s.label));
}, 200); // debounce — query on the typing pause, not on every keystroke
});Warstwy zawęża parametr layers=, a ranking łączy tier dopasowania z ważnością realną (ludność, punkty adresowe). Pełne parametry: autouzupełnianie.
Walidacja i geokodowanie adresów
Trzy tryby wywołania — polami, wg nazwy, jednym ciągiem — i jeden sposób rozgałęziania: po polu result.
import httpx
r = httpx.get("https://teryt24.pl/v1/geocode",
params={"address": "Wrocław-Fabryczna, ul. Eugeniusza Horbaczewskiego 35"},
timeout=10)
data = r.json()
if data["result"] == "exact":
point = data["matches"][0] # lat, lon, number, streetId, postalCode
elif data["result"] == "street":
nearest = data["nearestNumbers"] # number not in PRG — closest registered ones
elif data["result"] == "street-ambiguous":
candidates = data["streets"] # several streets match — re-query with street=<id>
elif data["result"] == "locality-ambiguous":
shortlist = data["localities"] # several localities match — re-query with sym=<sym>
else: # "none" — genuinely unknown address
passUwaga na podzielone miasta: nazwa „Warszawa” odpowiada ~90 rekordom SIMC — tryb wg nazwy zwraca wtedy locality-ambiguous z listą kandydatów; dopytaj z sym=. Pełne parametry: geokodowanie.
Kody pocztowe dla dostaw
Sto kodów jednym żądaniem — bulk zwraca dokładnie to, co sto osobnych zapytań, minus round-tripy.
r = httpx.post("https://teryt24.pl/v1/postal-codes/bulk",
json={"codes": ["00-002", "59-700"]},
timeout=30)
for row in r.json()["results"]:
if row["status"] == "ok":
print(row["code"], "->", ", ".join(x["city"] for x in row["rows"]))Pojedynczy kod ma osobną kartę /v1/postal-codes/{code} z danymi PRG (GUGiK). Pełne parametry: kody pocztowe.
Gotowy formularz adresowy
Cały workflow — podpowiedzi miejscowości i ulic, walidacja, przegląd wyniku — w jednym pliku bez zależności. Najszybsza droga do działającego formularza.
<link rel="stylesheet" href="/assets/address-widget.css">
<script src="/assets/address-widget.js"></script>
<div id="address"></div>
<script>
Teryt24AddressForm.mount(document.getElementById("address"), {
onValidated: function (result) {
// result.address carries the identifiers: locality SYM, street SYM_UL,
// postal code, coordinates. result.verdict is the answer's quality.
console.log(result.verdict, result.address.locality.sym);
},
});
</script>Widget jest samodzielny: etykiety konfigurowalne, wynik trafia do onValidated — ze strukturalnym adresem i identyfikatorami (SYM, SYM_UL, kod pocztowy, współrzędne). Zobacz działające demo. Kontrakt danych: walidacja adresu.
Masowa normalizacja pliku CSV
Wgraj plik z adresami, nazwij jego kolumny i odbierz plik wynikowy: oryginalne kolumny bez zmian plus potwierdzony adres, ocena dopasowania i powody, dla których wiersze nie zostały potwierdzone.
curl -X POST "https://teryt24.pl/v1/bulk/addresses?locality=miejscowosc&street=ulica&number=nr&id=id" \
-H "X-API-Key: t24_..." -H "Content-Type: text/csv" \
--data-binary @adresy.csv
# -> 202 { "id": "...", "status": "queued", "rows": 2 }
curl "https://teryt24.pl/v1/bulk/jobs/<job-id>" -H "X-API-Key: t24_..."
# -> { "status": "done", "rows": 2, "processed": 2, "matched": 1, "unmatched": 1, ... }
curl -OJ "https://teryt24.pl/v1/bulk/jobs/<job-id>/result.csv" -H "X-API-Key: t24_..."Wymaga klucza API (limity i klucze) — zadanie trzyma na instancji dane osobowe, więc nie jest dla anonimowych wywołań. Wartość, której rejestr nie potwierdził, zostaje w pliku PUSTA: zgadywanie adresu w arkuszu czyta się jak fakt. Wgrany plik i wynik są usuwane po 24 godzinach. Pełne parametry: masowa normalizacja.
Feed zmian rejestru
GUS publikuje różnice między stanami katalogu — feed podaje je jako zmiany, z datą stanu, do której należą.
curl "https://teryt24.pl/v1/changes?dataset=simc&since=2026-10-01&limit=5"
# -> { "changes": [ { "type": "renamed", "stanBefore": "2026-10-12",
# "stanAfter": "2026-11-09",
# "nameBefore": "...", "nameAfter": "..." } ],
# "meta": { "coverage": { "from": "...", "to": "...", "complete": false } } }Trzy znaczenia, o których trzeba wiedzieć: likwidacja oznacza, że obiekt ZIKNĄŁ z katalogu (nie że adres stał się nieprawidłowy); typ reordered to zmiana FORMATOWA (tylko część nazwy ulicy decydująca o kolejności alfabetycznej) i można go odfiltrować; daty to stany katalogu, nie moment publikacji pliku. Pełne parametry: feed zmian.
Punkty na mapie — GeoJSON
format=geojson zmienia odpowiedź w RFC 7946 FeatureCollection — geometrie lecą prosto do Leafleta albo MapLibre.
const res = await fetch("https://teryt24.pl/v1/localities?q=warsz&limit=50&format=geojson");
const geo = await res.json(); // RFC 7946 FeatureCollection — points as [lon, lat]
L.geoJSON(geo).addTo(map); // one marker per locality, row fields in feature.propertiesTen sam parametr przyjmują geocode, reverse-geocode i kody pocztowe w trybie geograficznym. Pełne parametry: miejscowości.
Ponawianie przy 429 i cache ETag
Limiter odpowiada nagłówkiem Retry-After (sekundy) — odczytaj go i poczekaj, zamiast ponawiać natychmiast.
async function getJson(url, attempt = 0) {
const res = await fetch(url);
if (res.status === 429 && attempt < 3) {
const wait = Number(res.headers.get("Retry-After") || "1");
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
return getJson(url, attempt + 1); // the limiter answered — wait, then retry
}
return res.json();
}Udane GET-y niosą ETag i Cache-Control na 1 dzień — powtórka z If-None-Match dostaje 304 bez treści. Własny kubełek limitera daje klucz API: limity i klucze.
Wszystkie endpointy /v1 zwracają JSON, obsługują CORS i są objęte dokumentem openapi.json. Interaktywna wersja tej referencji: /docs.
Limity: 60 zapytań/min na IP bez klucza; klucz API (nagłówek X-API-Key) dostaje własny kubełek. Po przekroczeniu endpointy odpowiadają HTTP 429 z nagłówkiem Retry-After. Szczegóły: limity i klucze · zasady użycia.
Dane: GUS TERYT, punkty adresowe PRG (GUGiK), kody pocztowe GeoNames — przez teryt24.pl z linkiem do zasad użycia. Użycie wyłącznie po stronie serwera jest zwolnione.