Validador SPEI · API

API de CEP · Documentación

Integra la validación de transferencias SPEI y la descarga del Comprobante Electrónico de Pago (CEP) oficial de Banco de México en tu propio software — en JSON, PDF, XML o ZIP. API REST, sin SDKs: funciona con curl, fetch o cualquier cliente HTTP.

Pensada para bancos, fintechs, ERPs, conciliadores y tesorerías que necesitan obtener los CEP de sus operaciones automáticamente, sin operar a mano el formulario web de Banxico. Tú envías los datos de la transferencia; nosotros consultamos a Banxico y te devolvemos el comprobante.

Flujo típico de integración: 1) lista los bancos una vez para mapear los códigos → 2) valida la transferencia → 3) descarga el CEP con el mismo cuerpo JSON. Pruébalo en vivo en /docs (Swagger).

Primeros pasos

De cero a validar un CEP en tres pasos. Usa los datos de tu comprobante SPEI (fecha, clave de rastreo, montos y cuentas).

Todos los datos de ejemplo de esta página son ficticios (tienen la forma correcta, pero no corresponden a ninguna transferencia real): con ellos Banxico responde 404 not_found. Sustitúyelos por los de tu operación.

1 · Obtén los códigos de banco

Lista las instituciones para conocer el code del emisor y receptor.

curl $BASE/api/banks
# [{ "code": "40012", "name": "BBVA MEXICO" }, { "code": "40014", "name": "SANTANDER" }, ...]

2 · Valida la transferencia

Envía los datos del pago y recibe el CEP en JSON.

curl -X POST $BASE/api/cep/validate \
  -H "Content-Type: application/json" \
  -d '{
    "fecha": "2026-09-25",
    "clave_rastreo": "EJEMPLO2026092500001",
    "emisor": "40014",
    "receptor": "40012",
    "cuenta": "012180015555555550",
    "monto": "1500.00"
  }'

3 · Descarga el comprobante

Con el mismo cuerpo JSON, baja el PDF oficial (o XML / ZIP). Esta es la vía recomendada para integraciones servidor-a-servidor.

curl -X POST "$BASE/api/cep/download?formato=PDF" \
  -H "Content-Type: application/json" \
  -d '{
    "fecha": "2026-09-25",
    "clave_rastreo": "EJEMPLO2026092500001",
    "emisor": "40014",
    "receptor": "40012",
    "cuenta": "012180015555555550",
    "monto": "1500.00"
  }' \
  -o cep.pdf
¿Tienes el PDF o una foto del comprobante? Súbelo y extraemos los datos automáticamente con POST /api/cep/batch.

Base URL

Todas las rutas son relativas a tu host. En estos ejemplos:

http://localhost:8000

Autenticación

Los endpoints de CEP aceptan una API key que identifica a tu integración. Envíala siempre que la tengas; cuando el servicio la requiere, es obligatoria. Los endpoints /api/health, /api/ready y /api/banks son siempre públicos. El campo auth de /api/health indica si el servidor exige una llave.

Puedes enviar la llave de tres formas (en orden de preferencia):

MétodoEjemplo
Header X-API-KeyX-API-Key: TU_API_KEY
Bearer tokenAuthorization: Bearer TU_API_KEY
Query param (para descargas)?api_key=TU_API_KEY
Una llave inválida responde 401 unauthorized. Los límites de uso son por IP, no por llave: una llave válida no amplía el límite. ¿Necesitas una API key para tu software? Solicítala en ceppro.mx.

GET/api/health

Health

Verifica que el servicio está disponible.

curl $BASE/api/health

Respuesta 200

{ "status": "ok", "version": "0.1.0", "auth": false }

auth es true cuando el servidor exige una API key en los endpoints de CEP.

GET /api/ready (readiness) responde 200 cuando los servicios de apoyo funcionan y 503 cuando Redis está configurado pero no responde. Úsalo para balanceadores y monitoreo; para saber si el proceso vive basta /api/health.


GET/api/banks

Listar bancos

Devuelve el catálogo de instituciones participantes en SPEI. Usa los code para los campos emisor y receptor. La respuesta se cachea (1 h) en el navegador.

curl $BASE/api/banks

Respuesta 200

