DEV Community

ValidatorAPI
ValidatorAPI

Posted on

Spanish Bank Codes and Postal Codes: Free Lookup Tables for Developers (2026)

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:

  1. A bank code lookup table (what bank does 2100 in an IBAN mean?)
  2. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)