Skip to main content

postali

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 dataclasses inmutables (py.typed incluido).
  • 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

Instalación

pip install postali-api

El paquete se instala como postali-api y se importa como postali.

Uso rápido

from postali import Postali

postali = Postali("mx")
print(postali.cp("06700").asentamientos[0].nombre)  # Roma Norte

Ejemplos

Autocompletar la colonia en un formulario de dirección

Un 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)

Validar un CP

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.

Limpiar una columna de CPs (CSV / pandas)

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 None

bulk normaliza cada código, marca los que no tienen formato válido como valid=False y parte la lista en lotes de 100.

Colombia y España

Postali("co").cp("050001").municipio   # 'Medellín'
Postali("es").cp(8001).municipio       # "08001" → 'Barcelona'

API

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.

Errores

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

Normalización de CPs

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 y atribución

Datos: Sepomex vía Postali / GeoNames (CC BY 4.0) para CO y ES.

English

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.

Licencia

MIT © Postali

Metadata

Release files for postali-api 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for postali-api 0.1.0
File Size Uploaded
postali_api-0.1.0.tar.gz 13.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for postali-api 0.1.0
File Interpreter ABI Platform
postali_api-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 25.6 kB

Release files / postali_api-0.1.0.tar.gz

Download URL postali_api-0.1.0.tar.gz
Size 13.3 kB
Tags Source
SHA-256 checksum
How to use checksums
24aec20f95c4b92443466cfff0e62c2424d4fafec49fbb690ba59d540c37e58e
BLAKE2b-256 checksum
How to use checksums
fb7e16e0034f50567d443d825f21f77a2d9fda12fb821d612f7c3f8a137414aa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log

Release files / postali_api-0.1.0-py3-none-any.whl

Download URL postali_api-0.1.0-py3-none-any.whl
Size 12.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5fa91526583c22feba8fe513e84cf77a774885ddbe09377db53f4609c319a3d9
BLAKE2b-256 checksum
How to use checksums
8e4b7c47a764c093d53581d88076b0bbad92fbbf3cc5a96817d7f23f17aa2844
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page