Part of a Data Solution
- Explore →
Postal Codes Solution
Every worldwide postal-code dataset in one bundle — the ultimate global reference for precise postal codes.
Premium
You're viewing a free sample
Get the complete dataset — all rows and files — delivered instantly after checkout, with lifetime access to the latest version.
- Secure checkout via Stripe
- Instant download after payment
- Lifetime access to the latest version
- Creative Commons Attribution 4.0 license
API Access
Access dataset files directly from scripts, code, or AI agents.
Browse dataset files
API Access
Access dataset files directly from scripts, code, or AI agents.
Each file has a stable URL (r-link) that you can use directly in scripts, apps, or AI agents. These URLs are permanent and safe to hardcode.
Start with these files — they give you everything you need to understand and access the dataset.
- 1. Fetch datapackage.yaml to inspect schema and resources
- 2. Download data resources listed in datapackage.yaml
- 3. Read README.md for full context
Data Files
postal-codes-pl-sample
| Field | Type | Description | Constraints | Title |
|---|---|---|---|---|
| country_code | string | ISO 3166-1 alpha-2 country code — always PL | { "pattern": "^PL$", "required": true } | Country Code |
| postal_code | string | Polish postal code (kod pocztowy) in NN-NNN form | { "pattern": "^\\d{2}-\\d{3}$", "required": true } | Postal Code |
| place_name | string | Locality name as supplied by the source | Place Name | |
| admin_name1 | string | Voivodeship name (wojewodztwo), as spelled by the source — see README | Administrative Name 1 | |
| admin_code1 | string | Official ISO 3166-2:PL voivodeship code, PL- prefix stripped — derived from the TERYT voivodeship number, not from the source's own sequential region field | { "pattern": "^[A-Z]{2}$" } | Administrative Code 1 |
| admin_name2 | string | County name (powiat) | Administrative Name 2 | |
| admin_code2 | string | Official GUS TERYT powiat code, 4 digits — leading zeros significant | { "pattern": "^\\d{4}$" } | Administrative Code 2 |
| admin_name3 | string | Municipality name (gmina) | Administrative Name 3 | |
| admin_code3 | string | Official GUS TERYT gmina code, 6 digits — leading zeros significant; always extends admin_code2 | { "pattern": "^\\d{6}$" } | Administrative Code 3 |
| latitude | number | Latitude, decimal degrees (WGS84) | Latitude | |
| longitude | number | Longitude, decimal degrees (WGS84) | Longitude | |
| accuracy | string | GeoNames accuracy of latitude/longitude. GeoNames documents 1=estimated, 4=geonameid, 6=centroid of addresses or shape; other values (e.g. 3) occur upstream without documentation. Empty where GeoNames gives none. | Accuracy | |
| alternative_city_name | string | Alternative or historic name for the place where known (often the historical German form), from the GeoNames gazetteer dump; empty where it only repeats place_name | Alternative City Name |
Download
Download sample CSVAbout
- Last updated
- 6 October 2026
- Total rows
- 100
- Format
- CSV
- File size
- 10.8 kB
About this dataset
Poland (PL): migration notes
Internal record of the P5 migration from the old scripts to the producer: the discrepancies found, their causes, and who decided what. This is a dated record, so the numbers are as of the migration and not kept current. It is not shipped in the zip.
- Issue: pc-28e.7.45 (closed 2026-10-03)
- Commit:
36df9c0(worktree commit09206b2, landed unchanged), batch 4 - Parity verdict:
unexplained(32 unclassified cells; the gate blocks on any unclassified cell); accepted as upstream drift - Producer:
custom, refresh disabled
Sources
| Input | URL | Notes |
|---|---|---|
| GeoNames postal export | https://download.geonames.org/export/zip/PL.zip | Carries the official GUS TERYT powiat and gmina codes |
| GeoNames gazetteer dump | https://download.geonames.org/export/dump/PL.zip | Used for alternative_city_name; replaces the legacy R2 copy |
- The old
recover_alt_names.pyread nothing upstream. It copied alternate names from the legacy R2 objectpl/pl.csv(an intermediate export of the oldscripts/geonames/geonames_parse_to_R2.pyjob), pulling it from R2 with credentials (--fetch). That object isn't a real source. - Poczta Polska's PNA register stays excluded: it is a commercial product whose terms forbid resale, the free PDF edition included (
.licensing-notes.md, 2026-08-27 audit).
Parity against the live zip (2026-10-03)
Rows: 72,899 live and 72,899 fresh, none only on one side. Row order identical (the old sort was kept, so there is no row_order class). 32 changed cells, all in alternative_city_name, all unclassified. No other column differs. Before writing the producer, the agent checked the dump rule offline against the live CSV: 72,867 of 72,899 alternate names match exactly.
Cause: edits to the GeoNames dump after the legacy snapshot (upstream drift). Each changed cell maps to a dump entry modified after the legacy R2 copy was taken. 20 cells gained a name, 12 lost one.
| Place | Rows (postal codes) | Live → fresh | Dump entry that now wins (modified) |
|---|---|---|---|
| Lipnik | 12 (98-354, 32-412, 19-230, 37-206, 37-220, 27-540, 28-404, 28-221, 42-253, 12-100, 63-507, 73-110) | Comuna de Lipnik → empty | 13665098 (2026-06-29), no alternate names; last matching entry wins |
| Waksmund | 3 (34-400, 34-431, 34-433) | empty → Vaksmund | 756227 (2026-09-02) |
| Widawa | 2 (98-170, 42-287) | empty → Vidava | 13562336 (2026-09-02) |
| Binarowa | 2 (38-306, 38-340) | empty → Binarova | 775917 (2026-09-02) |
| Skawinki | 2 (34-100, 34-143) | empty → Skavinki | 3085805 (2026-09-02) |
| Siestrzechowice | 2 (48-300, 48-303) | empty → Grunau | 3085928 (2026-09-11) |
| Bestwinka | 1 (43-512) | empty → Bestvinka | 3103655 (2026-09-02) |
| Borowno | 1 (42-233) | empty → Borovno | 3102819 (2026-09-02) |
| Lipkowo | 1 (19-400) | empty → Lipkovo | 12110766 (2026-09-02) |
| Naprawa | 1 (34-240) | empty → Naprava | 3091135 (2026-09-02) |
| Plewiska | 1 (62-064) | empty → Pleviska | 3088846 (2026-09-02) |
| Twardawa | 1 (48-250) | empty → Tvardava | 3082940 (2026-09-02) |
| Siedliska Pierwsze | 1 (21-060) | empty → Siedliska | 759385 (2026-04-17) |
| Stare Kostry | 1 (18-214) | empty → Kostry Stare | 767974 (2026-05-14) |
| Stare Warele | 1 (18-214) | empty → Warel Stare | 756154 (2026-05-14) |
The dump entries and dates are from the agent's trace. The per-place row breakdown was recomputed on 2026-10-05 from the live CSV and the committed data/pl.csv (it reproduces the 32 cells; the parity report lists only 10 examples).
The public sample (postal.datahub.io/pl/pl.csv) came out unexplained (informational only): 194 leading_zero_restored and 112 unclassified cells. It is the old generic GeoNames parse that turned TERYT 0201 into 201.0.
Decisions
| Decision | By | Basis |
|---|---|---|
Rebuild alternative_city_name from the GeoNames dump with the scripts/producers/geonames.py rule (first non-numeric alternate name of the entry whose asciiname equals the cleaned place_name; last match wins), instead of copying the legacy R2 pl/pl.csv | Agent, under plan decision D1 (write the producer against the original source); put to the user, who replied with a general rule (row below) and did not address the switch separately | The legacy object isn't fetchable from any original source. The old script's docstring said its own re-derivation (name + nearest coordinate) reproduced only ~76%; the asciiname rule matches all but the 32 drift cells. This is the D1 precedent later cited for de (280 cells, described to the user as "the same approach you approved for pl", session 7df0c5be), pt (18 cells) and others |
| Accept the 32 alt-name cells | User, 2026-10-03 (session d33e1b7e): "in general I accept any changes that stem from updated source data" | Said in reply to the pl/ph question ("Accept both?"), which the user had left unanswered in the form. The orchestrator recorded it as the extended standing rule (upstream drift traced to source) in docs/parity/p5.md and the plan |
Keep recover_alt_names.py's "drop aliases that just repeat place_name" rule | Agent: a faithful port | The cleaned name is only the lookup key; place_name ships uncleaned, as live does |
admin_code1 for the one row without TERYT (88-420 Jeziora) falls back to the GeoNames region number, mapped through the TERYT prefix | Agent | Live already has KP there |
| No row-count assert | Agent | Old asserts otherwise ported (see below) |
| Drop row and distinct-code counts from README; keep admin-unit counts (2,476 gminas, 380 powiats, 16 voivodeships), checked against the fresh data | Agent, per the README counts rule | ATTRIBUTION.txt untouched |
Transforms carried over
Folded from fetch_source.py → build_base.py → recover_alt_names.py → integrity_checker.py (package.py, publish_r2.py and the scripts README deleted):
- TERYT powiat (4 digits) and gmina (6 digits) codes copied verbatim as text (no pandas
201.0). admin_code1→ ISO 3166-2:PL from the TERYT voivodeship prefix, not GeoNames' sequential region number (72–87). GeoNames number, TERYT prefix and region name must agree 1:1:1, and all 16 voivodeships must be present.- Voivodeship names kept as GeoNames spells them (English exonyms, e.g. "Lower Silesia").
- Rows sorted by (admin_code1, admin_code2, postal_code, place_name).
integrity_checker.py's checks are nowProducerErrors: NN-NNN postal codes; 4/6-digit TERYT with the gmina extending the powiat; no literal "nan" or ".0" suffix; coordinates inside Poland's bounding box; unique primary key (postal_code, place_name, admin_code3). The gmina is in the key because 78-650 Pilów is listed in two gminas (321703, 321705); both rows are kept.
Open questions
- Resolved (user, 2026-10-05, pc-28e.7.59): the D1 switch of
alternative_city_namefrom the legacy R2 copy to the GeoNames dump is confirmed. .licensing-notes.mdstill saysalternative_city_name"was recovered from the existing R2 copy rather than re-derived", which is no longer true, and it carries row counts (72,899 / 72,900 / 20,299). Left for the end-of-epic sweep (pc-hfz).
Provenance
Reconstructed 2026-10-05 from the agent transcript d33e1b7e-5027-498a-a97f-ba914ee2524e/subagents/agent-a651a38384495c267.jsonl, the orchestrator session d33e1b7e-… (hold, question, user reply, landing), session 7df0c5be-… (later citations of the pl precedent), the plan's D1 entry (session 068ca403-…), the commit message, the bd notes and close reason, and docs/parity/p5.md.