https://api.veriko.mx/v1/validate-ocrValidate a SPEI transfer (OCR)
How-to guide →Validates a SPEI transfer from its receipt, as an image or a PDF. It extracts the data from the receipt through optical character recognition (OCR) and validates it against Banxico to obtain the transaction's CEP.
The receipt is sent as image or as image_url. When sent as a URL, the target host must serve it over a secure protocol (SSL/HTTPS).
The final verdict is the same as in manual validation:
valid: The transaction's CEP was found and validated.not_found: The transaction's CEP was NOT found.cep_unavailable: Banxico recognized the transaction but the CEP is not available for now.returned: the transaction was settled and the beneficiary institution later sent it back. The CEP, when there is one, is still delivered.error: Internal service error.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
async | query | string | optional | When Default: |
Idempotency-Key | header | string | optional | Optional client-generated key (Stripe-style) that guarantees the request is processed exactly once within a 24-hour TTL. The scope is 11111111-2222-3333-4444-555555555555 |
| Parameter | Type | Required | Description |
|---|---|---|---|
image | string (byte) | Required unless image_url is sent | Receipt encoded in base64, as an image or a PDF. If iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg== |
image_url | string (uri) | Required unless image is sent | Public HTTPS URL of the receipt. The file is fetched when the request arrives and follows the same format and size limits as https://storage.example.com/receipts/comprobante-2025-03.jpg |
cuenta_beneficiaria | string (pattern) | optional | Receiving account for the transfer (useful when it is missing or incomplete in the image). Required for DiMo phone; optional if the image shows the full account or its last digits, which are completed with the saved beneficiaries (they do not replace this field). Accepts CLABE (18 digits), card (16 digits) or DiMo phone (10 digits). It cannot be sent together with 012180004412345678 |
cuentas_candidatas | array<string> (items: 2–10, unique items) | optional | Accounts among which the receiving one is, for when the image does not show it and it is not known which. It replaces ["012180004412345678","002010077777777771"] |
banco_emisor | string | optional | SPEI code of the sending bank (4 or 5 digits, e.g. 40012 |
banco_receptor | string | optional | SPEI code of the receiving bank (4 or 5 digits, e.g. 40002 |
retry_policy | object | optional | Automatic retry policy for this validation. If omitted, the policy configured at |
enabled | boolean | optional |
true |
max_retries | integer (≥ 1) | optional | Maximum number of automatic retries. Once reached, the cycle enters the 3 |
interval_seconds | integer (≥ 300, ≤ 86400) | optional | Seconds to wait between each retry (300–86400, i.e., 5 minutes to 24 hours). e.g.600 |
outcomes | array<string> (items: 1–?, unique items) | optional | Outcomes that trigger a retry. By default, they are ["not_found","cep_unavailable","error"] |
client_ref | string (pattern, 1–64) | optional | Reference of your own that is returned as sent in the validation, in the validation webhooks, and as an exact filter of orden-4812 |
retain_image | boolean | optional |
Default: false |
curl -X POST 'https://api.veriko.mx/v1/validate-ocr' \
-H 'Authorization: Bearer veriko_••••' \
-H 'Content-Type: application/json' \
-d '{
"image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
}'Python example — coming soon.
JavaScript example — coming soon.
PHP example — coming soon.
Retry policy
POST /v1/validate-ocr
Validation retry policy: activation, limit, interval, and eligible outcomes.
- Attempts
- —
- Interval
- 5 min – 1 h × 24
- Eligible outcomes
not_found,cep_unavailable,error
This operation accepts an optional retry policy.
| Field | Type | Description |
|---|---|---|
type* | string | JSON:API resource type. Always validation |
id* | string (uuid) | Unique identifier of the validation (UUID v4). e.g.3fa85f64-5717-4562-b3fc-2c963f66afa6 |
attributes* | object | Canonical validation data. |
validation_type | string | Validation mode — direct |
is_playground | boolean |
false |
status | string | Lifecycle state — valid |
banxico_status | string | nullnullable | Verdict reported by Banxico. It can be valid |
processing_time_ms | integer | nullnullable | Milliseconds spent processing the validation. e.g.1320 |
request_data | object | Copy of the original request fields. |
client_ref | string | Reference of your own sent in the request ( orden-4812 |
created_at | string (date-time) | Validation creation timestamp in ISO 8601 UTC. e.g.2025-03-15T14:22:10Z |
completed_at | string | nullnullable | Date and time of completion, in ISO 8601 UTC. 2025-03-15T14:22:11Z |
enqueued_at | string | nullnullable | Queue-entry timestamp in ISO 8601 UTC. e.g.2026-04-30T10:15:00Z |
processing_started_at | string | nullnullable | Processing start timestamp in ISO 8601 UTC. e.g.2026-04-30T10:15:00Z |
expires_at | string | nullnullable | Expiration timestamp for queued validations in ISO 8601 UTC. After this, the validation moves to 2026-04-30T10:15:00Z |
etag_version | integer | nullnullable | Incremental version used with 1 |
image_path | string | nullnullable | Relative reference to the receipt, image or PDF, only when comprobantes/3fa85f64-5717-4562-b3fc-2c963f66afa6.jpg |
image_retained | boolean |
true |
purged_at | string (date-time) | Date and time, in ISO 8601 UTC, when the validation was purged with 2026-10-02T09:30:00Z |
ocr_result | object | nullnullable | Fields extracted from the receipt, only when |
ocr_confidence | number | nullnullable | OCR score from 0 to 1, only when 0.94 |
normalized_data | object | nullnullable | Fields normalized after OCR (when |
normalization_warnings | array | nullnullable | Normalization warnings present. They can explain an unexpected |
duplicate_of | object | Earlier validation that had already confirmed this same transfer in the same account. It only appears when a |
id* | string (uuid) | Identifier of the earlier validation. e.g.3fa85f64-5717-4562-b3fc-2c963f66afa6 |
created_at* | string (date-time) | Creation date and time of the earlier validation, in ISO 8601 UTC. e.g.2026-10-01T15:04:05Z |
account_conflict | object | Conflict between the account sent and the one shown in the image. It only appears on |
sent_last4* | string | Last 4 digits of the account sent in 5678 |
read_last4* | string | Last 4 digits of the account shown in the image (the visible ones, 3 or 4, if it is masked). e.g.9012 |
candidate_match | object | Which of the |
index* | integer | Position (from 0) of the winning account in the 1 |
account_last4* | string | Last 4 digits of the winning account. e.g.7771 |
is_masked | boolean | nullnullable | Whether the beneficiary account is masked in the receipt, be it a CLABE, a card or a phone number. It is included only when it is |
banxico_result | object | nullnullable | Data returned by the Banxico CEP. The typed properties below are the 20 declared by the official SPEI complement schema.
|
operationDate | string | SPEI business day that Banxico assigned to the operation, as reported by the CEP ( 2025-03-15 |
processingTime | string | Operation time as reported by the CEP ( 14:22:10 |
speiKey | string | Operation key within the SPEI system (the CEP's |
trackingKey | string | SPEI tracking key of the operation — the same one sent in the validation request. e.g.MBAN01002503151422ABCDEF |
digitalSignature | string | Digital seal of the CEP ( |
certificateNumber | string | Serial number of the digital certificate Banxico used to seal the CEP. |
cadenaCda | string | Original data string of the operation ( |
senderBank | string | Sending bank of the operation, as identified by the CEP. e.g.BBVA |
senderName | string | Name of the operation's sender, as reported by the CEP. |
senderAccountType | string | Sender's account type (e.g. CLABE or card), as reported by the CEP. |
senderAccount | string | Sender's account number, as reported by the CEP — unmasked in this resource (a private lookup, owned by the validation's account). |
senderRfc | string | Sender's RFC (Mexican tax ID), as reported by the CEP. |
receiverBank | string | Receiving bank of the operation, as identified by the CEP. e.g.STP |
beneficiaryName | string | Name of the operation's beneficiary, as reported by the CEP. |
beneficiaryAccountType | string | Beneficiary's account type (e.g. CLABE or card), as reported by the CEP. |
beneficiaryAccount | string | Beneficiary's account number, as reported by the CEP. Unmasked in this resource (a private lookup, owned by the validation's account). Outgoing webhooks DO mask it to the last 4 digits in |
beneficiaryRfc | string | Beneficiary's RFC (Mexican tax ID), as reported by the CEP. |
amount | number (float) | Operation amount Banxico confirmed via the CEP, in pesos. e.g.1500 |
iva | number (float) | VAT (IVA) of the operation reported by the CEP, in pesos. |
paymentConcept | string | Payment concept of the operation, as reported by the CEP. |
error_message | string | nullnullable | Human-readable error message when terminal. e.g.Banxico no respondió tras tres intentos. |
error_code | ValidationErrorCode | null | Stable code of the terminal error, when there is one.
After |
batch_id | integer | nullnullable | Bulk import job identifier, when applicable. e.g.318 |
batch_position | integer | nullnullable | Position within the job, starting at 1. e.g.12 |
retry_state | object | Full retry cycle state. Always present; if retries are not active, |
enabled | boolean |
true |
max_retries | integer | nullnullable | Configured maximum number of retries; 3 |
interval_seconds | integer | nullnullable | Interval between retries in seconds, from 600 |
outcomes | array | nullnullable | Outcomes that trigger a retry: ["not_found","cep_unavailable","error"] |
attempts_completed | integer | Number of retries completed so far. e.g.1 |
next_attempt_at | TimestampUTC | null | Date of the next retry; |
resolved_at | TimestampUTC | null | Date when a retry produced |
exhausted_at | TimestampUTC | null | Date when attempts were exhausted; |
cancelled_at | TimestampUTC | null | Cycle cancellation date; |
terminal_state | string | nullnullable | Cycle state: pending |
links | object | Related links (JSON:API |
self | string | Canonical link to the validation details. e.g./v1/validations/3fa85f64-5717-4562-b3fc-2c963f66afa6 |
cep_xml | string | nullnullable | URL of the CEP in XML format. It only appears when /v1/validations/a1b2c3d4-e5f6-4890-abcd-ef0123456789/cep?format=xml |
cep_pdf | string | nullnullable | URL of the CEP in PDF format. It only appears when /v1/validations/a1b2c3d4-e5f6-4890-abcd-ef0123456789/cep?format=pdf |
| Status | Class | Description | Body |
|---|---|---|---|
| 200 | 2xx | Resolved validation with the data extracted from the receipt and its verdict. | Validation |
| 202 | 2xx | Validation enqueued. Poll GET /v1/validations/{id} until one of the terminal statuses (valid, not_found, cep_unavailable, invalid, returned, failed, error). meta.next_poll_after_seconds hints the recommended first-poll interval.
| ValidationQueued |
| 400 | 4xx | The Idempotency-Key header value does not meet the allowed format (alphanumeric + _ + -, 1–255 characters).
| ErrorResponse |
| 401 | 4xx | Authentication is required or the provided credentials are invalid. | ErrorResponse |
| 409 | 4xx | A request with the same Idempotency-Key is still being processed. The client must retry after the number of seconds indicated by the Retry-After header. In-flight rows older than idempotency.in_flight_timeout_seconds (300 by default) are automatically treated as zombies and cleaned up on the next attempt.
| ErrorResponse |
| 413 | 4xx | The request body exceeds the maximum accepted size (body_too_large). | ErrorResponse |
| 422 | 4xx | The image or request data is invalid. Possible codes: image_or_image_url_required, invalid_image, invalid_image_format, image_too_large, invalid_url, invalid_url_scheme, url_ssrf_blocked, invalid_clabe_checksum, image_too_small, image_mime_mismatch, image_dimensions_too_large, image_decompression_bomb, image_polyglot_detected, image_url_unreachable, image_url_too_large, image_url_too_many_redirects. With a PDF: pdf_invalid_structure, pdf_encrypted, pdf_active_content, pdf_hidden_content, pdf_modified_after_issue, pdf_too_many_pages, pdf_unsupported_image, pdf_text_layer_too_large, and pdf_multiple_receipts. With a retain_image that is not a boolean: invalid_retain_image. With cuentas_candidatas: cuenta_y_candidatas_excluyentes, cuentas_candidatas_invalidas and, when no candidate matches, cuenta_unresolvable_after_probes. In asynchronous mode, a generic image failure is reported as image_invalid. Reusing Idempotency-Key with a different body produces idempotency_key_reused. | ErrorResponse |
| 429 | 4xx | Rate limit exceeded | ErrorResponse |
| 503 | 5xx | OCR is unavailable, the Banxico request could not be completed (banxico_rate_limit_exhausted), or the asynchronous validation could not be started (dispatch_failed). | ErrorResponse |
| Header | Type | Description |
|---|---|---|
Idempotent-Replayed | string | Present only when the client sent Idempotency-Key. false for fresh responses; true when replayed from cache. |
| Status | Code | Example |
|---|---|---|
| 400 | invalid_idempotency_key | Invalid Idempotency-Key format. Allowed: A-Z a-z 0-9 _ - (1-255 chars). Envelope
|
| 401 | unauthorized | Invalid or missing authentication credentials. Envelope
|
| 409 | idempotency_key_in_progress | A request with this Idempotency-Key is still being processed. Envelope
Response headers
|
| 413 | body_too_large | The request body is too large. Envelope
|
| 429 | rate_limit_exceeded | Rate limit exceeded. Try again in 45 seconds. Envelope
Response headers
|