Working with Spanish IBANs or building an address form for Spain?
You probably need two things that are surprisingly hard to find in a clean, developer-ready format:
-
A bank code lookup table (what bank does
2100in an IBAN mean?) -
A province/postal code reference (what province is
28001? What autonomous community?)
I've compiled both from public sources (Banco de España, INE, Correos) into CSV and JSON files you can drop straight into your project.
Product 1: Spanish Bank Codes Database
What it solves
A Spanish IBAN looks like this:
ES91 2100 0418 4502 0005 1332
^^^^
Bank code = 2100 = CaixaBank
Positions 5–8 of a Spanish IBAN are the bank code — a 4-digit identifier registered with Banco de España.
There's no free, structured database of these codes anywhere. The official source is a PDF.
What's in the file
95 bank records covering:
- Big 5: Santander, BBVA, CaixaBank, Sabadell, Bankinter
- Online banks: Openbank, WiZink, EVO Banco, Revolut Spain
- Foreign branches: Deutsche Bank, BNP Paribas, Citibank, Barclays
- Rural savings banks: Cajamar, Caja Rural de Navarra, Globalcaja...
- Historic/absorbed banks: Banesto, Bankia, Banco Popular (useful for old IBANs)
Fields: bank_code, official_name, common_name, swift_bic, website, type
Code example
import json
with open('spanish_banks.json') as f:
banks = {b['bank_code']: b for b in json.load(f)}
iban = 'ES9121000418450200051332'
bank_code = iban[4:8]
print(banks[bank_code]['common_name']) # CaixaBank
print(banks[bank_code]['swift_bic']) # CAHMESMMXXX
const banks = require('./spanish_banks.json');
const lookup = Object.fromEntries(banks.map(b => [b.bank_code, b]));
const iban = 'ES9121000418450200051332';
const bank = lookup[iban.slice(4, 8)];
console.log(bank.common_name); // CaixaBank
Available on Gumroad for $19 → Spanish Bank Codes Database
Product 2: Spanish Provinces & Postal Codes Database
What it solves
Spanish postal codes encode the province in their first 2 digits:
28001 = Province 28 = Madrid
08001 = Province 08 = Barcelona
41001 = Province 41 = Sevilla
But to build a province dropdown, or map a postal code to its autonomous community, you need a lookup table that ties everything together.
What's in the files (5 files)
| File | Rows | Contents |
|---|---|---|
spanish_communities.csv |
19 | Autonomous communities + cities |
spanish_provinces.csv |
52 | INE codes, community mapping, capitals |
spanish_major_cities.csv |
60 | Cities with population tier |
spanish_postal_lookup.csv |
675 | One row per postal code |
spanish_postal_data.json |
— | All data combined |
Fields: provinces
ine_code → "28"
postal_prefix → "28"
province_name → "Madrid"
community_name → "Comunidad de Madrid"
capital → "Madrid"
Code examples
import csv
provinces = {
row['postal_prefix']: row
for row in csv.DictReader(open('spanish_provinces.csv', encoding='utf-8'))
}
postal = '08001'
p = provinces[postal[:2]]
print(p['province_name']) # Barcelona
print(p['community_name']) # Cataluña
# Full postal code lookup
lookup = {
row['postal_code']: row
for row in csv.DictReader(open('spanish_postal_lookup.csv', encoding='utf-8'))
}
print(lookup['28001']['city']) # Madrid
print(lookup['08001']['community']) # Cataluña
const data = require('./spanish_postal_data.json');
const provinces = Object.fromEntries(data.provinces.map(p => [p.postal_prefix, p]));
console.log(provinces['41'].province_name); // Sevilla
console.log(provinces['41'].community_name); // Andalucía
Available on Gumroad for $24 → Spanish Provinces & Postal Codes Database
Why not just build this yourself?
You could. The data is technically public. But:
- Banco de España's bank registry is a formatted PDF
- INE's postal code data requires parsing their bulk download + cleaning encoding errors
- Correos' postal system doesn't expose a public API or clean CSV
For most projects, the hour+ of scraping, cleaning, and testing isn't worth it when the lookup table itself only needs to be done once.
Free alternative: the province-from-postal trick
If you only need to identify the province from a postal code — and don't need the full database — you can hardcode just the 52 prefix mappings:
PROVINCE_PREFIXES = {
"01": "Álava", "02": "Albacete", "03": "Alicante",
"04": "Almería", "05": "Ávila", "06": "Badajoz",
"07": "Illes Balears", "08": "Barcelona", "09": "Burgos",
"10": "Cáceres", "11": "Cádiz", "12": "Castellón",
"13": "Ciudad Real", "14": "Córdoba", "15": "A Coruña",
"16": "Cuenca", "17": "Girona", "18": "Granada",
"19": "Guadalajara", "20": "Gipuzkoa", "21": "Huelva",
"22": "Huesca", "23": "Jaén", "24": "León",
"25": "Lleida", "26": "La Rioja", "27": "Lugo",
"28": "Madrid", "29": "Málaga", "30": "Murcia",
"31": "Navarra", "32": "Ourense", "33": "Asturias",
"34": "Palencia", "35": "Las Palmas", "36": "Pontevedra",
"37": "Salamanca", "38": "S.C. de Tenerife", "39": "Cantabria",
"40": "Segovia", "41": "Sevilla", "42": "Soria",
"43": "Tarragona", "44": "Teruel", "45": "Toledo",
"46": "Valencia", "47": "Valladolid", "48": "Bizkaia",
"49": "Zamora", "50": "Zaragoza", "51": "Ceuta", "52": "Melilla",
}
def province_from_postal(postal_code: str) -> str:
return PROVINCE_PREFIXES.get(postal_code[:2], "Unknown")
print(province_from_postal("28001")) # Madrid
print(province_from_postal("08041")) # Barcelona
If you need community mapping, city names, or the full postal lookup — that's what the dataset is for.
Also check out the free Spanish Document Validator API for NIF/NIE/CIF/IBAN validation.
Top comments (0)