POSThttps://api.veriko.mx/v1/validate-ocr

Validate a SPEI transfer (OCR)

Audience
public
Auth
API key
Permission
validations_ocr:create
How-to guide →

Takes a receipt image, extracts the fields from it, and validates against the Banxico CEP in a single call. The image travels as base64 in image or as a remote address in image_url. cuenta_beneficiaria is optional here, unlike in the direct validation: when the receipt shows the account masked, it is resolved against the accounts registered at /v1/beneficiaries. That is the practical reason to have beneficiaries registered before using this endpoint. Asynchronous mode. With ?async=1 the response is an immediate 202 and the verdict is collected by polling GET /v1/validations/{id}. The image is processed before responding even in this mode, so this 202 takes longer than the direct validation's. Every call consumes plan quota, just as the direct validation does and with the same deduction on acceptance. Exhausting the quota returns 429; the detail is in quotas and plans. The Idempotency-Key header behaves exactly as in POST /v1/validate, which is the preferable route when the receipt fields are already known: it skips the extraction step and its margin of error. After the verdict, the full detail — with ocr_result, image_path, and retry_state — is at GET /v1/validations/{id}.

Parameters
ParameterInTypeRequiredDescription
asyncquerystringoptional

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: 0

Idempotency-Keyheaderstringoptional

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 + _ + -.

