Postal codes API
Three lookup modes: by code or prefix (q=), the codes serving one locality (locality=SYM), and the nearest codes for coordinates (lat+lon). Every row carries the TERC hierarchy and — when it resolves to a single locality — its SYM, kind and population.
The /v1/postal-codes/{code} detail adds the PRG (GUGiK) aggregates: address-point count, streets at the buildings, and the area centroid. POST /v1/postal-codes/bulk takes up to 100 codes at once — data cleaning without a round-trip festival.
GET /v1/postal-codes
Digits and dashes are interchangeable ('00-950', '00950', prefix '00-9'). format=geojson returns PRG points.
Try it live: /v1/postal-codes?q=00-002
| Name | Required | Description |
|---|---|---|
q | — | Postal code in any form: '00-950', '00950', or a prefix of either ('00-9', '009'); non-digits are ignored. Required unless locality or lat+lon is given. |
locality | — | Locality SYM (7 digits, as returned by /v1/localities): the postal codes serving it — PRG official set first, then the PNA name ladder; one row per code, named for the requested locality (synthesized from the registry when GeoNames never paired it). Mutually exclusive with q and lat+lon; sym/type/population always describe the requested locality. |
lat | — | WGS84 latitude (-90..90). Together with lon triggers nearest mode: the codes whose official PRG centroids are closest, distance-sorted (each with distanceKm, centroidLat/centroidLon, addressCount, streetCount). Mutually exclusive with q and locality. A blank value ('?lat=') is treated as absent — it does not trigger the mode. |
lon | — | WGS84 longitude (-180..180). Must be given together with lat. |
limit | — | Maximum results (1–50). |
offset | — | Skip the first N results (0-based, max 10000) to page past the limit. |
format | — | 'geojson' answers an RFC 7946 FeatureCollection (row points — the PRG centroids in nearest mode — in [lon,lat] order) instead of the JSON envelope. |
Example
Lookup by code (q=)
curl "https://teryt24.pl/v1/postal-codes?q=00-002"Response:
{
"postalCodes": [
{ "code": "00-002", "city": "Warszawa", "province": "MAZOWIECKIE", "county": "Warszawa", "municipality": "Warszawa", "municipalityKind": "gmina miejska, miasto stołeczne", "lat": 52.2358, "lon": 21.0101, "sym": "0988796", "citySlug": null, "type": "95", "typeLabel": "dzielnica m.st. Warszawy", "population": 1702139 }
],
"meta": { "total": 1, "offset": 0, "limit": 10 }
}| Status | Description |
|---|---|
200 | Successful response — the row shape depends on the mode: the q/locality DTO for prefix and locality lookups, the nearest DTO (code, distanceKm, centroid, counts) for lat+lon. |
400 | Invalid query parameters. |
429 | Rate limit exceeded (60 requests/min/IP) or a plan's monthly quota exhausted. Includes `Retry-After` and `X-RateLimit-*` headers; the body's `code` distinguishes the two kinds. |
500 | Unexpected server failure (e.g. the database is unreachable). The body is deliberately generic — error detail lives in the server logs, never in the response. |
503 | Postal-code dataset not available (generate data/pna CSV via pnpm fetch:pna); the nearest mode additionally requires the PRG ingest. |
GET /v1/postal-codes/{code}
The full code record: every locality (PNA) plus the PRG aggregates — 404 for a code outside the register.
Try it live: /v1/postal-codes/00-002
| Name | Required | Description |
|---|---|---|
code path | ✓ | Postal code in the display ('59-700') or digits ('59700') form. |
Example
Code 00-002 (Warszawa)
curl "https://teryt24.pl/v1/postal-codes/00-002"Response:
{
"code": "00-002",
"rows": [
{ "code": "00-002", "city": "Warszawa", "province": "MAZOWIECKIE", "county": "Warszawa", "municipality": "Warszawa", "municipalityKind": "gmina miejska, miasto stołeczne", "lat": 52.2358, "lon": 21.0101, "sym": "0988796", "citySlug": null, "type": "95", "typeLabel": "dzielnica m.st. Warszawy", "population": 1702139 }
],
"prg": { "addressCount": 1, "streetCount": 1, "centroidLat": 52.23579285110347, "centroidLon": 21.010129613434586, "streets": ["ul. Świętokrzyska"] }
}| Status | Description |
|---|---|
200 | Successful response. |
400 | Invalid query parameters. |
404 | Unknown postal code. |
429 | Rate limit exceeded (60 requests/min/IP) or a plan's monthly quota exhausted. Includes `Retry-After` and `X-RateLimit-*` headers; the body's `code` distinguishes the two kinds. |
500 | Unexpected server failure (e.g. the database is unreachable). The body is deliberately generic — error detail lives in the server logs, never in the response. |
503 | Postal-code dataset not available (generate data/pna CSV via pnpm fetch:pna). |
POST /v1/postal-codes/bulk
A bulk row is byte-for-byte the single GET /v1/postal-codes/{code} answer; an unknown code returns as a status:error row carrying the HTTP status the single GET would have answered.
Request body (JSON)
| Name | Required/typ | Description |
|---|---|---|
codes | array (1–100) | Codes in the display ('59-700') or digits ('59700') form. |
Example
Two codes in one request
curl -X POST "https://teryt24.pl/v1/postal-codes/bulk" \
-H "Content-Type: application/json" \
-d '{"codes":["00-002","59-700"]}'{"codes":["00-002","59-700"]}Response (truncated — first rows shown):
{
"results": [
{ "code": "00-002", "status": "ok", "rows": [ { "code": "00-002", "city": "Warszawa", "province": "MAZOWIECKIE", "county": "Warszawa", "municipality": "Warszawa", "municipalityKind": "gmina miejska, miasto stołeczne", "lat": 52.2358, "lon": 21.0101, "sym": "0988796", "citySlug": null, "type": "95", "typeLabel": "dzielnica m.st. Warszawy", "population": 1702139 } ], "prg": { "addressCount": 1, "streetCount": 1, "centroidLat": 52.23579285110347, "centroidLon": 21.010129613434586, "streets": ["ul. Świętokrzyska"] } },
{ "code": "59-700", "status": "ok", "rows": [ { "code": "59-700", "city": "Bolesławice", "province": "DOLNOŚLĄSKIE", "county": "bolesławiecki", "municipality": "Bolesławiec", "municipalityKind": "gmina wiejska", "lat": 51.2798, "lon": 15.5485, "sym": "0188937", "citySlug": null, "type": "01", "typeLabel": "wieś", "population": 876 } ] }
]
}| Status | Description |
|---|---|
200 | Per-code results in request order: `status:"ok"` rows carry the detail endpoint's body (code, rows, prg); `status:"error"` rows carry the error the single GET would have answered with. |
400 | Invalid query parameters. |
429 | Rate limit exceeded (60 requests/min/IP) or a plan's monthly quota exhausted. Includes `Retry-After` and `X-RateLimit-*` headers; the body's `code` distinguishes the two kinds. |
500 | Unexpected server failure (e.g. the database is unreachable). The body is deliberately generic — error detail lives in the server logs, never in the response. |
Browse the data
Every postal code has an info page with its localities and PRG data:
All /v1 endpoints return JSON, speak CORS and are covered by the openapi.json document. Interactive version of this reference: /en/docs.
Limits: 60 requests/min per IP without a key; an API key (the X-API-Key header) gets its own bucket. Over the limit the endpoints answer HTTP 429 with a Retry-After header. Details: limits & keys · usage policy.
Data: GUS TERYT, PRG address points (GUGiK), GeoNames postal codes — via teryt24.pl with a link to the usage policy. Server-side-only use is exempt.