Cliente oficial para Python de la API gratuita de códigos postales de Postali: México, Colombia y España.
- Sin API key, sin registro, sin cuota mensual.
- Cero dependencias: sólo la biblioteca estándar (
urllib). Python 3.9+. - Respuestas tipadas como
dataclassesinmutables (py.typedincluido). - Restaura el cero inicial que se pierde en Excel, un CSV o
int()(6700→"06700").
Documentación de la API: https://postali.app/api/docs · Sitio: https://postali.app
pip install postali-apiEl paquete se instala como postali-api y se importa como postali.
from postali import Postali
postali = Postali("mx")
print(postali.cp("06700").asentamientos[0].nombre) # Roma NorteUn endpoint de tu backend (aquí con Flask) que el formulario llama cuando el usuario termina de escribir el CP:
from flask import Flask, jsonify
from postali import Postali, PostaliError
app = Flask(__name__)
postali = Postali("mx")
@app.get("/direccion/<cp>")
def direccion(cp: str):
try:
r = postali.cp(cp) # "6700" también funciona
except PostaliError as e:
if e.code in ("not_found", "invalid_cp"):
return jsonify(error="CP no encontrado"), 404
raise
return jsonify(
estado=r.estado,
municipio=r.municipio,
colonias=[a.nombre for a in r.asentamientos],
)¿Buscas por nombre de colonia en lugar de CP?
for hit in postali.search("roma norte", limit=5).results:
print(hit.cp, hit.nombre, hit.municipio, hit.estado)if not postali.validate("06700"): # ValidateResult es truthy si el CP existe
raise ValueError("Ese código postal no existe.")validate lanza PostaliError con code == "invalid_cp" si el texto ni siquiera tiene formato de CP (p. ej. "abc"), sin hacer la petición.
r = postali.bulk(["06700", "44100", 6700, "abc"])
for item in r.results: # mismo orden que la entrada
print(item.cp, item.valid, item.municipio)
# 06700 True Cuauhtémoc · 44100 True Guadalajara · 06700 True Cuauhtémoc · abc False Nonebulk normaliza cada código, marca los que no tienen formato válido como valid=False y parte la lista en lotes de 100.
Postali("co").cp("050001").municipio # 'Medellín'
Postali("es").cp(8001).municipio # "08001" → 'Barcelona'Postali(country="mx", *, base_url="https://postali.app", timeout=10.0, user_agent=...)
| Método | Endpoint | Devuelve |
|---|---|---|
cp(codigo) |
GET /api/v1/{country}/cp/{codigo} |
CpResult |
validate(codigo) |
GET /api/v1/{country}/validate/{codigo} |
ValidateResult |
search(q, limit=None) |
GET /api/v1/{country}/search?q= |
SearchResult |
estados() |
GET /api/v1/{country}/estados |
EstadosResult |
estado(slug) |
GET /api/v1/{country}/estado/{slug} |
Estado |
municipios(estado_slug) |
GET /api/v1/{country}/estado/{slug}/municipios |
MunicipiosResult |
municipio(estado_slug, municipio_slug) |
GET /api/v1/{country}/municipio/{estado}/{municipio} |
MunicipioResult |
bulk(codigos) |
POST /api/v1/{country}/bulk |
BulkResult |
"Estado" es el nivel 1 de cada país: estado en México, departamento en Colombia, provincia en España. Los campos de cada resultado se llaman igual que en el JSON de la API.
Todo error de la API o de red se lanza como PostaliError, con code, status, message y docs_url:
from postali import PostaliError
try:
postali.cp("00000")
except PostaliError as e:
print(e.code, e.status, e.message) # not_found 404 No se encontró el recurso solicitado.code |
Cuándo |
|---|---|
invalid_cp |
El CP no tiene el formato del país |
invalid_query |
Falta q, está vacía o hay un parámetro inválido |
not_found |
El CP, estado o municipio no existe |
rate_limited |
Demasiadas peticiones desde tu IP |
internal_error |
Error del servidor (5xx) |
timeout |
Se superó el timeout |
network_error |
No se pudo conectar |
http_error |
Otra respuesta no exitosa |
from postali import normalize_cp
normalize_cp("6700") # '06700'
normalize_cp(" 76 148 ") # '76148'
normalize_cp("50001", "co") # '050001'
normalize_cp("670") # None (incompleto)Se añade como máximo un cero, y nunca delante de otro cero: ningún CP de México, Colombia o España empieza por 00.
Datos: Sepomex vía Postali / GeoNames (CC BY 4.0) para CO y ES.
postali is the official zero-dependency Python client (stdlib urllib, Python 3.9+) for the free Postali postal-code API covering Mexico, Colombia and Spain. No API key required.
from postali import Postali
r = Postali("mx").cp("06700") # "mx" | "co" | "es"
print(r.estado, r.municipio, [a.nombre for a in r.asentamientos])Responses are typed frozen dataclasses; errors raise PostaliError with code (invalid_cp, not_found, rate_limited, timeout…), status and docs_url. Postal codes that lost their leading zero (6700) are restored automatically. API reference: https://postali.app/api/docs
Data: Sepomex via Postali / GeoNames (CC BY 4.0) for CO and ES.
MIT © Postali