e.g. 11111111-2222-3333-4444-555555555555
Parameters
ParameterTypeRequiredDescription
imagestring (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.

e.g. iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==
image_urlstring (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_beneficiariastring (pattern)optional

Optional hint for the beneficiary account: 18-digit CLABE, 16-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
retry_policyobjectoptional

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.

Request
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.

Response 2xxValidation
FieldTypeDescription
type*string

JSON:API resource type. Always validation.

e.g. validation
id*string (uuid)

Unique identifier of the validation (UUID v4).

e.g. 3fa85f64-5717-4562-b3fc-2c963f66afa6
attributes*object

Canonical validation data.

validation_typestring

direct for text-parameter requests; ocr for receipt image requests.

e.g. direct
is_playgroundboolean

Whether the validation ran in playground mode. Playground executions still query Banxico but do not consume quota, emit webhooks, or fire notifications.

e.g. false
statusstring

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).

e.g. valid
banxico_statusstring | nullnullable

Banxico-reported status after query. null before query.

e.g. valid
processing_time_msinteger | nullnullable

Milliseconds from enqueue to terminal resolution.

e.g. 1320
request_dataobject

Literal snapshot of the original request fields.

created_atstring (date-time)

UTC timestamp of enqueue.

e.g. 2025-03-15T14:22:10Z
completed_atstring | nullnullable

UTC timestamp of terminal resolution. null while status is queued/processing.

e.g. 2025-03-15T14:22:11Z
enqueued_atstring | nullnullable

Timestamp of bus enqueue.

e.g. 2026-04-30T10:15:00Z
processing_started_atstring | nullnullable

Timestamp of the first worker XCLAIM.

e.g. 2026-04-30T10:15:00Z
expires_atstring | nullnullable

Expiration timestamp for queued validations. After this, the job moves to failed.

e.g. 2026-04-30T10:15:00Z
etag_versioninteger | nullnullable

Incremental version used for If-None-Match polling.

e.g. 1
image_pathstring | nullnullable

Relative path of the receipt image. OCR only.

e.g. ocr/2026/04/a1b2c3d4.jpg
ocr_resultobject | nullnullable

Raw OCR result. OCR only.

ocr_confidencenumber | nullnullable

OCR confidence 0–1. OCR only; null for direct.

e.g. 0.94
normalized_dataobject | nullnullable

Normalized post-OCR fields used to query Banxico.

normalization_warningsarray | nullnullable

Warnings emitted by the normalization pipeline.

is_maskedboolean | nullnullable

Whether the receipt has a masked PAN.

banxico_resultobject | nullnullable

Literal payload returned by Banxico CEP.

error_messagestring | nullnullable

Human-readable error message when terminal.

e.g. Banxico no respondió tras tres intentos.
error_codeValidationErrorCode | null

Machine-readable error code when terminal.

batch_idinteger | nullnullable

Bulk import batch identifier if applicable.

e.g. 318
batch_positioninteger | nullnullable

Position within the batch (1-indexed).

e.g. 12
retry_stateobject

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.

enabledboolean

Whether the retry cycle is active for this validation.

e.g. true
max_retriesinteger | nullnullable

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.

e.g. 3
interval_secondsinteger | nullnullable

Interval between retries in seconds (300–86400). null if enabled=false.

e.g. 600
outcomesarray | nullnullable

Validation outcomes that trigger a retry (not_found, cep_unavailable, error). null if enabled=false.

e.g. ["not_found","cep_unavailable","error"]
attempts_completedinteger

Number of retries completed so far.

e.g. 1
next_attempt_atTimestampUTC | null

Timestamp of the next scheduled retry. null if the cycle is in a terminal state or if no retries are active.

resolved_atTimestampUTC | null

Timestamp when a retry resolved the validation to valid. null if the cycle has not ended by resolution.

exhausted_atTimestampUTC | null

Timestamp when retries were exhausted without resolution. null if the cycle has not ended by exhaustion.

cancelled_atTimestampUTC | null

Timestamp when the cycle was explicitly cancelled. null if not cancelled.

terminal_statestring | nullnullable

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. null if the validation has no retry cycle.

e.g. pending
linksobject

Related links (JSON:API links).

selfstring

URL of the validation.

e.g. /v1/validations/3fa85f64-5717-4562-b3fc-2c963f66afa6
cep_xmlstring | nullnullable

URL of the CEP in XML format. null if status is not valid.

e.g. /v1/validations/a1b2c3d4-e5f6-7890-abcd-ef0123456789/cep.xml
cep_pdfstring | nullnullable

URL of the CEP in PDF format. null if status is not valid.

e.g. /v1/validations/a1b2c3d4-e5f6-7890-abcd-ef0123456789/cep.pdf
Response status codesPOST /v1/validate-ocr
StatusClassDescriptionBody
2002xxValidation verdict with fields extracted by OCR. The ocr_confidence field (0–1) indicates extraction confidence.No body
2022xxValidation 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
4004xxThe Idempotency-Key header value does not meet the allowed format (alphanumeric + _ + -, 1–255 characters). ErrorResponse
4014xxAuthentication is required or the provided credentials are invalid.ErrorResponse
4094xxA 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
4134xxThe request body exceeds the maximum accepted size (body_too_large).ErrorResponse
4224xxRequest 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
4294xxRate limit exceededErrorResponse
5035xxOCR 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
Response headers200
HeaderTypeDescription
Idempotent-ReplayedstringPresent only when the client sent Idempotency-Key. false for fresh responses; true when replayed from cache.
Errors from POST /v1/validate-ocr
StatusCodeExample
400invalid_idempotency_key

Invalid Idempotency-Key format. Allowed: A-Z a-z 0-9 _ - (1-255 chars).

Envelope
meta.request_id
3c4d5e6f7a8b
401unauthorized

Invalid or missing authentication credentials.

Envelope
meta.request_id
c4d5e6f7a8b9
409idempotency_key_in_progress

A request with this Idempotency-Key is still being processed.

Envelope
meta.request_id
2b3c4d5e6f7a
Response headers
  • Retry-After: integer — Seconds to wait before retrying
413body_too_large

The request body is too large.

Envelope
meta.request_id
1a2b3c4d5e6f
429rate_limit_exceeded

Rate limit exceeded. Try again in 45 seconds.

Envelope
meta.request_id
f7a8b9c0d1e2
Response headers
  • Retry-After: integer — Seconds to wait before retrying. Matches the endpoint's rate-limit window (typically 60s for list endpoints, 1-5s for in-flight idempotent operations).
  • X-RateLimit-Limit: integer — Configured request cap for this bucket (emitted only on 429).
  • X-RateLimit-Remaining: integer — Requests remaining in the current window — always 0 at the moment of the 429 (emitted only on 429).
  • X-RateLimit-Reset: integer — Absolute Unix epoch (seconds) when the window resets. Emitted only on 429, alongside Retry-After. Per-endpoint overrides exist (e.g. `rate_limited_login`).

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.