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
- opendata.swiss terms of use, open tier (commercial use permitted, attribution recommended) 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.yml to inspect schema and resources
- 2. Download data resources listed in datapackage.yml
- 3. Read README.md for full context
Data Files
Postal Codes Data Resource for Switzerland, CH
| Field | Description | Title |
|---|---|---|
| country_code | ISO 3166-1 alpha-2 code for the country. | Country Code |
| postal_code | Postal code for the location. | Postal Code |
| place_name | Name of the city, town, or place. | Place Name |
| admin_name1 | Primary administrative division (e.g., state, region). | Administrative Name 1 |
| admin_code1 | Code for the primary administrative division. | Administrative Code 1 |
| admin_name2 | Secondary administrative division (e.g., county, district). | Administrative Name 2 |
| admin_code2 | Code for the secondary administrative division. | Administrative Code 2 |
| admin_name3 | Tertiary administrative division (e.g., municipality, borough). | Administrative Name 3 |
| admin_code3 | Code for the tertiary administrative division. | Administrative Code 3 |
| latitude | Latitude coordinate of the place. | Latitude |
| longitude | Longitude coordinate of the place. | Longitude |
| accuracy | Accuracy level of the latitude and longitude coordinates. | Accuracy |
| alternative_city_name | Alternative name(s) for the city or place. | Alternative City Name |
| code_type | Extra field. 'locality' (official swisstopo register row) or 'po_box_or_large_account' (PO-box/corporate/branch code not in the locality register). | Code Type |
| address_share_pct | Extra field. For localities split across municipalities, this municipality's percentage share of the locality's addresses. | Address Share Percent |
Download
Download sample CSVAbout
- Last updated
- 16 September 2026
- Total rows
- ...
- Format
- CSV
- File size
- 12.8 kB
About this dataset
Switzerland (CH) — internal licensing notes
Source research, 2026-09-16 — first proper audit of this country
No .licensing-notes.md existed for CH before this pass. datapackage.yml
carries the old-style placeholder sources: [{title: "Multiple sources", path: https://datahub.io/collections/postal-codes-datasets}] — the same
non-informative pattern already flagged as a defect elsewhere in this
project (see feedback_datapackage_no_sources) — and no licenses: array
at all. Nothing in the repo currently records what ch/ch.csv actually is.
1. What the current R2 file (ch/ch.csv, 436,424 bytes, 2026-07-30) actually is — VERIFIED
Fetched directly from the main postal-codes R2 bucket (read-only,
R2_ACCESS_KEY/R2_SECRET_KEY from this repo's own .env). 4,520 data
rows + header.
Fetched https://download.geonames.org/export/zip/CH.zip (CC BY 4.0)
directly for comparison: CH.txt also has exactly 4,520 rows, and a
row-by-row diff of the first N rows shows the R2 file is a straight
tab-to-comma conversion of raw GeoNames CH.txt, nothing more:
GeoNames: CH 5000 Aarau Kanton Aargau AG Bezirk Aarau 1901 Aarau 4001 47.3887 8.0483 4
R2 file: CH,5000,Aarau,Kanton Aargau,AG,Bezirk Aarau,1901,Aarau,4001,47.3887,8.0483,4.0,Aarau
Confirmed defect introduced in that conversion: accuracy is
float-formatted (4.0, 1.0, 3.0, 6.0 instead of GeoNames' own
integer 4, 1, 3, 6). This is the exact class of bug
datasets/lk/scripts/publish_r2.py now hard-aborts on
(if r["accuracy"].endswith(".0")) — CH predates that guard and ships
with the defect live. Distribution in the raw GeoNames file: 4,006 rows
accuracy=4, 351 blank, 105 accuracy=3, 31 accuracy=1, 27 accuracy=6.
The alternative_city_name column is also visibly GeoNames' raw
alternatenames join — it includes transliteration noise typical of an
unfiltered GeoNames pull (e.g. CH4005 as an "alternative name" for
Erlinsbach, Bibershtajn for Biberstein) rather than a curated list.
The one genuinely good thing about this file — do not lose this when rebuilding
Checked directly against the real ISO 3166-2:CH canton list (26
cantons): GeoNames' admin_code1 for Switzerland is not the usual
"false friend" internal number seen in nearly every other country audited
in this project. All 26 distinct admin_code1 values in CH.txt are
exactly the real canton abbreviations — AG, AI, AR, BE, BL, BS, FR, GE, GL, GR, JU, LU, NE, NW, OW, SG, SH, SO, SZ, TG, TI, UR, VD, VS, ZG, ZH —
with zero mismatches. (No CH- prefix is applied, unlike some other
built countries in this repo, e.g. LK ships admin_code1: LK-2.)
admin_code2 (e.g. 1901 for "Bezirk Aarau") also turns out to be a
real official BFS number, not a GeoNames-internal one — confirmed via
the Swiss Federal Statistical Office's own documented scheme: a
Bezirksnummer is the official BFS canton number (01–26, Aargau = 19)
followed by a two-digit sequence number within the canton (01, 02, …), so
1901 = canton 19 (Aargau) district 01 (Bezirk Aarau). This checks out
across the sample. Switzerland appears to be an exception to this
project's usual GeoNames admin-code false-friend problem at both admin1
and admin2 — worth confirming exhaustively against BFS's own
Bezirksnummer register before relying on it for a rebuild, but nothing
found in this pass contradicts it.
Verdict on the current file: stale-by-convention (float accuracy bug, raw unfiltered alt-names, no licence metadata) but not fabricated or wrong at the admin-code level. It is a legitimate GeoNames CC BY 4.0 extract, mis-packaged (missing attribution, missing licence block, one formatting bug) rather than bad data.
2. Official tier-1/2 sources checked FIRST, per the standing GeoNames-is-fallback rule
Swiss Post (post.ch) — checked, not the primary distribution channel for this
Swiss Post publishes a "street directory with sorting data" (free) and an
open-data program (post.ch/open-data), oriented at addressing/sorting
data (street-level, for mailers) rather than a public locality↔PLZ↔canton
reference table. Not fetched directly this pass — the better-fitting
dataset (below) is the one BFS/swisstopo are statutorily tasked with
publishing.
swisstopo — CONFIRMED, this is the right official source, fetched and verified directly
Amtliches Ortschaftenverzeichnis mit Postleitzahl und Perimeter
("Official index of cities and towns including postal codes and
perimeter"), published by the Federal Office of Topography (swisstopo)
under a legal mandate (Art. 24 GeoNV — the ordinance on geographical
names). Confirmed via the opendata.swiss CKAN API
(api3/action/package_show):
- Publisher: Bundesamt für Landestopografie swisstopo
- Update frequency:
MONTHLY(accrual_periodicity), confirmed live — the STAC item'sproperties.updatedis2026-09-01, i.e. this month's refresh, not a stale snapshot. - Licence: opendata.swiss "terms of use" category
terms_open(rights: https://opendata.swiss/terms-of-use#terms_open) — fetched the terms-of-use page directly. This tier is opendata.swiss's most permissive: commercial use permitted, attribution only "recommended," not a hard legal requirement. No SPDX identifier exists for this — should be recorded ascustom:opendata-swiss-terms-openin anylicence_statusblock, per this project's provenance-gate convention (never freeform prose). - Distribution: found via the underlying STAC API
(
data.geo.admin.ch/api/stac/v0.9/collections/ch.swisstopo-vd.ortschaftenverzeichnis_plz/items), which is more reliable than scraping the (JS-rendered, bot-blocking — 403'd on a plain WebFetch, required a browser UA via curl) opendata.swiss/swisstopo HTML pages. Direct CSV asset:https://data.geo.admin.ch/ch.swisstopo-vd.ortschaftenverzeichnis_plz/ortschaftenverzeichnis_plz/ortschaftenverzeichnis_plz_4326.csv.zip(WGS84 variant; a_2056LV95/projected variant,.gdb,.shpand.xtfare also published from the same STAC item).
Fetched and inspected directly (AMTOVZ_CSV_WGS84.csv, ;-delimited,
UTF-8 BOM, CRLF): 5,718 data rows, fields:
Ortschaftsname; PLZ4; Zusatzziffer; ZIP_ID; Gemeindename; BFS-Nr;
Kantonskürzel; Adressenanteil; E; N; Sprache; Validity
This is a materially better source than GeoNames on every axis that matters to this product:
Kantonskürzelis the real ISO 3166-2:CH canton code, straight from the authority itself — 26 real cantons confirmed present, plus a deliberate blank for the 20 rows that are actually Liechtenstein municipalities sharing the Swiss postal-code range 9485–9499 (Vaduz, Schaan, Triesen, etc. — Liechtenstein uses Swiss Post). These must be filtered out (or handled explicitly) for acountry_code: CH-only product — confirmed GeoNames' ownCH.txtalready excludes these 20 LI localities (they live in GeoNames'LI.txtinstead), so the current R2 file does not have this problem; a naive rebuild from the raw swisstopo file would need the same filter or it would introduce it.BFS-Nris the real official Swiss municipality number (the same numbering BFS's Amtliches Gemeindeverzeichnis uses), not an internal ID — a genuine upgrade in traceability over GeoNames even though (per above) GeoNames' own admin codes for CH turned out to be legitimate too.- 3,190 unique 4-digit postal codes, 2,123 municipalities, all 26
cantons represented — broader and finer-grained than GeoNames'
4,520-row / effectively-locality-level file, because
Zusatzziffer("supplementary digit") captures genuine sub-PLZ subdivisions GeoNames collapses (e.g. Lausanne 25/26/27 as distinct large-account PO subdivisions of PLZ 1000, each with its own coordinate and municipality share). - No Bezirk (district) field. This file gives locality → PLZ →
Gemeinde (BFS-Nr) → Kanton, but nothing at the admin2/district tier —
so it cannot alone reproduce the current file's
admin_name2/admin_code2(Bezirk) columns. BFS's own Amtliches Gemeindeverzeichnis der Schweiz (bfs.admin.ch/.../agvch.html, also distributed via opendata.swiss, "Historisiertes Gemeindeverzeichnis der Schweiz") is the documented source of the Bezirksnummer/Bezirksname crosswalk and would need to be joined in separately — found and its numbering scheme confirmed (canton-number + 2-digit sequence, matching the1901-style codes already seen), but the dataset itself was not fetched/inspected row-by-row in this pass.
GeoNames — fallback baseline, evaluated per the standing rule (checked LAST, not defaulted to)
download.geonames.org/export/zip/CH.zip → 200, CC BY 4.0, 4,520 rows.
Already characterized in full above (§1) since it's also what the current
R2 file is built from. Its admin-code fidelity for CH is unusually good
for this project, but its granularity (no PO-box-style sub-PLZ
subdivisions), unfiltered alternate-names noise, and update cadence
(irregular community-driven refresh vs swisstopo's guaranteed monthly
cycle) are all inferior to the confirmed official source.
Verdict
A real official upgrade exists and was not previously used — recommend
a rebuild from swisstopo's Amtliches Ortschaftenverzeichnis, not a
patch of the current GeoNames-derived file.
This is NOT a "GeoNames is good enough" case like some other
already-audited countries in this project — Switzerland has a
statutorily-mandated, monthly-updated, directly-fetchable, liberally-
licensed (terms_open, commercial use permitted, attribution merely
recommended) official federal source that is finer-grained than what
ships today. The current file's admin-code correctness (both cantons and
what turned out to be real BFS district numbers) is a pleasant surprise
and should be preserved/cross-checked in any rebuild, but the file itself
is stale packaging of a lesser source, not a defensible final answer.
Recommended next step (not executed in this pass — research/audit only):
- Rebuild from swisstopo's
ortschaftenverzeichnis_plz_4326.csv.zip(WGS84 variant) as the postal_code/place_name/canton/municipality backbone. - Filter out the 20 rows with blank
Kantonskürzel(Liechtenstein municipalities under Swiss PLZs 9485–9499) unless a decision is made to ship them undercountry_code: LIin a separate product instead. - Source
admin_name2/admin_code2(Bezirk) from BFS's Amtliches Gemeindeverzeichnis (sameterms_openfamily, needs its own licence check before use) rather than carrying GeoNames' — even though GeoNames' Bezirk numbers checked out as genuine BFS numbers in the sample inspected, this should be verified against the authoritative table directly rather than assumed to hold everywhere. - Fix the
accuracyfloat-formatting bug regardless of source chosen — it is a pure packaging defect. - Write a real
licences:/licence_statusblock (custom:opendata-swiss-terms-open,decision: "unresolved"until a human signs off,channel: "postal-codes-r2") via the shareddatapackage.ymlconvention — replace the currentsources: [{title: "Multiple sources", ...}]placeholder either way.
Not done in this pass, by design: no script written, no
datapackage.yml change, no R2 write, no git commit. This file is the
audit record only.
Build — EXECUTED 2026-09-16 (LOCAL ONLY, nothing published)
Built per the verdict above: official swisstopo source as the backbone, replacing the raw GeoNames pass-through.
Result: 5,974 rows — 5,698 official locality rows + 276 PO-box /
large-account rows. 3,362 distinct PLZ, all 26 cantons, 2,112
municipalities. District tier filled on 5,881/5,974 rows (98.4%).
Frictionless: valid: True (run from inside datasets/ch/data/).
A correction to the audit above, found during the build. The audit concluded swisstopo was "broader and finer-grained" than GeoNames. That was half right. Direct comparison of the two:
- Every one of swisstopo's 3,177 distinct PLZ4 also appears in GeoNames.
- GeoNames carries 185 PLZ that swisstopo has no row for at all.
Inspected directly, those 185 are PO-box and corporate large-account
codes — "Zofingen PF" (Postfach), "Zofingen PostFinance", "Zofingen PF
UBS Verarb.", "Villigen PSI", branch codes like "Aarau 1". They are real
deliverable postal codes; they are simply not localities, which is
exactly why a locality register omits them. A swisstopo-only build would
therefore have been a silent 185-code regression against the file
already shipping. They are carried through as extra rows tagged
code_type = po_box_or_large_account. The union lands at 3,362 distinct
PLZ — exactly matching GeoNames' own count, with official provenance
added rather than coverage traded away.
Second structural finding: the row grain is not one-row-per-code.
swisstopo's file is a locality × municipality table — a locality
straddling a municipal boundary gets one row per municipality plus that
municipality's Adressenanteil (address share). 1,202 of 4,060
localities are split this way (PLZ 1015 "Lausanne" spans Ecublens 84.1%,
Saint-Sulpice 8.3%, Chavannes-près-Renens 7.6%). Those rows are kept,
not collapsed — the 13-column schema has one municipality slot and
picking a winner would discard real data. The share ships as a disclosed
extra column (address_share_pct) so a consumer can reduce to one row
per code themselves. Primary key is (postal_code, place_name,
admin_code3), verified unique across the union.
BFS deliberately not used. BFS's authoritative municipality→district
register was fetched successfully (agvchapp.bfs.admin.ch/api/communes/ levels) but NOT used: its opendata.swiss record declares no licence at
all (license_id: None, every resource rights: None) and BFS's own
terms-of-use pages returned HTTP 500 from this environment, so no
permission could be confirmed. Silence is not permission. The district
tier comes from GeoNames (CC BY 4.0, explicit) instead, joined on the
official BFS municipality number. If BFS's terms are confirmed later,
swapping that join is an isolated change.
Other decisions, all disclosed not guessed: 20 Liechtenstein rows
(PLZ 9485–9499) excluded — separate country, and GeoNames excludes them
too. 3 municipalities (BFS-Nr 304, 2275, 6458) have contradictory
district values in GeoNames after municipality mergers — those ship with
an empty district rather than an arbitrary pick. accuracy empty on
every row (swisstopo carries no accuracy grade; this also retires the
float-4.0 defect that affected 4,169 of the old file's 4,520 rows).
alternative_city_name dropped rather than filled from GeoNames' raw
alternate-names noise. admin_name1 uses official ISO 3166-2:CH names,
not GeoNames' language-inconsistent ones.
licence_status: compatible — swisstopo terms_open (commercial use
permitted, attribution recommended) + GeoNames CC BY 4.0
(attribution-only, no share-alike).
NOT done, by instruction: no R2 upload, no git commit, no
_meta/catalog.json change, no publish_r2.py, and datasets/ch/ datapackage.yml left untouched — it still carries the "Multiple sources" placeholder, no licenses array, no premium_tier, and stale
resource bytes. All of that needs updating before any republish.