Validar una transferencia SPEI desde un comprobante en imagen o PDF

Objetivo

Permite que Veriko extraiga los campos del comprobante SPEI desde una imagen o un PDF y valide la transferencia contra el CEP de Banxico — sin que necesites parsear el comprobante manualmente. El registro de validación resultante es idéntico al de POST /v1/validate; puedes descargar el certificado CEP y activar reintentos de la misma manera.

Requisitos previos

  • Una API key activa (veriko_…) con el permiso validations_ocr:create.
  • Un comprobante en JPEG, PNG, WebP o PDF de 1 a 3 páginas, de hasta 12 MB.
  • El comprobante debe ser un SPEI legible. Los números de cuenta enmascarados se resuelven contra tu lista de beneficiarios registrados cuando la forma enmascarada coincide con una cuenta registrada.

Pasos

1. Enviar el comprobante

Envía el comprobante (imagen o PDF) como cadena en base64 en el campo image, o pasa una URL pública en image_url. Opcionalmente puedes proporcionar cuenta_beneficiaria como pista de desambiguación cuando el comprobante muestra una cuenta enmascarada.

Opción A — URL remota:

curl -X POST 'https://api.veriko.mx/v1/validate-ocr' \
  -H 'Authorization: Bearer veriko_••••' \
  -H 'Content-Type: application/json' \
  -d '{
    "image_url": "https://storage.example.com/comprobantes/spei-001.png"
  }'

Opción B — comprobante en base64:

curl -X POST 'https://api.veriko.mx/v1/validate-ocr' \
  -H 'Authorization: Bearer veriko_••••' \
  -H 'Content-Type: application/json' \
  -d '{
    "image": "<bytes-del-comprobante-en-base64>"
  }'

Respuesta síncrona 200 — el OCR extrajo los campos y Banxico respondió en banda:

{
  "data": {
    "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
    "type": "validation",
    "attributes": {
      "validation_type": "ocr",
      "status": "valid",
      "ocr_confidence": 0.96,
      "ocr_result": {
        "fecha": "2025-04-10",
        "monto": 12500.00,
        "clave_rastreo": "MXBA20250410003456"
      },
      "image_path": "comprobantes/c3d4e5f6-a7b8-9012-cdef-123456789012.png",
      "processing_time_ms": 2840,
      "created_at": "2025-04-10T11:30:00Z",
      "completed_at": "2025-04-10T11:30:02Z"
    },
    "links": {
      "self": "/v1/validations/c3d4e5f6-a7b8-9012-cdef-123456789012",
      "cep_xml": "/v1/validations/c3d4e5f6-a7b8-9012-cdef-123456789012/cep?format=xml",
      "cep_pdf": "/v1/validations/c3d4e5f6-a7b8-9012-cdef-123456789012/cep?format=pdf"
    }
  }
}

Si el resultado sigue siendo not_found, error_code puede precisar qué revisar:

  • not_found_identity: fecha, clave o referencia y bancos.
  • not_found_account: cuenta del beneficiario; el importe sí coincide.
  • not_found_amount: importe; la cuenta sí coincide.
  • not_found_account_and_amount: cuenta e importe.
  • not_found_account_or_amount: la operación existe, pero no se pudo aislar el campo.

Un captcha, una respuesta ambigua o un control fallido no producen un diagnóstico específico. La ausencia de diagnóstico tampoco confirma los datos.

Si enviaste cuenta_beneficiaria y el comprobante muestra otra cuenta, completa o enmascarada con al menos tres dígitos finales visibles que no son el final de la que enviaste, la respuesta incluye account_conflict con sent_last4 y read_last4: los últimos dígitos de cada una (en una cuenta enmascarada, los que el comprobante muestra). Prevalece la cuenta enviada y el veredicto no cambia; el campo, que también queda en normalization_warnings, explica un not_found inesperado.

Si el comprobante no muestra ni la clave de rastreo ni la referencia numérica (muchas apps los dejan tras «Ver más detalles»), la validación termina con preflight_failed, y su detalle (field_errors[].detail y error_message) indica que hay que enviar la captura del detalle completo. El código no cambia.

Comprobante en PDF

El campo image también acepta un PDF de 1 a 3 páginas, con el mismo límite de 12 MB. El PDF viaja tal cual, sin convertirse en imagen, y se rechaza si está cifrado, si se modificó después de emitirse o si trae contenido activo u oculto: cada caso tiene su código (pdf_encrypted, pdf_modified_after_issue, pdf_active_content, pdf_hidden_content…).