[
  { "code": "40002", "name": "BANAMEX" },
  { "code": "40012", "name": "BBVA MEXICO" },
  { "code": "40014", "name": "SANTANDER" }
]

POST/api/cep/validate

Validar CEP

Valida una transferencia contra Banxico y devuelve los datos estructurados del comprobante en JSON. Ver parámetros para el detalle de campos.

curl -X POST $BASE/api/cep/validate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: TU_API_KEY" \
  -d '{
    "fecha": "2026-09-25",
    "clave_rastreo": "EJEMPLO2026092500001",
    "emisor": "40014",
    "receptor": "40012",
    "cuenta": "012180015555555550",
    "monto": "1500.00"
  }'

Respuesta 200

{
  "clave_rastreo": "EJEMPLO2026092500001",
  "fecha_operacion": "20260925",
  "hora_operacion": "12:00:00",
  "monto": "1500.00",
  "iva": "0.00",
  "concepto": "PAGO DE EJEMPLO",
  "emisor": "SANTANDER",
  "receptor": "BBVA MEXICO",
  "ordenante_nombre": "JUAN PEREZ EJEMPLO",
  "ordenante_cuenta": "014180000000000013",
  "ordenante_rfc": "XAXX010101000",
  "beneficiario_nombre": "EMPRESA DE EJEMPLO SA DE CV",
  "beneficiario_cuenta": "012180015555555550",
  "beneficiario_rfc": "XAXX010101000",
  "sello": "U0VMTE8gREUgRUpFTVBMTw==..."
}

Un CEP encontrado es el comprobante de que el pago se acreditó, así que la respuesta no incluye el estado del pago. Cuando Banxico muestra el estado en lugar del CEP (p. ej. Devuelto, o Liquidado antes de emitir el CEP), la respuesta es un 409 con ese estado.

Tiempo de respuesta: una consulta nueva tarda unos segundos (Banxico se consulta a un ritmo controlado, compartido por todos los usuarios). En cuanto la validación responde, el servidor descarga el PDF de esa misma transferencia en segundo plano, así que una descarga inmediatamente posterior suele salir de la caché. Usa un timeout de cliente de al menos 60 s: el servidor deja de reintentar a los 40 s y, si la cola hacia Banxico está llena, responde 503 busy de inmediato. Si llegan varias solicitudes de la misma transferencia a la vez, Banxico se consulta una sola vez y todas reciben el mismo resultado.


POSTGET/api/cep/download

Descargar CEP

Valida y devuelve el comprobante oficial como archivo binario. Elige el formato con el parámetro de query formato = PDF · XML · ZIP (por defecto PDF). Hay dos formas, con los mismos datos que validar:

  • POST (recomendado para integraciones) — cuerpo JSON, igual que validate. Sin codificar nada en la URL.
  • GET — todos los datos en la query string. Útil para enlaces de descarga directos que el navegador puede abrir.
# POST (recomendado): mismo cuerpo JSON que validate
curl -X POST "$BASE/api/cep/download?formato=PDF" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: TU_API_KEY" \
  -d '{
    "fecha": "2026-09-25",
    "clave_rastreo": "EJEMPLO2026092500001",
    "emisor": "40014",
    "receptor": "40012",
    "cuenta": "012180015555555550",
    "monto": "1500.00"
  }' \
  -o cep.pdf

# GET: enlace directo (descargable en el navegador)
curl -G $BASE/api/cep/download \
  --data-urlencode "fecha=2026-09-25" \
  --data-urlencode "clave_rastreo=EJEMPLO2026092500001" \
  --data-urlencode "emisor=40014" \
  --data-urlencode "receptor=40012" \
  --data-urlencode "cuenta=012180015555555550" \
  --data-urlencode "monto=1500.00" \
  --data-urlencode "formato=PDF" \
  --data-urlencode "api_key=TU_API_KEY" \
  -o cep.pdf

Respuesta: el archivo binario con Content-Type application/pdf · application/xml · application/zip y Content-Disposition: attachment. El ZIP de Banxico contiene el PDF y el XML del mismo CEP. Los errores son los mismos que en validar (ver códigos de error).


POST/api/cep/download-zip

Varios CEP en un ZIP

