https://api.veriko.mx/v1/validate-ocr Validate a SPEI transfer (OCR)
How-to guide →Uploads a receipt image, extracts fields via OCR, and validates against Banxico CEP in a single call. Accepts image as base64 (image) or remote URL (image_url). The cuenta_beneficiaria field is optional: if the image shows a masked account, the system resolves it from accounts registered at /v1/beneficiaries. Use ?async=1 to enqueue the OCR pipeline and receive an immediate 202; the image is sanitized and persisted synchronously before enqueuing. Poll GET /v1/validations/{id} until a terminal status. Accepts optional Idempotency-Key with the same semantics as POST /v1/validate — use that endpoint when the receipt fields are already known. After the verdict, full details (including ocr_result, image_path, and retry_state) are available via GET /v1/validations/{id}.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
async | query | string | optional | When `1`/`true`/`yes`, the validation is queued and the server responds 202 immediately with a `validation_id`; the client polls `GET /v1/validations/{id}` until the terminal state. Without the flag or with `0`, the response is synchronous and returns the final result in the same POST. 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 `(user_id, endpoint, key)`. Retries with the same key and the same body return the byte-for-byte cached response with the `Idempotent-Replayed: true` header, without consuming rate-limit quota, without re-firing webhooks, and without creating a new `validations` row. Same key with a different body → 422 `idempotency_key_reused`. Same key with an in-flight request → 409 `idempotency_key_in_progress`. 5xx responses are not cached (retries with the same key are processed for real). Format: 1–255 characters, alphanumeric + `_` + `-`. |
| Parameter | Type | Required | Description |
|---|---|---|---|
image | string (byte) | Required unless image_url is sent | Receipt image encoded in base64 (JPEG, PNG, or WebP). Maximum size: 12 MB. Maximum dimensions: 12,000 px per side. Mutually exclusive with `image_url`; if both are provided, `image` takes precedence. |
image_url | string (uri) | Required unless image is sent | Public HTTPS URL of the receipt image. The server downloads the image on receipt and persists it the same way as the `image` flow. Same format and size limits apply (JPEG, PNG, or WebP, maximum 12 MB). e.g.https://storage.example.com/receipts/comprobante-2025-03.jpg |
cuenta_beneficiaria | string (pattern) | optional | Optional hint for the beneficiary account: 18-digit CLABE, 13-19 digit card, or 10-digit DiMo phone, auto-detected by length. Used to disambiguate when OCR extracts a partial or masked number from the receipt. e.g.012180004412345678 |
banco_emisor | string (pattern) | optional | Optional sender bank override. Accepted only when the value is a 4-5 digit numeric Banxico participant code. Textual values are ignored — OCR-extracted text is trusted over the client hint. e.g.40012 |
banco_receptor | string (pattern) | optional | Optional receiver bank override. Accepted only when the value is a 4-5 digit numeric Banxico participant code. Textual values are ignored. Useful when OCR could not derive the receiving bank or the client knows the exact Banxico code. e.g.40002 |
retry_policy | object | optional | Automatic retry policy for this validation. If omitted, the policy configured at `PUT /v1/users/me/retry-policy` is used. Retries do not consume validation quota. |
curl -X POST 'https://api.veriko.mx/v1/validate-ocr' \
-H 'Authorization: Bearer mxcep_••••' \
-H 'Content-Type: application/json' \
-d '{
"image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
}'
Python example — coming soon.
JavaScript example — coming soon.
PHP example — coming soon.
| Field | Type | Description |
|---|---|---|
id * | string (uuid) | Unique identifier of the validation (UUID v4). |
type * | string | JSON:API resource type. Always `validation`. |
attributes * | object | Canonical validation data. |
validation_type | string | `direct` for text-parameter requests; `ocr` for receipt image requests. |
is_playground | boolean | Whether the validation ran in playground mode. Playground executions still query Banxico but do not consume quota, emit webhooks, or fire notifications. |
status | string | Lifecycle state: `queued` — enqueued for worker; `processing` — worker handling; `valid` — CEP found and data matches; `not_found` — Banxico queried, transfer not found; `cep_unavailable` — Banxico unreachable; `invalid` — payload rejected post-enqueue; `failed` — terminal failure; `error` — retriable error (Banxico HTTP 5xx). |
banxico_status | string | null nullable | Banxico-reported status after query. `null` before query. |
processing_time_ms | integer | null nullable | Milliseconds from enqueue to terminal resolution. |
request_data | object | Literal snapshot of the original request fields. |
created_at | string (date-time) | UTC timestamp of enqueue. |
completed_at | string | null nullable | UTC timestamp of terminal resolution. `null` while `status` is `queued`/`processing`. |
enqueued_at | string | null nullable | Timestamp of bus enqueue. |
processing_started_at | string | null nullable | Timestamp of the first worker XCLAIM. |
expires_at | string | null nullable | Expiration timestamp for queued validations. After this, the job moves to `failed`. |
etag_version | integer | null nullable | Incremental version used for `If-None-Match` polling. |
image_path | string | null nullable | Relative path of the receipt image. OCR only. |
ocr_result | object | null nullable | Raw OCR result. OCR only. |
ocr_confidence | number | null nullable | OCR confidence 0–1. OCR only; `null` for `direct`. |
normalized_data | object | null nullable | Normalized post-OCR fields used to query Banxico. |
normalization_warnings | array | null nullable | Warnings emitted by the normalization pipeline. |
is_masked | boolean | null nullable | Whether the receipt has a masked PAN. |
banxico_result | object | null nullable | Literal payload returned by Banxico CEP. |
error_message | string | null nullable | Human-readable error message when terminal. |
error_code | string | null nullable | Machine-readable error code when terminal. |
batch_id | integer | null nullable | Bulk import batch identifier if applicable. |
batch_position | integer | null nullable | Position within the batch (1-indexed). |
retry_state | object | Full retry cycle state. Always present; if retries are not active, `enabled=false` and policy fields are `null`. Bulk import rows always have `enabled=false`. |
enabled | boolean | Whether the retry cycle is active for this validation. |
max_retries | integer | null nullable | Configured maximum number of retries. The upper bound depends on the plan (`retry_max_retries`) or the global default `max_retries_cap` (typically 5–10). `null` if `enabled=false`. |
interval_seconds | integer | null nullable | Interval between retries in seconds (300–86400). `null` if `enabled=false`. |
outcomes | array | null nullable | Validation outcomes that trigger a retry (`not_found`, `cep_unavailable`, `error`). `null` if `enabled=false`. |
attempts_completed | integer | Number of retries completed so far. |
next_attempt_at | union | Timestamp of the next scheduled retry. `null` if the cycle is in a terminal state or if no retries are active. |
resolved_at | union | Timestamp when a retry resolved the validation to `valid`. `null` if the cycle has not ended by resolution. |
exhausted_at | union | Timestamp when retries were exhausted without resolution. `null` if the cycle has not ended by exhaustion. |
cancelled_at | union | Timestamp when the cycle was explicitly cancelled. `null` if not cancelled. |
terminal_state | string | null nullable | Terminal state of the cycle: `pending` — active, no final result yet; `resolved` — a retry obtained `valid`; `exhausted` — all attempts used; `cancelled` — cancelled by the user. |
links | object | Related links (JSON:API `links`). |
self | string | URL of the validation. |
cep_xml | string | null nullable | URL of the CEP in XML format. `null` if `status` is not `valid`. |
cep_pdf | string | null nullable | URL of the CEP in PDF format. `null` if `status` is not `valid`. |
| Status | Class | Description | Body |
|---|---|---|---|
| 200 | 2xx | Validation verdict with fields extracted by OCR. The `ocr_confidence` field (0–1) indicates extraction confidence. | No body |
| 202 | 2xx | Validation enqueued. Poll `GET /v1/validations/{id}` until one of the terminal statuses (`valid`, `not_found`, `cep_unavailable`, `invalid`, `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 |
| 422 | 4xx | Request validation failed. Body-level 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`. Post-decode sanitizer codes (both sync path and async pre-dispatch): `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`. In async mode generic sanitization failure is reported as `image_invalid`. Reused `Idempotency-Key` with different body → `idempotency_key_reused`. | ErrorResponse |
| 429 | 4xx | Rate limit exceeded | ErrorResponse |
| 503 | 5xx | OCR not configured on this deploy (`ocr_not_configured`), Banxico upstream failure (`banxico_rate_limit_exhausted`), or async dispatch failure (Redis down → `dispatch_failed`). Wait a few minutes and retry. | 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 | Detail |
|---|---|---|
| 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
|
| 429 | rate_limit_exceeded | Rate limit exceeded. Try again in 45 seconds. Envelope
Response headers
|
Retry policy
POST /v1/validate-ocr
Automatic retry policy. Configures when and how many times the system retries a validation that returned an eligible outcome (`not_found`, `cep_unavailable` or `error` by default).
- Attempts
- —
- Interval
- 5 min – 1 h × 24
- Eligible outcomes
-
not_found,cep_unavailable,error
This operation accepts an optional retry policy.