teryt24.pl

teryt24.pl · Dokumentacja API

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
    pass

Uwaga 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.properties

Ten 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.

Atrybucja jest warunkiem użycia. Wyświetlając wyniki w aplikacji, stronie, mapie lub raporcie, pokaż: 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.