Recibe hasta 10 transferencias —cada una con los mismos campos que validar— y devuelve un solo archivo ZIP con el CEP de cada una en el formato indicado por query (PDF por defecto, o XML / ZIP). Útil para integraciones que quieren un solo archivo con varios CEP.

curl -X POST "$BASE/api/cep/download-zip?formato=PDF" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: TU_API_KEY" \
  -d '{ "items": [ {
    "fecha": "2026-09-25",
    "clave_rastreo": "EJEMPLO2026092500001",
    "emisor": "40014",
    "receptor": "40012",
    "cuenta": "012180015555555550",
    "monto": "1500.00"
  } ] }' \
  -o ceps.zip
  • Respuesta 200: application/zip (ceps.zip). Las transferencias que fallen se omiten, y el encabezado X-CEP-Omitidos dice cuántas (no hace falta comparar el contenido). Si a mitad del ZIP la cola hacia Banxico se llena o aparece un captcha, las restantes no se intentan y también cuentan en X-CEP-Omitidos.
  • Si ninguna se pudo generar, el código refleja la causa: 404 not_found (ninguna encontrada), 409 (cep_pending/cep_status), 502 banxico_unavailable o 503 (captcha_enforced/busy; reintenta). Una causa transitoria tiene prioridad sobre «no encontrada».
  • 422 con más de 10 transferencias o datos inválidos.
  • 503 busy si el servidor ya está procesando el máximo de lotes o la cola hacia Banxico está llena; reintenta tras el Retry-After.

Cada transferencia se consulta a Banxico al ritmo compartido, así que un ZIP de 10 CEP nuevos puede tardar minutos: usa un timeout amplio. Comparte el límite por IP de los lotes (6/min por defecto).


POST/api/cep/batch

Lote por archivos

Sube hasta 10 archivos (PDF o imagen, máx. 8 MB cada uno). El servicio extrae los campos de cada archivo (texto de PDF o OCR de imagen) y valida cada comprobante contra Banxico. Se envía como multipart/form-data con el campo repetido files.

La herramienta de lotes de Banxico (CEP-SCL) está protegida con reCAPTCHA y no se puede automatizar, así que este endpoint ejecuta la consulta individual para cada comprobante. La extracción es de mejor esfuerzo: un campo mal leído simplemente devuelve not_found para ese resultado.
curl -X POST $BASE/api/cep/batch \
  -H "X-API-Key: TU_API_KEY" \
  -F "files=@comprobante1.pdf" \
  -F "files=@captura2.png"

Respuesta 200

{
  "items": [
    {
      "filename": "comprobante1.pdf",
      "source_index": 0,
      "status": "valid",          // valid | pending | no_cep | needs_input | not_found | extract_failed | error
      "request": { "fecha": "2026-09-25", "clave_rastreo": "...", "...": "..." },
      "details": { "beneficiario_nombre": "...", "monto": "1500.00" }
    },
    { "filename": "captura2.png", "source_index": 1, "status": "not_found", "message": "..." }
  ]
}

Cómo leer items

  • Uno o más resultados por archivo, en orden de subida. Un PDF de dispersión con un comprobante por página produce un resultado por página, con filename "archivo.pdf (p. N)". Usa source_index (índice base 0 del archivo subido) para asociar cada resultado a su archivo; dos archivos pueden llamarse igual.
  • Ninguna página se descarta en silencio: una página con su propia clave de rastreo pero sin todos los datos aparece como needs_input (ver missing_fields); una página sin clave propia se toma como continuación de la anterior.
  • Hasta 20 comprobantes consultados por lote. Cuenta cada resultado que podría consultar a Banxico (los completos y los needs_input). Los que exceden el límite llegan como error con el mensaje «Se alcanzó el límite de 20 comprobantes por lote», sin consultar a Banxico: envíalos en otro lote. Por eso items puede tener más resultados que archivos.
  • pending y no_cep traen estado (como los 409 de validar). Un error es reintentable (p. ej. Banxico saturado o no disponible).

Descarga los archivos de cada valid con /api/cep/download usando los campos de request (normalmente ya están en caché). Un lote de muchos comprobantes nuevos puede tardar minutos; si quieres mostrar progreso usa la versión en streaming. Respuestas de error: 401, 422 (sin archivos o más de 10), 429 y 503 busy.


