veriko · SDK de Python para la API de Veriko
Cliente oficial de Veriko para Python. Valida transferencias SPEI mexicanas contra el CEP de Banco de México, descarga el comprobante oficial y verifica la firma de los webhooks.
Sin dependencias de runtime. Python 3.9 o superior.
from veriko import Veriko
client = Veriko() # lee VERIKO_API_KEY del entorno
validation = client.validate_transfer(
fecha="2025-03-15",
monto=15000.50,
clave_rastreo="MXBA20250315001234",
cuenta_beneficiaria="012180004412345678",
)
print(validation.status) # 'valid'
Qué es el CEP
El Comprobante Electrónico de Pago (CEP) es el documento que expide el Banco de México (Banxico) por cada transferencia que pasa por el SPEI, el sistema de pagos interbancarios mexicano. Contiene el sello digital y la cadena original de la institución receptora, de modo que acredita que una transferencia ocurrió y por qué importe.
El CEP no tiene efectos fiscales: no sustituye a una factura.
Su consulta manual se hace en el portal de Banxico, un comprobante a la vez.
Qué hace la API
La API consulta el CEP y devuelve un veredicto en el campo status:
| veredicto | significado |
|---|---|
valid |
Banxico devolvió un CEP que coincide con los datos enviados |
not_found |
Banxico no tiene un CEP con esos datos. Puede ser temporal: un CEP tarda en publicarse |
cep_unavailable |
Banxico no pudo responder. No dice nada sobre la transferencia |
returned |
El pago se liquidó y la institución beneficiaria lo devolvió después |
invalid |
Los datos enviados no forman una consulta válida |
error |
Fallo durante el procesamiento; el motivo viaja en error_code |
Con veredicto valid, el comprobante queda disponible en XML y en PDF.
Las familias de operaciones
El cliente agrupa las 66 operaciones M2M de la API en once familias:
| familia | qué cubre |
|---|---|
client.validations |
Validar por campos o por imagen, consultar, listar, exportar, la política de reintentos y la descarga del comprobante |
client.webhooks |
Registrar endpoints, rotar su secreto, enviar un evento de prueba y leer el historial de entregas |
client.catalog |
Catálogo de bancos SPEI, banco emisor de una tarjeta y estado del servicio de Banxico |
client.beneficiaries |
Cuentas beneficiarias guardadas y la importación masiva, como ciclo completo |
client.usage |
Cuota de validaciones, límites de tasa y registro de actividad de la API |
client.account |
Perfil y política de reintentos predeterminada de la cuenta |
client.dashboard |
Resumen del panel |
client.plans |
Catálogo y comparación de planes públicos, sin clave de API |
client.insights |
Resumen, tendencias, bancos y beneficiarios principales |
client.finance |
Resumen, estado de cuenta, vistas previas y descargas financieras |
client.billing |
Suscripción activa |
Las tres operaciones de uso más frecuente están también en la raíz del cliente,
como atajo: validate_transfer(), get_validation() y get_cep().
Instalación
pip install veriko
Mientras la versión sea 0.x, una versión menor puede ajustar la superficie pública del SDK.
La versión 1.0.0 se reservará para una integración estable en producción.
Autenticación
La clave de API se obtiene en el panel (app.veriko.mx) y empieza con
veriko_. El cliente la toma de la variable de entorno VERIKO_API_KEY:
export VERIKO_API_KEY=veriko_tu_clave_aqui
Sólo client.plans.list_public() y client.plans.get_public_plan_comparison() se pueden usar sin
clave y no envían Authorization. El resto conserva la autenticación de la API: una petición sin
clave falla localmente antes de salir a la red.
El argumento api_key la recibe directamente cuando la gestiona otro mecanismo:
client = Veriko(api_key="veriko_tu_clave_aqui")
Validar una transferencia
La operación exige la fecha de envío, el importe y la clave de rastreo o la referencia numérica. Enviar las dos precisa la búsqueda. El banco emisor, el receptor y la cuenta beneficiaria son opcionales y mejoran la identificación.
from veriko import Veriko
client = Veriko()
validation = client.validate_transfer(
fecha="2025-03-15", # YYYY-MM-DD, la fecha de envío
monto=15000.50, # en pesos, con centavos
clave_rastreo="MXBA20250315001234",
referencia_numerica="1234567",
emisor="BANCO NACIONAL DE MEXICO",
receptor="BBVA MEXICO",
cuenta_beneficiaria="012180004412345678", # CLABE, tarjeta o celular DiMo
)
if validation.status == "valid":
print("Pago confirmado en", validation.processing_time_ms, "ms")
elif validation.status == "not_found":
print("Banxico no encuentra el pago todavía")
Los argumentos conservan el nombre que viaja en el JSON de la API.
Cada llamada consume cuota del plan, y se descuenta al aceptar la petición.
Desde la imagen del comprobante
validation = client.validations.validate_ocr(
image="comprobante.png", # ruta, bytes o un objeto Path
cuenta_beneficiaria="012180004412345678", # obligatoria para celular DiMo
)
El SDK lee el archivo y lo codifica en base64. image_url sirve para una imagen
ya publicada en HTTPS. Formatos: JPEG, PNG o WebP.
Sin esperar al veredicto
Para volumen, la API acepta la petición y responde con el identificador:
queued = client.validations.enqueue(
fecha="2025-03-15",
monto=15000.50,
clave_rastreo="MXBA20250315001234",
)
validation = client.validations.wait_for(queued.id) # sondea hasta el veredicto
wait_for() manda el ETag de la respuesta anterior en cada vuelta, así que un
sondeo que no encuentra cambios no descarga otra vez el mismo cuerpo. Espera a
que el veredicto quede firme, no sólo a que el estado sea terminal: una
validación con reintentos en marcha llega a not_found y sigue cambiando
después. Esa distinción es Validation.is_settled.
La alternativa a sondear es suscribirse al webhook validation.completed.
Listar y recorrer el historial
pagina = client.validations.list(status="valid", per_page=50)
print(pagina.total, "validaciones,", pagina.total_pages, "páginas")
for validation in client.validations.iter(from_="2025-03-01", to="2025-03-31"):
print(validation.id, validation.status)
iter() pide la página siguiente sólo cuando la anterior se agota. El filtro de
fecha es from_ con guion bajo, porque from es palabra reservada de Python;
viaja como from.
status acepta un estado o una lista. with_deleted=True devuelve sólo las validaciones retiradas
y False sólo las activas, y playground=True limita a las del banco de pruebas.
Un listado trae menos campos que get(): no incluye los datos enviados, el resultado de Banxico ni
los enlaces al comprobante, y por eso ValidationSummary no tiene has_cep.
El historial se exporta entero con client.validations.export(format="csv"), que
admite también xlsx.
Descargar el CEP
cep = client.get_cep(validation.id, format="pdf") # o format="xml"
cep.write_to(cep.filename) # CEP-<id>.pdf
get_cep() devuelve el archivo: el XML que emitió Banxico, con su sello digital y su cadena
original, o el PDF equivalente. Validation.has_cep indica si existe antes de pedirlo; cuando no,
la API responde 404 con cep_not_available y el SDK lanza NotFoundError.
Registrar un webhook
endpoint = client.webhooks.create(
url="https://miapp.example.com/hooks/pagos",
events=["validation.completed"],
)
print(endpoint.secret) # 64 hex
El secreto de firma viaja una sola vez, en esta respuesta. Si se pierde,
client.webhooks.regenerate_secret(endpoint.id) devuelve uno nuevo, y el
anterior deja de valer.
client.webhooks.test(endpoint.id) manda un evento de prueba y dice si el
receptor lo aceptó. Las entregas de prueba no cuentan para el contador de fallos
consecutivos que apaga un endpoint a los tres seguidos; cuando eso pasa, el
estado queda en auto_disabled y se reactiva con
client.webhooks.update(id, status="active").
El historial de intentos está en client.webhooks.deliveries(), con o sin
identificador de endpoint. Los filtros status y event_type sólo existen en el listado global:
con un endpoint y un filtro, el SDK consulta ese listado con endpoint_id.
create() y update() aceptan description, una etiqueta libre que update(id, description=None)
borra.
Catálogo y estado de Banxico
bancos = client.catalog.banks() # instituciones SPEI con su código
tarjeta = client.catalog.bin_lookup("455632") # banco emisor de una tarjeta
estado = client.catalog.banxico_status()
print(estado["status"]) # 'operational'
banxico_status() sirve para distinguir un cep_unavailable propio de la transferencia de una caída
del servicio, y banxico_timeseries() devuelve la serie de latencia o de veredictos por ventana. Las
consultas que devuelven un recurso sin modelo propio (stats(), bin_lookup(), banxico_status(),
banxico_timeseries()) entregan sus atributos como diccionario.
Lecturas condicionales
client.validations.get() y client.catalog.banks() admiten if_none_match. Cada lectura trae su
ETag (Validation.etag, BankList.etag), y cuando nada cambió la API responde 304, que el SDK
levanta como APIError con status=304.
Verificar la firma de un webhook
Cada entrega llega firmada con HMAC-SHA256 del cuerpo, usando el secreto que devolvió
POST /v1/webhooks al registrar el endpoint:
X-Webhook-Signature: sha256=<hex>
Lo que se firma es el cuerpo tal como llegó. Interpretar el JSON y volver a serializarlo cambia los bytes y la firma deja de cuadrar.
import os
from flask import Flask, request
from veriko import parse_webhook, SignatureVerificationError
app = Flask(__name__)
SECRET = os.environ["VERIKO_WEBHOOK_SECRET"]
@app.post("/hooks/veriko")
def recibir():
try:
evento = parse_webhook(
request.get_data(), # el cuerpo crudo
request.headers.get("X-Webhook-Signature"),
SECRET,
)
except SignatureVerificationError:
return "", 400
if evento.event == "validation.completed" and evento.validation is not None:
print(evento.validation.id, evento.validation.status)
return "", 200 # responder 2xx primero, procesar después
verify_webhook(payload, signature, secret) hace la misma comprobación y devuelve True o
False, para decidir el código de respuesta aparte. La comparación es en tiempo constante.
El receptor completo está en examples/flask_webhook.py.
Las cuatro cabeceras de una entrega
| cabecera | contenido |
|---|---|
X-Webhook-Signature |
sha256= seguido del HMAC en hexadecimal |
X-Veriko-Event |
Tipo de evento, por ejemplo validation.completed |
X-Veriko-Delivery-Id |
Identificador de la entrega, estable entre reintentos |
X-Veriko-Timestamp |
Momento del envío, ISO 8601 con sufijo Z |
El Delivery-Id es el valor que permite descartar entregas repetidas.
Reintentos
Hay dos mecanismos distintos con el mismo nombre.
Los del cliente repiten una petición que falló por causas pasajeras. El SDK reintenta los
5xx, el 408 y el 429, y respeta el Retry-After de la respuesta cuando lo trae. El resto de
los 4xx no se reintenta, porque la petición hay que corregirla antes de repetirla.
client = Veriko(max_retries=3) # 0 los desactiva; por omisión son 2
Los de la API siguen consultando a Banxico durante horas, porque un CEP tarda en publicarse, y avisan por webhook cuando el veredicto cambia.
from veriko import RetryPolicy
validation = client.validate_transfer(
fecha="2025-03-15",
monto=15000.50,
clave_rastreo="MXBA20250315001234",
retry_policy=RetryPolicy(
max_retries=3,
interval_seconds=600, # de 300 a 86 400
outcomes=["not_found", "cep_unavailable"], # qué veredictos reintentar
),
)
El avance del ciclo se lee en client.get_validation(id).retry_state, o se espera al webhook
validation.retry.resolved. Los intentos ya hechos están en client.validations.retry_attempts(id).
La política de una validación ya creada se cambia con client.validations.set_retry_policy(id, policy) y el ciclo se detiene con client.validations.cancel_retries(id). Las dos devuelven el
estado del ciclo (RetryState), no la validación completa.
Idempotencia
Reintentar un POST sin clave de idempotencia puede duplicar la validación, y su cargo, cuando la
respuesta se perdió pero la petición llegó. Con clave, la repetición devuelve la respuesta original
durante 24 horas.
validation = client.validate_transfer(
fecha="2025-03-15",
monto=15000.50,
clave_rastreo="MXBA20250315001234",
idempotency_key="pedido-4f3a2b1c", # el identificador del intento de negocio
)
La clave se deriva del intento de negocio (el número de pedido, de lote o de transacción), no se genera al azar en cada envío: una clave aleatoria por reintento anula la protección.
Sin idempotency_key, el SDK genera una por llamada y la repite en sus propios reintentos. Esa
clave no sobrevive al proceso que la generó, así que un reenvío posterior sí se ejecuta dos veces.
Errores
Todas las excepciones heredan de VerikoError. Las de la API traen code, que es el contrato
estable, y detail, que se traduce y puede reformularse entre versiones.
from veriko import InvalidRequestError, RateLimitError, NotFoundError
try:
validation = client.validate_transfer(...)
except InvalidRequestError as error: # 400, 413, 422
for entrada in error.errors: # un 422 trae una entrada por campo
print(entrada["code"], entrada["source"]["pointer"])
except RateLimitError as error: # 429
print("esperar", error.retry_after, "segundos")
except NotFoundError: # 404
...
| excepción | estado |
|---|---|
AuthenticationError |
401 |
ForbiddenError |
403 |
NotFoundError |
404 |
ConflictError |
409 |
InvalidRequestError |
400, 413, 422 |
RateLimitError |
429 |
ServerError |
5xx |
ConnectionError |
Sin respuesta, con los reintentos agotados |
SignatureVerificationError |
La firma de un webhook no cuadra |
Cada error de la API trae request_id, que identifica la petición en los registros del sistema.
Beneficiarios
client.beneficiaries guarda las cuentas a las que se paga: CLABE, tarjeta o celular DiMo. El tipo
se detecta por longitud y el banco se deriva del número, salvo en un celular, que exige bank_code.
beneficiary = client.beneficiaries.create(
account_number="012180004412345678",
label="Proveedor ABC",
)
beneficiary = client.beneficiaries.lookup("012180004412345678") # la cuenta ya guardada
La lista se recorre con client.beneficiaries.list(), y se exporta con
client.beneficiaries.export(format="csv").
Importación masiva
La importación es un ciclo, no una sola operación:
plantilla = client.beneficiaries.import_template(format="csv") # 1. descargar
plantilla.write_to(plantilla.filename)
trabajo = client.beneficiaries.import_start("beneficiarios.csv") # 2. subir
trabajo = client.beneficiaries.import_wait(trabajo.id) # 3. esperar la vista previa
for fila in client.beneficiaries.iter_import_preview(trabajo.id): # 4. revisar
if fila.status == "correctable":
client.beneficiaries.import_edit_row(trabajo.id, fila.id, parsed_bank_code="40012")
client.beneficiaries.import_commit(trabajo.id) # 5. confirmar
Nada se persiste hasta import_commit(). import_wait() espera a preview_ready o a un estado
final; el endpoint de estado no expone ETag, así que cada vuelta descarga el cuerpo. Una
importación que todavía no se confirmó se cancela con import_cancel().
Consumo
summary = client.usage.summary()
print(summary.used, "de", summary.limit, "(", summary.tone, ")")
historial = client.usage.history(months=6)
limites = client.usage.limits()
client.usage.export(format="csv") descarga el registro de actividad.
Superficie M2M
El SDK cubre exactamente las 66 operaciones del spec público: security: [] para las públicas y
ApiKeyAuth para las autenticadas.
Entre ellas están el perfil y su política de reintentos, el resumen del panel, los dos endpoints públicos de planes, las cuatro vistas de insights, las siete operaciones financieras y la suscripción. Por ejemplo:
profile = client.account.my_profile()
trends = client.insights.get_trends(range="30d", metric="latency")
statement = client.finance.get_statement(month="2026-04", format="pdf")
statement.write_to(statement.filename)
Los reportes mensual, por contraparte, por banco y contable conservan el valor por omisión de la API:
format="csv". Usa format="preview" explícitamente cuando necesites la respuesta JSON para
procesarla en memoria.
Desarrollo
pip install -e ".[dev]"
pytest # las pruebas
ruff check . # el estilo
mypy # los tipos
Ninguna prueba llama a la API. El arnés levanta un servidor HTTP local que sirve las respuestas
guardadas en tests/recordings/, recortadas de los ejemplos del spec público.
spec/openapi.yaml es copia de docs.veriko.mx/openapi.yaml.
tests/test_operations.py contrasta cada operación con ese spec (ruta, parámetros, cabeceras y
cuerpo, y que sea de máquina a máquina), y tests/test_models_follow_spec.py comprueba que los
campos de cada modelo existan en él. tests/test_spec_public.py vigila que la copia cumpla el
contrato de la API.
La prueba de operaciones compara el conjunto M2M completo, ejerce cada ruta contra el servidor HTTP local y falla si aparece una operación sin método o un método ajeno al contrato.
Enlaces
- Documentación de la API: docs.veriko.mx
- Spec de OpenAPI: docs.veriko.mx/openapi.yaml
- Ejemplos en otros lenguajes: veriko-mx-labs/examples
- El CEP, en detalle: docs.veriko.mx/es/concepts/cep-concept
- Registro de cambios: CHANGELOG.md
Licencia
MIT — ver LICENSE.
Metadata
Release files for veriko 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| veriko-0.5.1.tar.gz | 206.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| veriko-0.5.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 248.7 kB
Release files / veriko-0.5.1.tar.gz
| Download URL | veriko-0.5.1.tar.gz |
|---|---|
| Size | 206.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d3333f0be5d07786744aa34b37db051db85fb9c2c1d8a85d9eaf39b43aa15b04
|
|
BLAKE2b-256 checksum How to use checksums |
28ab99e7eb7c66dd473bccb7962449b89704a9a64fd203e89d36dcf2150a4b77
|
| 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 22, 2026.
Transparency logRelease files / veriko-0.5.1-py3-none-any.whl
| Download URL | veriko-0.5.1-py3-none-any.whl |
|---|---|
| Size | 42.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
afb2af25eed84557ed5e9f31883b6527c8de881a7979334bc4168bd7d81bbd9e
|
|
BLAKE2b-256 checksum How to use checksums |
ff330301697ca6674c579cb45324cf6a5d87f06ee06e4b9249d666c392d72cca
|
| 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 22, 2026.
Transparency log