Si el PDF es el CEP que emite Banxico, sus datos se leen directamente de su texto, sin OCR. Cuando Banxico confirma el pago, se comprueba además que el sello y la cadena original del archivo coincidan con los del CEP oficial, y el resultado queda en banxico_result._cep_upload:

"_cep_upload": { "authenticated": true }

Con "authenticated": false, differing_fields lista los campos que no coinciden y normalization_warnings lo avisa. El veredicto no cambia: sigue siendo el de Banxico.

Varias cuentas posibles

Cuando el comprobante no muestra la cuenta y no sabes a cuál de varias se hizo el pago, cuentas_candidatas sustituye a cuenta_beneficiaria:

{
  "image_url": "https://storage.example.com/comprobantes/spei-003.png",
  "cuentas_candidatas": ["012180004412345678", "002010077777777771"]
}

La lista lleva de 2 a 3 cuentas válidas y distintas, y no puede combinarse con cuenta_beneficiaria: enviar las dos responde 422 cuenta_y_candidatas_excluyentes, y una lista que no cumple, 422 cuentas_candidatas_invalidas, las dos antes de consumir cuota. Tiene prioridad sobre la cuenta que se lea en la imagen, vale también con ?async=1 y consume una sola unidad de cuota. La cuenta ganadora vuelve completa en normalized_data.cuenta_beneficiaria y su posición en candidate_match. Si ninguna coincide, la respuesta es un 422 y la unidad se consume igual, porque el comprobante ya se leyó. El detalle del resultado está en la guía de validación directa.

No conservar el comprobante

De forma predeterminada el archivo del comprobante se conserva. Si prefieres que no quede en la plataforma, envía retain_image en false:

{
  "image": "<bytes-del-comprobante-en-base64>",
  "retain_image": false
}

El archivo se borra en cuanto la validación llega a un estado terminal del que ya no se necesita, también con ?async=1. El veredicto y los datos extraídos se conservan. La validación lo dice con image_retained en false y GET /v1/validations/{id}/image responde 410 image_not_retained, en lugar de un 404 genérico. Un retain_image que no es booleano responde 422 invalid_retain_image antes de consumir cuota.

Para borrar más tarde una validación, con su CEP y todo lo derivado, ver Borrar una validación de forma definitiva.

2. Modo asíncrono (?async=1)

Para pipelines de alto volumen, añade ?async=1 para recibir un 202 inmediato y dejar que la consulta OCR + CEP corra en segundo plano. El comprobante se sanea y persiste antes de encolar la importación, por lo que no se perderá si el trabajador está caído momentáneamente.

curl -X POST 'https://api.veriko.mx/v1/validate-ocr?async=1' \
  -H 'Authorization: Bearer veriko_••••' \
  -H 'Content-Type: application/json' \
  -d '{ "image_url": "https://storage.example.com/comprobantes/spei-002.png" }'

Respuesta 202:

{
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "type": "validation",
    "attributes": { "status": "queued" }
  }
}

Sondea GET /v1/validations/{id} cada 2–5 segundos hasta que status alcance un valor terminal. Respeta el header Retry-After cuando esté presente.

3. Descargar el certificado CEP

Una vez que status sea valid y la respuesta traiga links.cep_pdf y links.cep_xml, descarga el certificado igual que en una validación manual:

curl 'https://api.veriko.mx/v1/validations/c3d4e5f6-.../cep?format=pdf' \
  -H 'Authorization: Bearer veriko_••••' \
  --output cep.pdf

4. Recuperar el comprobante almacenado

El comprobante se persiste en la plataforma y es accesible vía GET /v1/validations/{id}/image, salvo que la validación se haya enviado con retain_image=false (responde 410 image_not_retained). Úsalo para mostrar el comprobante en tu UI o para archivarlo junto con el CEP. Un PDF llega siempre como adjunto (Content-Disposition: attachment) y con Content-Type: application/pdf; guarda el archivo con la extensión que indique esa cabecera.

curl 'https://api.veriko.mx/v1/validations/c3d4e5f6-.../image' \
  -H 'Authorization: Bearer veriko_••••' \
  --output comprobante.png

Siguientes pasos