POST/api/cep/batch/stream

Lote en streaming

Igual que /api/cep/batch (mismos archivos, límites y errores), pero la respuesta es application/x-ndjson: un objeto JSON por línea, enviado en cuanto cada comprobante termina, en orden de subida. Cada línea tiene la misma forma que un elemento de items, con source_index. Sirve para mostrar progreso en vivo.

curl -N -X POST $BASE/api/cep/batch/stream \
  -H "X-API-Key: TU_API_KEY" \
  -F "files=@comprobante1.pdf" \
  -F "files=@captura2.png"

Los errores previos al inicio (401, 422, 429, 503 busy) llegan como JSON normal, no como NDJSON. Una vez iniciado el stream siempre responde 200; si un archivo falla, su línea trae status: "error".


Parámetros

Todos son obligatorios. Para validate y POST download van en el cuerpo JSON; para GET download, como query string. formato siempre va como parámetro de query.

CampoTipoDescripción
fechastringREQFecha de operación, formato YYYY-MM-DD.
clave_rastreostringREQClave de rastreo o número de referencia (≤ 40 caracteres). Se detecta automáticamente: solo dígitos y ≤ 7 → referencia (si Banxico no la encuentra así, se reintenta una vez como clave de rastreo; si ese reintento no alcanza turno en la cola, responde 503 busy y, al repetir la solicitud en los siguientes minutos, se busca directamente como clave de rastreo); en otro caso → clave de rastreo.
emisorstringREQCódigo del banco emisor (ver /api/banks).
receptorstringREQCódigo del banco receptor.
cuentastringREQCuenta beneficiaria: CLABE (18 dígitos), tarjeta (16) o teléfono (10). Se eliminan espacios y guiones; el resto debe tener 18 dígitos (CLABE), 16 (tarjeta) o 10 (celular); si no, 422. El dígito verificador de la CLABE no se valida: Banxico decide si la transferencia existe.
montostringREQMonto en pesos, mayor que 0 y con hasta 2 decimales, p. ej. 1500.00.
formatostringOPCSolo download y download-zip: PDF (default), XML o ZIP.

Un campo que solo contiene espacios cuenta como faltante (422).


Códigos de error

Los errores devuelven JSON con error (código legible por máquina) y detail (mensaje en español). La excepción es el 422, que tiene dos formas y tu cliente debe aceptar ambas:

  • Validación de esquema (cuerpo JSON inválido, o un parámetro de query con tipo o longitud incorrectos): el formato estándar de FastAPI, sin error: {"detail": [{"type", "loc", "msg", …}]}.
  • Validaciones propias (p. ej. monto o datos inválidos en GET /api/cep/download, lote con más de 10 archivos): {"error": "invalid_request", "detail": "…"}.
HTTPerrorSignificado
401unauthorizedAPI key faltante o inválida (cuando el servidor tiene auth activada).
404not_foundBanxico no encontró una transferencia con esos datos (en download-zip: no se encontró ninguna; otras causas usan su propio código).
409cep_pendingBanxico identificó el pago, pero el CEP aún no está disponible (reintenta más tarde). Incluye estado (p. ej. Liquidado, En proceso) cuando Banxico lo muestra.
409cep_statusBanxico encontró la operación, pero está en un estado sin CEP descargable: Devuelto, Retornado, Cancelado, Rechazado, No liquidado o En proceso de devolución (ver estado).
422— / invalid_requestParámetros inválidos o faltantes (ver las dos formas arriba), p. ej. una cuenta que no tiene 18, 16 ni 10 dígitos.
413payload_too_largeCuerpo mayor a 1 MB (90 MB en /api/cep/batch*). Si lo corta el proxy, la respuesta puede no ser JSON.
429rate_limitedDemasiadas solicitudes desde tu IP (por defecto 30/min por endpoint en consultas que no están en caché y en bancos; 120/min en total en consultas, incluidas las de caché; 6/min compartidos entre lotes y ZIP); reintenta con backoff.
502banxico_unavailableBanxico no respondió, lo rechazó temporalmente (403/429) o devolvió un error transitorio, incluso después de los reintentos.
503captcha_enforcedBanxico exige captcha (rate-limiting); reintenta más tarde.
503busyServicio saturado: la cola compartida hacia Banxico está llena, o el servidor ya atiende el máximo de consultas o lotes simultáneos. No se consultó a Banxico (salvo cuando el detail indica que la búsqueda por número de referencia no encontró la transferencia y la búsqueda por clave de rastreo quedó en espera: al reintentar se busca directamente por clave de rastreo); incluye Retry-After: 10 — respétalo. Aplica a todos los endpoints de CEP.

