Bulk address normalisation from CSV
POST /v1/bulk/addresses takes a CSV of addresses and returns a job (202): the file is processed in the background, row by row, through the SAME validation pipeline GET /v1/validate runs. The columns are named by the caller (query parameters) — the service NEVER guesses which column is the street. A named API key is REQUIRED (401 without one): the job stores the customer's personal data on the instance for the retention window and costs real work.
The result file carries the customer's ORIGINAL columns, with the service's columns beside them: the confirmed locality, street, house number, postal code and coordinates, the match kind per field, and the reasons the rows that were not confirmed were not confirmed. A value the register did not confirm is left EMPTY — never guessed. The rows split into four groups: matched, ambiguous, invalid and unmatched.
Re-uploading the same file (or the same Idempotency-Key) returns the EXISTING job instead of processing and billing it twice. The upload and its result are deleted when the retention window ends (24 hours by default), and the job answers 410 Gone from then on.
POST /v1/bulk/addresses
Upload the file (its content IS the request body, text/csv) and name the columns. The answer is a 202 with the job handle.
| Name | Required | Description |
|---|---|---|
locality | — | The column holding the locality — a name or a 7-digit SYM. Required unless `address` is given. |
street | — | The column holding the street — a name or a 5-digit SYM_UL. |
number | — | The column holding the house number. |
postalCode | — | The column holding the postal code — it also narrows an ambiguous locality name. |
address | — | The column holding the WHOLE address (one free-text field) — the alternative to the four separate columns. |
id | — | The column identifying the row (echoed into the result). |
Request body (JSON)
| Name | Required/typ | Description |
|---|---|---|
<treść żądania> | CSV (text/csv) | The file, with a header row. UTF-8 (a BOM is fine), comma, semicolon or tab delimited. |
Example
The upload
curl -X POST "https://teryt24.pl/v1/bulk/addresses?locality=miejscowosc&ulica=ulica&numer=nr&kod=kod&id=id" \
-H "Content-Type: application/json" \
-d 'id,miejscowosc,ulica,nr,kod
1,Wrocław,ul. Legnicka,61,53-331
2,Wrocław,ul. Legnicka,62,53-331'id,miejscowosc,ulica,nr,kod
1,Wrocław,ul. Legnicka,61,53-331
2,Wrocław,ul. Legnicka,62,53-331Response:
{
"id": "0f3c1b7e-2f5a-4a1e-9c2d-6b1a2f0d4e11",
"status": "queued",
"filename": "adresy.csv",
"rows": 2,
"processed": 0,
"matched": 0,
"ambiguous": 0,
"invalid": 0,
"unmatched": 0,
"error": null,
"resultUrl": null
}| Status | Description |
|---|---|
200 | The same file was uploaded before: the EXISTING job is returned (`duplicate: true`) — the file is not processed or billed twice. |
202 | The job was accepted: `{ id, status, rows, processed, matched, ambiguous, invalid, unmatched, resultUrl }`. Poll GET /v1/bulk/jobs/{id} for progress and GET /v1/bulk/jobs/{id}/result.csv for the result. |
400 | Invalid query parameters. |
401 | A named API key is required for bulk processing. |
413 | The file has more rows than the configured limit. |
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. |
GET /v1/bulk/jobs/{id}
The job's state and progress: how many rows were processed and how they split (matched / ambiguous / invalid / unmatched).
Try it live: /v1/bulk/jobs/0f3c1b7e-2f5a-4a1e-9c2d-6b1a2f0d4e11
| Name | Required | Description |
|---|---|---|
id path | ✓ | The job id. |
Example
The finished job
curl "https://teryt24.pl/v1/bulk/jobs/0f3c1b7e-2f5a-4a1e-9c2d-6b1a2f0d4e11"Response:
{
"id": "0f3c1b7e-2f5a-4a1e-9c2d-6b1a2f0d4e11",
"status": "done",
"rows": 2,
"processed": 2,
"matched": 1,
"ambiguous": 0,
"invalid": 0,
"unmatched": 1,
"resultUrl": "/v1/bulk/jobs/0f3c1b7e-2f5a-4a1e-9c2d-6b1a2f0d4e11/result.csv"
}| Status | Description |
|---|---|
200 | The job's state. |
401 | A named API key is required. |
404 | No such job (for this key). |
410 | The job's files were deleted when the retention window ended. |
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. |
GET /v1/bulk/jobs/{id}/result.csv
The result file: the customer's columns unchanged + the service's columns. 409 while the job is still running; 410 once the files are gone.
Try it live: /v1/bulk/jobs/0f3c1b7e-2f5a-4a1e-9c2d-6b1a2f0d4e11/result.csv
| Name | Required | Description |
|---|---|---|
id path | ✓ | The job id. |
Example
One result row (trimmed)
curl "https://teryt24.pl/v1/bulk/jobs/0f3c1b7e-2f5a-4a1e-9c2d-6b1a2f0d4e11/result.csv"Response (truncated — first rows shown):
"id","miejscowosc","ulica","nr","kod","teryt24_status","teryt24_verdict","teryt24_locality","teryt24_locality_sym","teryt24_street","teryt24_street_id","teryt24_house_number","teryt24_postal_code","teryt24_lat","teryt24_lon","teryt24_match","teryt24_issues"
"1","Wrocław","ul. Legnicka","61","53-331","matched","valid","Wrocław","0983988","ul. Legnicka","12400","61","53-331","50.7322","16.6403","locality=exact;street=exact;houseNumber=exact;postalCode=exact",""| Status | Description |
|---|---|
200 | The result file. |
401 | A named API key is required. |
404 | No such job (for this key). |
409 | The job has not finished yet. |
410 | The job's files were deleted when the retention window ended. |
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. |
Browse the data
The same data has browsable pages — locality, street and postal-code cards:
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.