Ejemplo 404

{ "error": "not_found", "detail": "Banxico no encontró una transferencia que coincida con los datos proporcionados." }

Ejemplo 409 con estado

Cuando Banxico muestra el estado del pago en lugar del CEP, la respuesta lo incluye tal como aparece en Banxico, junto con sus fechas (fecha_recepcion y fecha_procesamiento) cuando Banxico las muestra.

{
  "error": "cep_status",
  "estado": "Devuelto",
  "fecha_recepcion": "25/09/2026 12:00:00",
  "fecha_procesamiento": "25/09/2026 12:00:03",
  "detail": "Estado del pago en Banxico: Devuelto. La institución del beneficiario devolvió el pago; …"
}

Ejemplo 503 busy

{ "error": "busy", "detail": "Banxico está muy solicitado en este momento; intenta de nuevo en unos segundos." }

Límites de uso e idempotencia

Banxico aplica rate-limiting por IP a su servicio de CEP. La API lo mitiga con una cola de consultas a ritmo controlado (compartida por todos los usuarios), reintentos con backoff y caché de comprobantes (los CEP son inmutables, así que repetir la misma consulta dentro de las 24 h siguientes es instantáneo, no vuelve a golpear a Banxico y no cuenta para el límite de 30 por minuto). Cuando la cola está llena, la API responde 503 busy de inmediato en lugar de hacerte esperar.

LímiteValor por defecto
Consultas que pueden llegar a Banxico (no están en caché), por IP y por endpoint (validate tiene su propio límite; download GET y POST comparten uno; /api/banks tiene otro, donde cuenta toda solicitud)30 por minuto
Todas las consultas (validate y download), incluidas las que salen de la caché, por IP120 por minuto
Lotes y ZIP (batch, batch/stream, download-zip), por IP6 por minuto
Archivos por lote · comprobantes consultados por lote10 · 20
Transferencias por download-zip10
Tamaño por archivo (lote)8 MB
Una consulta individual (con reintentos) no inicia nuevas peticiones a Banxico después de~40 s (más el tiempo de respuesta de Banxico)

Recomendaciones para integrar

  • Trata cada consulta como idempotente: validar o descargar la misma transferencia siempre devuelve el mismo CEP. Puedes reintentar sin efectos secundarios.
  • Reintenta con backoff exponencial ante 429 rate_limited, 502 banxico_unavailable, 503 captcha_enforced y 503 busy (p. ej. 2 s, 4 s, 8 s); en busy respeta el Retry-After (10 s).
  • Usa un timeout de al menos 60 s en consultas individuales: una consulta nueva puede esperar su turno en la cola hacia Banxico. Para lotes y ZIP, usa uno de varios minutos (o el streaming).
  • Los endpoints de lote y ZIP (/api/cep/batch, /api/cep/batch/stream, /api/cep/download-zip) tienen un límite por IP más estricto que las consultas individuales, porque cada uno consulta hasta 20 CEP (lote) o 10 (ZIP). Procesa los lotes de forma secuencial, no en paralelo.
  • 409 cep_pending significa que el pago existe pero el CEP aún no está listo (el campo estado dice en qué punto va, p. ej. Liquidado o En proceso): reintenta más tarde (minutos/horas), no de inmediato.
  • 409 cep_status significa que la operación existe pero está en un estado sin CEP descargable (p. ej. Devuelto): no reintentes, revisa el campo estado y el detail que Banxico reporta.
  • Evita ráfagas concurrentes desde una sola IP; encola y procesa a un ritmo moderado para no disparar el captcha de Banxico.
Volumen alto: para flujos sostenidos (cientos por hora), apóyate en la caché, encola las solicitudes y mantén un ritmo moderado. Si necesitas un límite mayor o una API key dedicada, escríbenos desde ceppro.mx.