openapi: 3.1.0
info:
  title: Veriko API
  description: |-
    REST API para validar transferencias SPEI mexicanas contra Banxico CEP. Soporta validación manual por campos, validación por OCR de comprobantes, importación masiva (CSV/XLSX/imágenes), gestión de beneficiarios, métricas de uso, finanzas y notificaciones.

    Las respuestas de error incluyen `error.detail` traducible. La cabecera `Accept-Language: es` o `Accept-Language: en` permite negociar el idioma; `error.code` es el contrato estable y NO se traduce.

    Todas las fechas se emiten en UTC; cada respuesta lo confirma en `meta.datetime`.

    Las integraciones programáticas se autentican mediante una clave de API (`Authorization: Bearer veriko_…`).
  version: 1.70.0
  contact:
    name: Veriko Support
    email: soporte@veriko.mx
  license:
    name: Proprietary
  x-translations:
    en:
      title: Veriko API
      description: |-
        REST API to validate Mexican SPEI bank transfers against Banxico CEP. Supports field-based manual validation, OCR receipt validation, bulk import (CSV/XLSX/images), beneficiary management, usage metrics, finance and notifications.

        Error responses carry a translatable `error.detail`. The `Accept-Language: es` or `Accept-Language: en` header negotiates the language; `error.code` is the stable contract and is NOT translated.

        All datetimes are emitted in UTC; each response confirms it in `meta.datetime`.

        Programmatic integrations authenticate with an API key (`Authorization: Bearer veriko_…`).
servers:
  - url: https://api.veriko.mx/v1
    description: Production — programmatic access via API key
    x-translations:
      en:
        description: Production — programmatic access via API key
security:
  - ApiKeyAuth: []
tags:
  - name: Public
    description: Recursos públicos de solo lectura — catálogo de bancos SPEI y otros datos abiertos.
    x-translations:
      en:
        description: Public read-only resources — SPEI bank catalog and other open data.
  - name: Validations
    description: SPEI transfer validation against Banxico CEP — direct fields and OCR receipts.
    x-translations:
      en:
        description: SPEI transfer validation against Banxico CEP — direct fields and OCR receipts.
  - name: Beneficiaries
    description: Saved beneficiary accounts (CLABE, debit/credit card or phone) and bulk import.
    x-translations:
      en:
        description: Saved beneficiary accounts (CLABE, debit/credit card or phone) and bulk import.
  - name: Banxico Status
    description: Banxico CEP service health status and latency timeseries.
    x-translations:
      en:
        description: Banxico CEP service health status and latency timeseries.
  - name: Plans
    description: Subscription plan listing and self-service upgrade.
    x-translations:
      en:
        description: Subscription plan listing and self-service upgrade.
  - name: Billing
    description: Subscription, invoices, credits, refunds and Stripe Checkout/Portal sessions.
    x-translations:
      en:
        description: Subscription, invoices, credits, refunds and Stripe Checkout/Portal sessions.
  - name: Finance
    description: Monthly statement, SAT/fiscal reports and bulk CEP archive download.
    x-translations:
      en:
        description: Monthly statement, SAT/fiscal reports and bulk CEP archive download.
  - name: Usage
    description: API usage statistics — summary, history, breakdown, limits and heatmap.
    x-translations:
      en:
        description: API usage statistics — summary, history, breakdown, limits and heatmap.
  - name: Insights
    description: Self-scoped metrics — volume, latency, top banks, frequent beneficiaries.
    x-translations:
      en:
        description: Self-scoped metrics — volume, latency, top banks, frequent beneficiaries.
  - name: Webhooks
    description: Outgoing webhook subscriptions and delivery log.
    x-translations:
      en:
        description: Outgoing webhook subscriptions and delivery log.
  - name: Users
    description: Recursos de la cuenta del usuario actual — perfil, inicio y cierre de sesión, restablecimiento de contraseña, API key, 2FA, cambio de email y política de reintentos.
    x-translations:
      en:
        description: Current user account resources — profile, login and logout, password reset, API key, 2FA, email change and default retry policy.
  - name: Dashboard
    description: Dashboard summary aggregations.
    x-translations:
      en:
        description: Dashboard summary aggregations.
paths:
  /users/me:
    get:
      x-related:
        - GET /v1/users/me/retry-policy
        - PUT /v1/users/me/retry-policy
      tags:
        - Users
      summary: Obtener la información de una cuenta de usuario
      description: |-
        Devuelve en una sola llamada todos los datos recabados del usuario
        autenticado:

        - **Perfil**: identificador, correo electrónico, nombre, rol, estado, zona
          horaria e idioma.
        - **Datos de la clave de API**: el prefijo y los últimos 4 caracteres, nunca
          la clave.
        - **Datos de la suscripción**: el plan, el estado y el fin del periodo en
          curso.
        - **Estado de la autenticación de 2 factores (2FA)**.
        - **Permisos del rol**: como lista plana de pares `resource:action`.
        - **Resumen de notificaciones**: notificaciones sin leer, envío push y
          vínculo con Telegram.

        Los campos de perfil que solo tienen sentido en una sesión interactiva —el
        teléfono, el último acceso— se quedan fuera y los sirve
        `GET /v1/users/me/profile-detail`.

        {% callout type="info" %}
        Cuando alguna de esas piezas no se puede calcular, la respuesta llega con el
        resto y añade `_warnings` con el nombre de la pieza que falta.

        {% /callout %}
      operationId: myProfile
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/users/me' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Información completa del usuario autenticado.
          x-translations:
            en:
              description: Complete information for the authenticated user.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        allOf:
                          - $ref: '#/components/schemas/JsonApiResourceBase'
                          - type: object
                            properties:
                              type:
                                type: string
                                enum:
                                  - user
                              id:
                                type: string
                                format: uuid
                              attributes:
                                $ref: '#/components/schemas/UserMeBundle'
              examples:
                bundle_basic:
                  summary: Información completa sin 2FA activo
                  x-translations:
                    en:
                      summary: Complete information without 2FA enabled
                  value:
                    data:
                      type: user
                      id: b1e4a2c3-1234-4abc-8def-000000000001
                      attributes:
                        id: b1e4a2c3-1234-4abc-8def-000000000001
                        email: carlos@example.com
                        name: Carlos Lopez
                        role: user
                        status: active
                        timezone: America/Mexico_City
                        language: es
                        email_verified_at: '2026-04-30T10:15:00Z'
                        created_at: '2026-01-15T08:00:00Z'
                        api_key:
                          prefix: veriko_a1b2
                          last_4: f9c2
                        subscription:
                          plan_slug: pro
                          plan_name: Pro
                          status: active
                          current_period_end: '2026-06-15T00:00:00Z'
                          cancel_at: null
                          billing_interval: month
                        two_factor:
                          enabled: false
                          method: sms
                          last_used_at: null
                        permissions:
                          - validations:read
                          - validations:create
                          - beneficiaries:read
                          - users:read_self
                        notifications:
                          unread_count: 2
                          push_enabled: false
                          telegram_linked: false
                    meta:
                      version: 1.48.0
                      request_id: a2b3c4d5e6f7a8b9c0d1e2f3
        '401':
          $ref: '#/components/responses/Unauthorized'
      x-translations:
        en:
          summary: Get the current user's complete information
          description: |-
            Returns in a single call everything the application needs to know about
            the authenticated user:

            - Their profile: identifier, email, name, role, status, timezone and
              language.
            - Their API key data: the prefix and the last 4 characters, never the
              key.
            - Their subscription, with the plan, the status and the end of the
              current period.
            - Two-factor authentication (2FA) state.
            - The role permissions, as a flat list of `resource:action` pairs.
            - The notifications summary: unread, push delivery and Telegram link.

            The profile fields that only make sense in an interactive session — the
            phone, the last sign-in — are left out and served by
            `GET /v1/users/me/profile-detail`.

            {% callout type="info" %}
            When one of those pieces cannot be computed, the response still arrives
            with the rest and adds `_warnings` naming the missing one. That is what
            allows rendering an incomplete panel instead of a blank one.

            {% /callout %}
      security:
        - ApiKeyAuth: []
  /validate:
    post:
      x-collapsed-200-links: false
      tags:
        - Validations
      summary: Validar manualmente una transferencia SPEI
      description: |-
        Valida manualmente una transferencia SPEI para obtener el comprobante (CEP) de **Banxico**.

        Se requieren los siguientes datos:

        - Fecha.
        - Monto.
        - Clave de rastreo / Referencia numérica.
        - Banco emisor.
        - Banco receptor.
        - Cuenta beneficiaria, o una lista de cuentas candidatas.

        La cuenta beneficiaria (`cuenta_beneficiaria`) es obligatoria en esta operación, salvo que se envíe `cuentas_candidatas`. Si faltan las dos, la petición se rechaza con `422 preflight_failed` y el error de campo `cuenta_required`: el servicio no la busca entre los beneficiarios guardados.

        Cuando no se sabe a cuál de varias cuentas se hizo el pago, `cuentas_candidatas` sustituye a `cuenta_beneficiaria`: de 2 a 3 cuentas en una sola validación y con una sola unidad de cuota. La consulta recorre las candidatas en el orden enviado y adopta la primera que coincide con la transferencia; esa cuenta vuelve completa en `normalized_data.cuenta_beneficiaria` y su posición en `candidate_match`. Si ninguna coincide, la respuesta es un estado HTTP `422` con el motivo en `code`. Las dos formas no pueden enviarse juntas (`422 cuenta_y_candidatas_excluyentes`) y una lista que no cumple se rechaza con `422 cuentas_candidatas_invalidas`, las dos antes de consumir cuota.

        El veredicto final (estado terminal) de una validación puede ser alguno de estos:

        - `valid`: El CEP de la transacción fue encontrado y validado.
        - `not_found`: El CEP de la transacción NO fue encontrado.
        - `cep_unavailable`: Banxico reconoció la transacción pero el CEP no está disponible por el momento.
        - `returned`: la transacción se liquidó y la institución beneficiaria la devolvió después. El CEP, si existe, se sigue entregando.
        - `error`: Error interno del servicio.

        Un veredicto `not_found`, `cep_unavailable` o `error` no es necesariamente definitivo: el CEP puede tardar en ser publicado. En ese caso se puede hacer uso de los **reintentos automáticos**, enviando `retry_policy` en el cuerpo de la petición según {% concept slug="retry-policy" %}la política de reintentos{% /concept %}.

        {% callout type="info" %}
        **Modo asíncrono:**\
        Enviando `?async=1` en la URL, la operación se procesa en segundo plano y la respuesta es un estado HTTP `202` inmediato con el ID de la validación en el cuerpo.\
        \
        El veredicto se recoge sondeando `GET /v1/validations/{id}` hasta un estado terminal. El uso de `ETag` para no descargar dos veces lo mismo está descrito en {% concept slug="async-validations" %}las operaciones asíncronas{% /concept %}.

        {% /callout %}

        {% callout type="info" %}
        **Idempotencia:**\
        La cabecera `Idempotency-Key` es opcional y evita duplicar una validación cuando se repite una petición. Las respuestas `5xx` no se guardan, de modo que un fallo del servidor no deja la clave de idempotencia inutilizable.\
        Reutilizar la cabecera con un cuerpo distinto provoca un estado HTTP `422` (con `idempotency_key_reused` en el cuerpo).\
        Reutilizar la cabecera mientras la operación anterior sigue en curso provoca un estado HTTP `409` (con `idempotency_key_in_progress` en el cuerpo).

        {% /callout %}

        {% callout type="warning" %}
        **Políticas de cobro (cuota):**\
        **Cada llamada consume cuota del plan** y se descuenta al aceptar la petición. Si se agota la cuota, el endpoint responde con un estado HTTP `429` sin intentar la validación.

        {% /callout %}
      operationId: validateDirect
      externalDocs:
        url: https://docs.veriko.mx/how-to/validate-spei
        description: 'How-to: validación manual de una transferencia SPEI'
      parameters:
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
        - $ref: '#/components/parameters/AsyncQueryParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidationRequest'
            examples:
              tracking_only:
                summary: Validación solo con clave de rastreo
                value:
                  fecha: '2025-03-15'
                  monto: 15000.5
                  clave_rastreo: MXBA20250315001234
                  emisor: BANCO NACIONAL DE MEXICO
                  receptor: BBVA MEXICO
                  cuenta_beneficiaria: '012180004412345678'
              reference_only:
                summary: Validación solo con referencia numérica
                value:
                  fecha: '2025-03-15'
                  monto: 15000.5
                  referencia_numerica: '1234567'
                  emisor: BANCO NACIONAL DE MEXICO
                  receptor: BBVA MEXICO
                  cuenta_beneficiaria: '012180004412345678'
              both_identifiers:
                summary: Clave de rastreo + referencia (mayor precisión)
                value:
                  fecha: '2025-03-15'
                  monto: 15000.5
                  clave_rastreo: MXBA20250315001234
                  referencia_numerica: '1234567'
                  emisor: BANCO NACIONAL DE MEXICO
                  receptor: BBVA MEXICO
                  cuenta_beneficiaria: '012180004412345678'
              candidate_accounts:
                summary: Varias cuentas candidatas, una sola unidad de cuota
                value:
                  fecha: '2025-03-15'
                  monto: 15000.5
                  clave_rastreo: MXBA20250315001234
                  emisor: BANCO NACIONAL DE MEXICO
                  cuentas_candidatas:
                    - '012180004412345678'
                    - '002010077777777771'
              phone_dimo:
                summary: Cuenta beneficiaria celular DiMo (10 dígitos)
                value:
                  fecha: '2025-03-15'
                  monto: 5000
                  clave_rastreo: MXBA20250315009876
                  emisor: BANCO NACIONAL DE MEXICO
                  receptor: BANAMEX
                  cuenta_beneficiaria: '5512345678'
              with_retry_policy:
                summary: Con política de reintentos automáticos en el cuerpo
                value:
                  fecha: '2025-03-15'
                  monto: 15000.5
                  clave_rastreo: MXBA20250315001234
                  cuenta_beneficiaria: '012180004412345678'
                  retry_policy:
                    enabled: true
                    max_retries: 3
                    interval_seconds: 600
                    outcomes:
                      - not_found
                      - cep_unavailable
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/validate' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json' \
              -d '{
                "fecha": "2025-03-15",
                "monto": 15000.5,
                "clave_rastreo": "MXBA20250315001234",
                "referencia_numerica": "1234567",
                "emisor": "BANCO NACIONAL DE MEXICO",
                "receptor": "BBVA MEXICO",
                "cuenta_beneficiaria": "012180004412345678"
              }'
      responses:
        '200':
          description: 'Veredicto final de la validación. El campo `status` puede ser: `valid`, `not_found`, `cep_unavailable`, `returned` o `error`. Cuando la respuesta trae `links.cep_pdf` y `links.cep_xml`, el certificado CEP está disponible en `GET /v1/validations/{id}/cep`.'
          x-translations:
            en:
              description: Final validation verdict. The `status` field can be `valid`, `not_found`, `cep_unavailable`, `returned`, or `error`. When the response carries `links.cep_pdf` and `links.cep_xml`, the CEP certificate is available through `GET /v1/validations/{id}/cep`.
          headers:
            Idempotent-Replayed:
              schema:
                type: string
                enum:
                  - 'true'
                  - 'false'
              description: Presente solo cuando la petición incluyó la cabecera `Idempotency-Key`. `false` para respuestas frescas; `true` cuando la respuesta es reutilizada desde el caché de idempotencia, con una vigencia de 24 horas por combinación de cuenta, endpoint y clave.
              x-translations:
                en:
                  description: Only present when the request included the `Idempotency-Key` header. `false` marks a fresh response; `true` marks a response reused from the idempotency cache, which remains valid for 24 hours per account, endpoint, and key.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/Validation'
              examples:
                valid_found:
                  summary: Transferencia confirmada en el CEP
                  value:
                    data:
                      id: f47ac10b-58cc-4372-a567-0e02b2c3d479
                      type: validation
                      attributes:
                        validation_type: direct
                        is_playground: false
                        status: valid
                        banxico_status: valid
                        processing_time_ms: 1320
                        request_data:
                          fecha: '2025-03-15'
                          monto: 15000.5
                          clave_rastreo: MXBA20250315001234
                          referencia_numerica: '1234567'
                          emisor: BANCO NACIONAL DE MEXICO
                          receptor: BBVA MEXICO
                          cuenta_beneficiaria: '012180004412345678'
                        created_at: '2025-03-15T14:22:10Z'
                        completed_at: '2025-03-15T14:22:11Z'
                        retry_state:
                          enabled: false
                          max_retries: null
                          interval_seconds: null
                          outcomes: null
                          attempts_completed: 0
                          next_attempt_at: null
                          resolved_at: null
                          exhausted_at: null
                          cancelled_at: null
                          terminal_state: null
                      links:
                        self: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479
                        cep_xml: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479/cep?format=xml
                        cep_pdf: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479/cep?format=pdf
                    meta:
                      version: 1.51.0
                      request_id: b3c4d5e6f7a8
                not_found:
                  summary: Transferencia no encontrada en el CEP
                  value:
                    data:
                      id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                      type: validation
                      attributes:
                        validation_type: direct
                        is_playground: false
                        status: not_found
                        banxico_status: not_found
                        processing_time_ms: 980
                        created_at: '2025-03-16T09:10:00Z'
                        completed_at: '2025-03-16T09:10:01Z'
                    meta:
                      version: 1.51.0
                      request_id: c4d5e6f7a8b9
                cep_unavailable:
                  summary: Banxico no respondió a tiempo
                  value:
                    data:
                      id: b2c3d4e5-f6a7-8901-bcde-f12345678901
                      type: validation
                      attributes:
                        validation_type: direct
                        is_playground: false
                        status: cep_unavailable
                        banxico_status: cep_unavailable
                        created_at: '2025-03-15T18:05:30Z'
                        completed_at: '2025-03-15T18:05:31Z'
                    meta:
                      version: 1.51.0
                      request_id: d5e6f7a8b9c0
        '202':
          $ref: '#/components/responses/ValidationQueuedAccepted'
        '400':
          $ref: '#/components/responses/InvalidIdempotencyKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/IdempotencyKeyInProgress'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: 'Falló la validación de la petición. Códigos típicos: `clave_or_ref_required`, `required` (fecha/monto), `invalid_date`, `invalid_amount`, `invalid_account_format`, `invalid_account_length`, `invalid_clabe_checksum`, `invalid_card_luhn`, `invalid_bank_code` (`emisor`/`receptor` no reconocidos), `intra_bank_no_cep` (emisor y receptor son el mismo banco), `invalid_field_type` (un campo que debe ser texto llegó como arreglo u objeto — p. ej. `emisor`, `receptor` o `referencia_numerica`), `invalid_receptor_participante`, `retry_policy_invalid`, `retry_pending_cap_exceeded`, `cuenta_y_candidatas_excluyentes` (`cuenta_beneficiaria` y `cuentas_candidatas` juntas), `cuentas_candidatas_invalidas` (la lista no tiene de 2 al máximo vigente de cuentas válidas y distintas) y `cuenta_unresolvable_after_probes` (ninguna candidata coincidió con la transferencia). También se emite cuando se reutiliza `Idempotency-Key` con un cuerpo distinto (`idempotency_key_reused`). Cuando los datos se contradicen entre sí, la petición se rechaza antes de consultar a Banxico con `preflight_failed` y el detalle por campo en `errors` (`clabe_receptor_mismatch` y `tarjeta_receptor_mismatch`: la cuenta pertenece a otro banco que el `receptor`; `cuenta_invalid_luhn`; `clave_fecha_incoherente`; `clave_longitud_invalida`). También se rechaza con `preflight_failed` cuando falta `cuenta_beneficiaria` (`cuenta_required`). Ningún rechazo `preflight_failed` de esta ruta consume cuota, sea por datos que se contradicen, por un campo mal formado o por una cuenta ausente.'
          x-translations:
            en:
              description: 'Request validation failed. Typical codes: `clave_or_ref_required`, `required` (fecha/monto), `invalid_date`, `invalid_amount`, `invalid_account_format`, `invalid_account_length`, `invalid_clabe_checksum`, `invalid_card_luhn`, `invalid_bank_code` (`emisor`/`receptor` not recognized), `intra_bank_no_cep` (sender and receiver are the same bank), `invalid_field_type` (a field that must be text arrived as an array or object — e.g. `emisor`, `receptor`, or `referencia_numerica`), `invalid_receptor_participante`, `retry_policy_invalid`, `retry_pending_cap_exceeded`, `cuenta_y_candidatas_excluyentes` (`cuenta_beneficiaria` and `cuentas_candidatas` together), `cuentas_candidatas_invalidas` (the list does not hold from 2 up to the current maximum of valid, distinct accounts) and `cuenta_unresolvable_after_probes` (no candidate matched the transfer). Also emitted when an `Idempotency-Key` is reused with a different body (`idempotency_key_reused`). When the submitted data contradicts itself, the request is rejected before Banxico is queried with `preflight_failed` and per-field detail in `errors` (`clabe_receptor_mismatch` and `tarjeta_receptor_mismatch`: the account belongs to a bank other than `receptor`; `cuenta_invalid_luhn`; `clave_fecha_incoherente`; `clave_longitud_invalida`). It is also rejected with `preflight_failed` when `cuenta_beneficiaria` is missing (`cuenta_required`). No `preflight_failed` rejection on this route consumes quota, whether the data contradicts itself, a field is malformed or the account is absent.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          description: Banxico no estuvo disponible (`banxico_rate_limit_exhausted`) o no fue posible iniciar la validación asíncrona (`dispatch_failed`). La petición puede repetirse después de unos minutos.
          x-translations:
            en:
              description: Banxico was unavailable (`banxico_rate_limit_exhausted`) or the asynchronous validation could not be started (`dispatch_failed`). The request can be repeated after a few minutes.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-related:
        - POST /v1/validate-ocr
        - GET /v1/validations/{id}
        - PUT /v1/validations/{id}/retry-policy
        - GET /v1/validations
      x-translations:
        en:
          summary: Manually validate a SPEI transfer
          description: |-
            Validates a SPEI transfer against the **Banxico** CEP from the fields
            you send.

            The following data is required:

            - Date.
            - Amount.
            - Tracking key / Numeric reference.
            - Sending bank.
            - Receiving bank.
            - Beneficiary account, or a list of candidate accounts.

            The beneficiary account (`cuenta_beneficiaria`) is required on this operation, unless `cuentas_candidatas` is sent. When both are missing, the request is rejected with `422 preflight_failed` and the field error `cuenta_required`: the service does not look it up among the saved beneficiaries.

            When it is not known which of several accounts the payment went to, `cuentas_candidatas` replaces `cuenta_beneficiaria`: 2 to 3 accounts in a single validation and with a single quota unit. The query goes through the candidates in the order sent and adopts the first one that matches the transfer; that account comes back in full in `normalized_data.cuenta_beneficiaria` and its position in `candidate_match`. If none matches, the response is an HTTP status `422` with the reason in `code`. The two forms cannot be sent together (`422 cuenta_y_candidatas_excluyentes`) and a list that does not comply is rejected with `422 cuentas_candidatas_invalidas`, both before any quota is consumed.

            The final verdict (terminal state) can be one of the following:

            - `valid`: The transfer CEP was found and validated.
            - `not_found`: The transfer CEP was NOT found.
            - `cep_unavailable`: Banxico recognized the transfer, but the CEP is not
              currently available.
            - `returned`: the transfer was settled and the beneficiary institution
              later sent it back. The CEP, when there is one, is still delivered.
            - `error`: Internal service error.

            A `not_found`, `cep_unavailable`, or `error` verdict is not necessarily
            final: the CEP may take time to be published. In that case, **automatic
            retries** can be used by sending `retry_policy` in the request body,
            according to {% concept slug="retry-policy" %}the retry policy{% /concept
            %}.

            {% callout type="info" %}
            **Asynchronous mode:**\
            With `?async=1` in the URL, the operation runs in the background and
            returns an immediate HTTP status `202` with the validation ID in the
            body.\
            \
            The verdict is collected by polling `GET /v1/validations/{id}` until a
            terminal state. The use of `ETag` to avoid downloading the same content
            twice is covered in {% concept slug="async-validations" %}asynchronous
            operations{% /concept %}.

            {% /callout %}

            {% callout type="info" %}
            **Idempotency:**\
            The `Idempotency-Key` header is optional and prevents duplicate
            validations when a request is repeated. `5xx` responses are not stored,
            so a server failure does not make the idempotency key unusable.\
            Reusing the header with a different body produces HTTP status `422`
            (with `idempotency_key_reused` in the body).\
            Reusing it while the previous operation is still in progress produces
            HTTP status `409` (with `idempotency_key_in_progress` in the body).

            {% /callout %}

            **Each call consumes plan quota**, deducted when the request is
            accepted. If the quota is exhausted, the endpoint returns HTTP status
            `429` without attempting the validation.
      security:
        - ApiKeyAuth: []
  /validate-ocr:
    post:
      tags:
        - Validations
      summary: Validar una transferencia SPEI (OCR)
      description: |-
        Valida una transferencia SPEI a partir de su comprobante, en imagen o en PDF. Extrae mediante reconocimiento óptico (OCR) los datos del comprobante y los valida en **Banxico** para obtener el CEP de la transacción.

        El comprobante se envía como `image` o `image_url`. Si es enviado mediante una URL, el host destino deberá contar con protocolo seguro (SSL/HTTPS).

        {% callout type="info" %}
        **Comprobante en PDF:**\
        Se acepta un PDF de 1 a 3 páginas, con el mismo máximo de `12 MB` que una imagen.
        Se rechaza si está cifrado, si se modificó después de emitirse o si trae contenido activo u oculto.\
        Si el PDF es el CEP de Banxico, sus datos se leen de su texto sin OCR y `banxico_result._cep_upload` indica
        si su sello y su cadena original coinciden con los del CEP oficial. El veredicto no cambia por esa comparación.\
        Un PDF con más de un comprobante se rechaza con `pdf_multiple_receipts`.

        {% /callout %}

        {% callout type="info" %}
        El campo `cuenta_beneficiaria` permite enviar explícitamente la cuenta receptora de la transferencia, enviarlo es especialmente útil cuando la imagen no lleva este dato o lo lleva incompleto.\
        Un beneficiario guardado (con `POST /v1/beneficiaries`) no sustituye a este campo: solo sirve para completar los dígitos de la cuenta que la imagen sí muestra. Si la imagen no trae dígitos suficientes, la validación se rechaza con `cuenta_unresolvable`, porque las cuentas guardadas no se prueban una por una.\
        \
        Cuando no se sabe cuál de varias cuentas recibió el pago, `cuentas_candidatas` las prueba en una sola validación (de 2 a 3 cuentas, una sola unidad de cuota, también con `?async=1`) y sustituye a `cuenta_beneficiaria`: enviar las dos se rechaza con `422 cuenta_y_candidatas_excluyentes`, y una lista que no cumple, con `422 cuentas_candidatas_invalidas`, las dos antes de consumir cuota. La lista tiene prioridad sobre la cuenta que se lea en la imagen; la ganadora vuelve completa en `normalized_data.cuenta_beneficiaria` y su posición en `candidate_match`. Si ninguna coincide, la respuesta es un estado HTTP `422` con el motivo en `code`.

        {% /callout %}

        {% callout type="info" %}
        **Retención del comprobante:**\
        De forma predeterminada el archivo del comprobante se conserva y se sirve con `GET /v1/validations/{id}/image`. Con `retain_image=false`, se borra cuando la validación llega a un estado terminal del que ya no se necesita, y el veredicto y los datos extraídos se conservan. La validación publica `image_retained=false` y la imagen responde un estado HTTP `410` (con `image_not_retained` en el cuerpo).\
        \
        Un valor que no es booleano responde un estado HTTP `422` (con `invalid_retain_image` en el cuerpo), antes de consumir cuota. Para borrar después una validación y todo lo que dejó, está `POST /v1/validations/{id}/purge/prepare`, descrito en {% concept slug="data-retention" %}la retención de comprobantes y datos{% /concept %}.

        {% /callout %}

        El veredicto final es el mismo que en la validación manual:

        - `valid`: El CEP de la transacción fue encontrado y validado.
        - `not_found`: El CEP de la transacción NO fue encontrado.
        - `cep_unavailable`: Banxico reconoció la transacción pero el CEP no está disponible por el momento.
        - `returned`: la transacción se liquidó y la institución beneficiaria la devolvió después. El CEP, si existe, se sigue entregando.
        - `error`: Error interno del servicio.

        {% callout type="info" %}
        **Modo asíncrono:**\
        Enviando `?async=1` en la URL, el comprobante se comprueba antes de aceptar el trabajo y la respuesta es un estado HTTP `202` inmediato con el ID de la validación en el cuerpo.\
        \
        El veredicto se recoge sondeando `GET /v1/validations/{id}` hasta un estado terminal, como describe {% concept slug="async-validations" %}las operaciones asíncronas{% /concept %}.

        {% /callout %}

        {% callout type="info" %}
        **Idempotencia:**\
        La cabecera `Idempotency-Key` es opcional y evita duplicar una validación cuando se repite una petición. Las respuestas `5xx` no se guardan, de modo que un fallo del servidor no deja la clave de idempotencia inutilizable.\
        Reutilizar la cabecera con un cuerpo distinto provoca un estado HTTP `422` (con `idempotency_key_reused` en el cuerpo).\
        Reutilizar la cabecera mientras la operación anterior sigue en curso provoca un estado HTTP `409` (con `idempotency_key_in_progress` en el cuerpo).

        {% /callout %}

        {% callout type="warning" %}
        **Políticas de cobro (cuota):**\
        — Modo síncrono: Se descuenta la validación de la cuota al aceptar la petición, antes de comprobar el archivo. Es decir, un archivo inválido consume una validación.\
        — Modo asíncrono: El archivo se comprueba primero, así que un archivo inválido no consume nada.\
        \
        Un CEP en PDF que se lee sin OCR cuenta como una validación, igual que uno leído por OCR.\
        \
        La cuota sólo se devuelve cuando la validación termina en `error` por un fallo de la plataforma. Los reintentos automáticos posteriores no consumen una validación adicional, y el cómputo está en {% concept slug="quotas-and-plans" %}cuotas y planes{% /concept %}.

        {% /callout %}
      operationId: validateOcr
      externalDocs:
        url: https://docs.veriko.mx/how-to/validate-ocr
        description: Validate a SPEI transfer from a receipt image or PDF
      parameters:
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
        - $ref: '#/components/parameters/AsyncQueryParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OcrValidationRequest'
            examples:
              image_url_only:
                summary: Imagen remota vía URL
                value:
                  image_url: https://storage.example.com/receipts/spei-receipt-001.png
              image_url_with_hint:
                summary: URL con pista de cuenta beneficiaria
                value:
                  image_url: https://storage.example.com/receipts/spei-receipt-002.png
                  cuenta_beneficiaria: '012180004412345678'
              image_with_candidates:
                summary: Varias cuentas candidatas, una sola unidad de cuota
                value:
                  image_url: https://storage.example.com/receipts/spei-receipt-003.png
                  cuentas_candidatas:
                    - '012180004412345678'
                    - '002010077777777771'
              image_base64:
                summary: Imagen codificada en base64
                value:
                  image: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/validate-ocr' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json' \
              -d '{
                "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
              }'
      responses:
        '200':
          description: Validación resuelta con los datos extraídos del comprobante y su veredicto.
          x-translations:
            en:
              description: Resolved validation with the data extracted from the receipt and its verdict.
          headers:
            Idempotent-Replayed:
              schema:
                type: string
                enum:
                  - 'true'
                  - 'false'
              description: Presente solo cuando la petición incluyó `Idempotency-Key`. `false` identifica una respuesta nueva; `true`, una respuesta reutilizada desde el caché.
              x-translations:
                en:
                  description: Present only when the client sent `Idempotency-Key`. `false` for fresh responses; `true` when replayed from cache.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/Validation'
              examples:
                ocr_valid:
                  summary: OCR exitoso — transferencia confirmada
                  value:
                    data:
                      id: c3d4e5f6-a7b8-9012-cdef-123456789012
                      type: validation
                      attributes:
                        validation_type: ocr
                        is_playground: false
                        status: valid
                        banxico_status: valid
                        processing_time_ms: 2840
                        image_path: comprobantes/c3d4e5f6-a7b8-9012-cdef-123456789012.png
                        ocr_result:
                          fecha: '2025-04-10'
                          monto: 12500
                          clave_rastreo: MXBA20250410003456
                        ocr_confidence: 0.96
                        created_at: '2025-04-10T11:30:00Z'
                        completed_at: '2025-04-10T11:30:02Z'
                    meta:
                      version: 1.51.0
                      request_id: e6f7a8b9c0d1
                ocr_not_found:
                  summary: OCR extraído pero transferencia no encontrada
                  value:
                    data:
                      id: d4e5f6a7-b8c9-0123-defa-234567890123
                      type: validation
                      attributes:
                        validation_type: ocr
                        is_playground: false
                        status: not_found
                        banxico_status: not_found
                        ocr_confidence: 0.91
                        created_at: '2025-04-10T14:00:00Z'
                        completed_at: '2025-04-10T14:00:01Z'
                    meta:
                      version: 1.51.0
                      request_id: f7a8b9c0d1e2
        '202':
          $ref: '#/components/responses/ValidationQueuedAccepted'
        '400':
          $ref: '#/components/responses/InvalidIdempotencyKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/IdempotencyKeyInProgress'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: 'La imagen o los datos de la petición no son válidos. Códigos posibles: `image_or_image_url_required`, `invalid_image`, `invalid_image_format`, `image_too_large`, `invalid_url`, `invalid_url_scheme`, `url_ssrf_blocked`, `invalid_clabe_checksum`. También puede devolver: `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`. Con un 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` y `pdf_multiple_receipts`. Con `retain_image` que no es booleano: `invalid_retain_image`. Con `cuentas_candidatas`: `cuenta_y_candidatas_excluyentes`, `cuentas_candidatas_invalidas` y, si ninguna candidata coincide, `cuenta_unresolvable_after_probes`. En modo asíncrono, un fallo genérico de la imagen se reporta como `image_invalid`. Reutilizar `Idempotency-Key` con un cuerpo distinto produce `idempotency_key_reused`.'
          x-translations:
            en:
              description: '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`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          description: El OCR no está disponible, no pudo completarse la solicitud a Banxico (`banxico_rate_limit_exhausted`) o no fue posible iniciar la validación asíncrona (`dispatch_failed`).
          x-translations:
            en:
              description: OCR is unavailable, the Banxico request could not be completed (`banxico_rate_limit_exhausted`), or the asynchronous validation could not be started (`dispatch_failed`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-related:
        - POST /v1/validate
        - POST /v1/beneficiaries
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/image
        - POST /v1/validations/{id}/purge/prepare
      x-translations:
        en:
          summary: Validate a SPEI transfer (OCR)
          description: |-
            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).

            {% callout type="info" %}
            **Receipt as a PDF:**\
            A PDF of 1 to 3 pages is accepted, with the same `12 MB` maximum as an image.
            It is rejected when it is encrypted, when it was modified after being issued, or when it carries active or hidden content.\
            When the PDF is the Banxico CEP, its data is read from its text without OCR, and `banxico_result._cep_upload` shows
            whether its seal and original string match those of the official CEP. That comparison does not change the verdict.\
            A PDF with more than one receipt is rejected with `pdf_multiple_receipts`.

            {% /callout %}

            {% callout type="info" %}
            The `cuenta_beneficiaria` field sends the receiving account of the transfer explicitly, which is especially useful when the image does not carry that data or carries it incomplete.\
            A saved beneficiary (with `POST /v1/beneficiaries`) does not replace this field: it only completes the digits of the account that the image does show. When the image carries too few digits, the validation is rejected with `cuenta_unresolvable`, because the saved accounts are not tried one by one.\
            \
            When it is not known which of several accounts received the payment, `cuentas_candidatas` tries them in a single validation (2 to 3 accounts, a single quota unit, also with `?async=1`) and replaces `cuenta_beneficiaria`: sending both is rejected with `422 cuenta_y_candidatas_excluyentes`, and a list that does not comply, with `422 cuentas_candidatas_invalidas`, both before any quota is consumed. The list takes priority over the account read from the image; the winner comes back in full in `normalized_data.cuenta_beneficiaria` and its position in `candidate_match`. If none matches, the response is an HTTP status `422` with the reason in `code`.

            {% /callout %}

            {% callout type="info" %}
            **Receipt retention:**\
            By default the receipt file is kept and served with `GET /v1/validations/{id}/image`. With `retain_image=false`, it is deleted when the validation reaches a terminal status in which it is no longer needed, and the verdict and the extracted data are kept. The validation publishes `image_retained=false` and the image responds with an HTTP status `410` (with `image_not_retained` in the body).\
            \
            A value that is not a boolean responds with an HTTP status `422` (with `invalid_retain_image` in the body), before any quota is consumed. To delete a validation and everything it left behind afterward, there is `POST /v1/validations/{id}/purge/prepare`, described in {% concept slug="data-retention" %}the retention of receipts and data{% /concept %}.

            {% /callout %}

            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.

            {% callout type="info" %}
            **Asynchronous mode:**\
            With `?async=1` in the URL, the receipt is checked before the job is accepted and the response is an immediate HTTP status `202` with the validation ID in the body.\
            \
            The verdict is collected by polling `GET /v1/validations/{id}` until a terminal status, as {% concept slug="async-validations" %}asynchronous operations{% /concept %} describes.

            {% /callout %}

            {% callout type="info" %}
            **Idempotency:**\
            The `Idempotency-Key` header is optional and prevents duplicating a validation when a request is repeated. `5xx` responses are not stored, so a server failure does not leave the idempotency key unusable.\
            Reusing the header with a different body causes an HTTP status `422` (with `idempotency_key_reused` in the body).\
            Reusing the header while the previous operation is still in flight causes an HTTP status `409` (with `idempotency_key_in_progress` in the body).

            {% /callout %}

            {% callout type="warning" %}
            **Charging policy (quota):**\
            — Synchronous mode: the validation is charged against the quota when the request is accepted, before the file is checked. That is, an invalid file consumes a validation.\
            — Asynchronous mode: the file is checked first, so an invalid file consumes nothing.\
            \
            A CEP in PDF that is read without OCR counts as one validation, the same as one read through OCR.\
            \
            Quota is refunded only when the validation ends in `error` because of a platform failure. Later automatic retries do not consume an additional validation, and the accounting is in {% concept slug="quotas-and-plans" %}quotas and plans{% /concept %}.

            {% /callout %}
      security:
        - ApiKeyAuth: []
  /validations:
    get:
      tags:
        - Validations
      summary: Listar validaciones
      description: |-
        Devuelve el historial paginado de validaciones del usuario autenticado.

        Los filtros se combinan entre sí. Por ejemplo:

        - `status`: Acepta un estado, o varios separados por comas.
        - `with_deleted`: Distingue entre los registros activos y los retirados.
        - **Los demás** acotan por: modalidad, fecha, contenido, banco, importe, origen, referencia propia (`client_ref`) y ciclo de reintentos.

        La forma de la respuesta está descrita en {% concept slug="pagination" %}la paginación{% /concept %}.

        Para el registro completo de una validación, con el estado detallado de sus reintentos, está `GET /v1/validations/{id}`.
      operationId: listValidations
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
          example: 1
          description: Filtro — Página solicitada; los valores menores que `1` se ajustan a `1`.
          x-translations:
            en:
              description: Requested page; values below `1` are adjusted to `1`.
        - name: per_page
          in: query
          schema:
            type: integer
            default: 10
            minimum: 1
            maximum: 50
          example: 10
          description: Filtro — Elementos por página; se ajusta al intervalo de `1` a `50`.
          x-translations:
            en:
              description: Items per page; adjusted to the `1` to `50` range.
        - name: status
          in: query
          schema:
            type: string
          example: valid
          description: 'Filtro — Estado del ciclo de vida. Acepta un valor o una lista separada por comas: `queued`, `processing`, `valid`, `not_found`, `cep_unavailable`, `invalid`, `returned`, `failed` o `error`. Los valores no admitidos se ignoran.'
          x-translations:
            en:
              description: 'Lifecycle state. Accepts one value or a comma-separated list: `queued`, `processing`, `valid`, `not_found`, `cep_unavailable`, `invalid`, `returned`, `failed`, or `error`. Unsupported values are ignored.'
        - name: type
          in: query
          schema:
            type: string
            enum:
              - direct
              - ocr
          example: direct
          description: 'Filtro — Modalidad: `direct` para validación manual u `ocr` para validación a partir de una imagen.'
          x-translations:
            en:
              description: 'Mode: `direct` for manual validation or `ocr` for image-based validation.'
        - name: from
          in: query
          schema:
            type: string
            format: date
          example: '2025-01-01'
          description: Filtro — Fecha inicial inclusiva, aplicada sobre la creación de la validación. Enviarlo como lista (`from[]=`) devuelve un estado HTTP `422` con `invalid_filter`.
          x-translations:
            en:
              description: Inclusive start date, applied to the validation creation date. Sending it as a list (`from[]=`) returns HTTP status `422` with `invalid_filter`.
        - name: to
          in: query
          schema:
            type: string
            format: date
          example: '2025-03-31'
          description: Filtro — Fecha final inclusiva, aplicada sobre la creación de la validación. Enviarlo como lista (`to[]=`) devuelve un estado HTTP `422` con `invalid_filter`.
          x-translations:
            en:
              description: Inclusive end date, applied to the validation creation date. Sending it as a list (`to[]=`) returns HTTP status `422` with `invalid_filter`.
        - name: search
          in: query
          schema:
            type: string
            maxLength: 100
          example: scotiabank
          description: Filtro — Texto buscado, sin distinguir mayúsculas y minúsculas, en los datos enviados, normalizados y devueltos por Banxico. `%` y `_` se tratan como caracteres literales, y los 100 caracteres se cuentan por carácter (no por byte), así que un acento no se parte a la mitad. Si el texto, sin espacios ni guiones, queda compuesto sólo por dígitos, también se busca esa forma sin separadores contra la cuenta beneficiaria.
          x-translations:
            en:
              description: Case-insensitive text search over the submitted, normalized, and Banxico response data. `%` and `_` are treated as literal characters, and the 100-character limit is counted per character (not per byte), so an accented letter is never split in half. If the text, once spaces and hyphens are removed, is made up only of digits, that separator-free form is also matched against the beneficiary account.
        - name: client_ref
          in: query
          schema:
            type: string
            minLength: 1
            maxLength: 64
          example: orden-4812
          description: Coincidencia exacta con la `client_ref` enviada al validar. Distingue mayúsculas, acentos y espacios internos. Un valor vacío o que no cumple las reglas de `client_ref` devuelve un estado HTTP `422` (con `invalid_filter` en el cuerpo).
          x-translations:
            en:
              description: Exact match with the `client_ref` sent when validating. It distinguishes case, accents, and inner spaces. An empty value, or one that does not meet the `client_ref` rules, returns an HTTP status `422` (with `invalid_filter` in the body).
        - name: playground
          in: query
          schema:
            type: string
            enum:
              - '1'
          example: '1'
          description: Filtro — Muestra solo las validaciones del banco de pruebas (Playground). Si se envía, el único valor admitido es `1`; cualquier otro devuelve un estado HTTP `422`.
          x-translations:
            en:
              description: Keeps only playground validations. The only accepted value is `1`; any other value returns HTTP status `422`.
        - name: with_deleted
          in: query
          schema:
            type: string
            enum:
              - '0'
              - '1'
          example: '0'
          description: 'Filtro — Alcance de los registros retirados: omitido incluye activos y retirados; `0` muestra solo activos y `1` solo retirados.'
          x-translations:
            en:
              description: 'Scope for withdrawn records: omitted includes active and withdrawn records; `0` keeps active records only and `1`, withdrawn records only.'
        - name: batch_id
          in: query
          schema:
            type: integer
            minimum: 1
          example: 42
          description: Filtro — Identificador del trabajo de importación que generó las validaciones. Enviarlo como lista (`batch_id[]=`) devuelve un estado HTTP `422` con `invalid_filter` en vez de ignorarse.
          x-translations:
            en:
              description: Identifier of the import job that generated the validations. Sending it as a list (`batch_id[]=`) returns HTTP status `422` with `invalid_filter` instead of being ignored.
        - name: bank
          in: query
          schema:
            type: string
            pattern: ^\d{1,5}$
          example: '012'
          description: Filtro — Clave SPEI del banco emisor o receptor en la petición original. Los valores que no tengan de 1 a 5 dígitos se ignoran; enviarlo como lista (`bank[]=`) devuelve un estado HTTP `422` con `invalid_filter`.
          x-translations:
            en:
              description: SPEI code of the sending or receiving bank in the original request. Values outside 1 to 5 digits are ignored; sending it as a list (`bank[]=`) returns HTTP status `422` with `invalid_filter`.
        - name: amount_min
          in: query
          schema:
            type: number
            format: float
          example: 1000.5
          description: 'Filtro — Importe mínimo inclusivo, como número. El servidor también tolera enviarlo como texto con separador de miles o símbolo de moneda ("1,000.50", "$500") y lo normaliza con el mismo algoritmo que el resto de la plataforma, pero el contrato es un número: un cliente tipado debe mandarlo así. Un valor no vacío que no se reconozca como número devuelve un estado HTTP `422` con `invalid_filter`, en vez de ignorarse en silencio. Se aplica de forma independiente de `amount_max`.'
          x-translations:
            en:
              description: 'Inclusive minimum amount, as a number. The server also tolerates sending it as text with a thousands separator or a currency symbol ("1,000.50", "$500") and normalizes it with the same algorithm used across the platform, but the contract is a number: a typed client should send it that way. A non-empty value that isn''t recognized as a number returns HTTP status `422` with `invalid_filter`, instead of being silently ignored. Applied independently of `amount_max`.'
        - name: amount_max
          in: query
          schema:
            type: number
            format: float
          example: 50000
          description: Filtro — Importe máximo inclusivo, como número. Mismas reglas de tolerancia de formato y de error que `amount_min`. Se aplica de forma independiente de `amount_min`.
          x-translations:
            en:
              description: Inclusive maximum amount, as a number. Same formatting-tolerance and error rules as `amount_min`. Applied independently of `amount_min`.
        - name: retry_state
          in: query
          schema:
            type: string
            enum:
              - pending
              - resolved
              - exhausted
              - cancelled
          example: pending
          description: 'Filtro — Estado del ciclo de reintentos: `pending`, `resolved`, `exhausted` o `cancelled`. Otro valor devuelve un estado HTTP `422` con `invalid_filter`.'
          x-translations:
            en:
              description: 'Retry cycle state: `pending`, `resolved`, `exhausted`, or `cancelled`. Any other value returns HTTP status `422` with `invalid_filter`.'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/validations' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Lista paginada de validaciones con metadatos de paginación.
          x-translations:
            en:
              description: Paginated list of validations with pagination metadata.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/PublicValidationListItem'
                      meta:
                        type: object
                        properties:
                          pagination:
                            type: object
                            properties:
                              page:
                                type: integer
                                example: 1
                              per_page:
                                type: integer
                                example: 10
                              total:
                                type: integer
                                example: 47
                              total_pages:
                                type: integer
                                example: 5
              examples:
                with_results:
                  summary: Lista con validaciones de distintos estados
                  value:
                    data:
                      - id: f47ac10b-58cc-4372-a567-0e02b2c3d479
                        type: validation
                        attributes:
                          bank_name: BBVA MEXICO
                          bank_code: '40012'
                          beneficiary_label: Proveedor X
                          amount: 15000.5
                          tracking_key: MXBA20250315001234
                          referencia_numerica: '1234567'
                          beneficiary_account: '012180004412345678'
                          status: valid
                          banxico_status: valid
                          validation_type: direct
                          is_playground: false
                          created_at: '2025-03-15T14:22:10Z'
                          deleted_at: null
                          retry_state:
                            enabled: false
                            attempts_completed: 0
                            next_attempt_at: null
                            terminal_state: null
                      - id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                        type: validation
                        attributes:
                          bank_name: SCOTIABANK
                          bank_code: '40044'
                          beneficiary_label: null
                          amount: 8500
                          tracking_key: MXBA20250316005555
                          referencia_numerica: ''
                          beneficiary_account: '021180040900123456'
                          status: not_found
                          banxico_status: not_found
                          validation_type: ocr
                          is_playground: false
                          created_at: '2025-03-16T09:10:00Z'
                          deleted_at: null
                          retry_state:
                            enabled: false
                            attempts_completed: 0
                            next_attempt_at: null
                            terminal_state: null
                    meta:
                      version: 1.51.0
                      request_id: e1f2a3b4c5d6
                      pagination:
                        page: 1
                        per_page: 10
                        total: 47
                        total_pages: 5
                empty:
                  summary: Lista vacía (sin validaciones)
                  value:
                    data: []
                    meta:
                      version: 1.51.0
                      request_id: f2a3b4c5d6e7
                      pagination:
                        page: 1
                        per_page: 10
                        total: 0
                        total_pages: 0
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: 'Un filtro no se pudo aplicar (código `invalid_filter`): valor no admitido en `retry_state`, `playground` o `amount_min`/`amount_max` con formato irreconocible; o tipo incorrecto en `from`, `to`, `search`, `bank` o `batch_id` (por ejemplo, enviados como lista `from[]=`).'
          x-translations:
            en:
              description: 'A filter could not be applied (code `invalid_filter`): unsupported value in `retry_state`, `playground`, or `amount_min`/`amount_max` with an unrecognized format; or the wrong type in `from`, `to`, `search`, `bank`, or `batch_id` (for example, sent as a list `from[]=`).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-related:
        - GET /v1/validations/{id}
        - POST /v1/validate
        - POST /v1/validate-ocr
        - GET /v1/validations/export
        - GET /v1/validations/stats
      x-translations:
        en:
          summary: List validations
          description: |-
            Returns the paginated validation history of the authenticated account.

            The filters combine with each other:

            - `status` accepts one status, or several separated by commas.
            - `with_deleted` distinguishes active records from withdrawn ones.
            - The rest narrow by mode, date, content, bank, amount, origin, reference of your own (`client_ref`) and retry cycle.

            The shape of the response is described in {% concept slug="pagination" %}pagination{% /concept %}.

            For the complete record of a validation, with the detailed state of its retries, there is `GET /v1/validations/{id}`.
      security:
        - ApiKeyAuth: []
  /validations/stats:
    get:
      x-related:
        - DELETE /v1/validations/{id}
        - GET /v1/validations
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/cep
      tags:
        - Validations
      summary: Obtener las estadísticas de validaciones de la cuenta de usuario
      description: |-
        Devuelve contadores agregados de las validaciones de la cuenta:

        - Los contadores de primer nivel —`valid`, `not_found`, `cep_unavailable`, `returned`, `error` y `pending`— agrupan por `banxico_status`.
        - `by_status` agrupa por estado del ciclo de vida.
        - `by_type` separa las modalidades `direct` y `ocr`.

        Acepta los mismos filtros que `GET /v1/validations`, con una excepción: `status` se admite pero no modifica los contadores (cada uno lleva ya su propio objetivo).
      operationId: validationStats
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum:
              - direct
              - ocr
          example: direct
          description: 'Filtro — Modalidad: `direct` para validación manual; `ocr` para validación a partir de una imagen.'
          x-translations:
            en:
              description: 'Mode: `direct` for manual validation; `ocr` for image-based validation.'
        - name: from
          in: query
          schema:
            type: string
            format: date
          example: '2025-01-01'
          description: Filtro — Fecha inicial inclusiva, aplicada sobre la creación de la validación.
          x-translations:
            en:
              description: Inclusive start date, applied to the validation creation date.
        - name: to
          in: query
          schema:
            type: string
            format: date
          example: '2025-03-31'
          description: Filtro — Fecha final inclusiva, aplicada sobre la creación de la validación.
          x-translations:
            en:
              description: Inclusive end date, applied to the validation creation date.
        - name: search
          in: query
          schema:
            type: string
            maxLength: 100
          example: MXBA
          description: Filtro — Texto buscado, sin distinguir mayúsculas y minúsculas, en los datos enviados, normalizados y devueltos por Banxico. `%` y `_` se tratan como caracteres literales.
          x-translations:
            en:
              description: Case-insensitive text search over the submitted, normalized, and Banxico response data. `%` and `_` are treated as literal characters.
        - name: client_ref
          in: query
          schema:
            type: string
            minLength: 1
            maxLength: 64
          example: orden-4812
          description: Coincidencia exacta con la `client_ref` enviada al validar. Distingue mayúsculas, acentos y espacios internos. Un valor vacío o que no cumple las reglas de `client_ref` devuelve un estado HTTP `422` (con `invalid_filter` en el cuerpo).
          x-translations:
            en:
              description: Exact match with the `client_ref` sent when validating. It distinguishes case, accents, and inner spaces. An empty value, or one that does not meet the `client_ref` rules, returns an HTTP status `422` (with `invalid_filter` in the body).
        - name: playground
          in: query
          schema:
            type: string
            enum:
              - '1'
          example: '1'
          description: Filtro — Muestra solo las validaciones del banco de pruebas (Playground). Si se envía, el único valor admitido es `1`; cualquier otro devuelve un estado HTTP `422`.
          x-translations:
            en:
              description: Keeps only playground validations. The only accepted value is `1`; any other value returns HTTP status `422`.
        - name: with_deleted
          in: query
          schema:
            type: string
            enum:
              - '0'
              - '1'
          example: '0'
          description: 'Filtro — Alcance de los registros retirados: omitido incluye activos y retirados; `0` deja solo activos y `1` solo retirados.'
          x-translations:
            en:
              description: 'Scope for withdrawn records: omitted includes active and withdrawn records; `0` keeps active records only and `1`, withdrawn records only.'
        - name: batch_id
          in: query
          schema:
            type: integer
            minimum: 1
          example: 42
          description: Filtro — Identificador del trabajo de importación que generó las validaciones.
          x-translations:
            en:
              description: Identifier of the import job that generated the validations.
        - name: bank
          in: query
          schema:
            type: string
            pattern: ^\d{1,5}$
          example: '012'
          description: Filtro — Código SPEI del banco emisor o receptor. (1-5 dígitos. Más o menos dígitos provoca que el campo sea ignorado).
          x-translations:
            en:
              description: SPEI code of the sending or receiving bank in the original request. Values outside 1 to 5 digits are ignored.
        - name: amount_min
          in: query
          schema:
            type: number
            format: float
          example: 1000.5
          description: Filtro — Importe mínimo inclusivo de las validaciones contadas.
          x-translations:
            en:
              description: Inclusive minimum amount for the counted validations.
        - name: amount_max
          in: query
          schema:
            type: number
            format: float
          example: 50000
          description: Filtro — Importe máximo inclusivo de las validaciones contadas.
          x-translations:
            en:
              description: Inclusive maximum amount for the counted validations.
        - name: status
          in: query
          schema:
            type: string
          example: valid
          description: Filtro — Se acepta para mantener paridad con `GET /v1/validations`, pero no modifica ningún contador.
          x-translations:
            en:
              description: Accepted for parity with `GET /v1/validations`, but it does not change any counter.
        - name: retry_state
          in: query
          schema:
            type: string
            enum:
              - pending
              - resolved
              - exhausted
              - cancelled
          example: pending
          description: 'Filtro — Estado del ciclo de reintentos: `pending`, `resolved`, `exhausted` o `cancelled`. Otro valor devuelve un estado HTTP `422` con `invalid_filter`.'
          x-translations:
            en:
              description: 'Retry cycle state: `pending`, `resolved`, `exhausted`, or `cancelled`. Any other value returns HTTP status `422` with `invalid_filter`.'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/validations/stats' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Estadísticas agregadas de las validaciones de la cuenta.
          x-translations:
            en:
              description: Aggregated validation statistics for the account.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ValidationStats'
              examples:
                with_activity:
                  summary: Estadísticas de usuario activo
                  x-translations:
                    en:
                      summary: Active user statistics
                  value:
                    data:
                      type: validation_stats
                      attributes:
                        total: 47
                        valid: 35
                        not_found: 5
                        cep_unavailable: 2
                        returned: 0
                        error: 1
                        pending: 1
                        deleted: 2
                        other: 3
                        by_type:
                          direct: 30
                          ocr: 17
                        by_status:
                          queued: 2
                          processing: 1
                          valid: 35
                          not_found: 5
                          cep_unavailable: 2
                          invalid: 1
                          returned: 0
                          failed: 0
                          error: 1
                    meta:
                      version: 1.50.1
                      request_id: f4a5b6c7d8e9
                new_user:
                  summary: Estadísticas de usuario nuevo — todos en cero
                  x-translations:
                    en:
                      summary: New user statistics — all zeros
                  value:
                    data:
                      type: validation_stats
                      attributes:
                        total: 0
                        valid: 0
                        not_found: 0
                        cep_unavailable: 0
                        returned: 0
                        error: 0
                        pending: 0
                        deleted: 0
                        other: 0
                        by_type:
                          direct: 0
                          ocr: 0
                        by_status:
                          queued: 0
                          processing: 0
                          valid: 0
                          not_found: 0
                          cep_unavailable: 0
                          invalid: 0
                          returned: 0
                          failed: 0
                          error: 0
                    meta:
                      version: 1.50.1
                      request_id: a5b6c7d8e9f0
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Un filtro trae un valor fuera de su lista admitida. El código es `invalid_filter` y el `detail` nombra el parámetro.
          x-translations:
            en:
              description: A filter carries a value outside its allowed list. The code is `invalid_filter` and the `detail` names the parameter.
              examples:
                invalid_filter:
                  value:
                    errors:
                      - detail: The `retry_state` filter has an unsupported value.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalid_filter:
                  summary: Filtro fuera de la lista admitida
                  x-translations:
                    en:
                      summary: Filter outside the allowed list
                  value:
                    errors:
                      - status: '422'
                        code: invalid_filter
                        detail: El filtro `retry_state` trae un valor no admitido.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: c3d4e5f6a1b2
      x-translations:
        en:
          summary: Get validation statistics for the account
          description: |-
            Returns aggregate counters for the account's validations:

            - The top-level counters —`valid`, `not_found`, `cep_unavailable`, `returned`, `error` and `pending`— group by `banxico_status`.
            - `by_status` groups by lifecycle status.
            - `by_type` separates the `direct` and `ocr` modes.

            It accepts the same filters as `GET /v1/validations`, with one exception: `status` is admitted but does not change the counters, because each one already carries its own target.
      security:
        - ApiKeyAuth: []
  /validations/export:
    get:
      x-related:
        - DELETE /v1/validations/{id}
        - GET /v1/validations
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/cep
      tags:
        - Validations
      summary: Exportar validaciones
      description: |-
        Descarga en un archivo las validaciones que cumplen los filtros.

        {% concept slug="exports-and-formats" %}El formato se elige{% /concept %} con `format`: `csv` de forma predeterminada, o `xlsx`.

        La exportación admite hasta 100 000 filas, y `limit` permite reducir ese máximo.
      operationId: exportValidations
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - name: format
          in: query
          schema:
            type: string
            enum:
              - csv
              - xlsx
            default: csv
          example: csv
          description: 'Formato del archivo: `csv` o `xlsx`.'
          x-translations:
            en:
              description: 'File format: `csv` (default) or `xlsx`.'
        - name: status
          in: query
          schema:
            type: string
          example: valid
          description: |-

            Filtro — Estado del ciclo de vida. Acepta un valor o una lista separada por comas: `queued`, `processing`, `valid`, `not_found`, `cep_unavailable`, `invalid`, `returned`, `failed` o `error`. Los valores no admitidos se ignoran.
          x-translations:
            en:
              description: 'Lifecycle state. Accepts one value or a comma-separated list: `queued`, `processing`, `valid`, `not_found`, `cep_unavailable`, `invalid`, `returned`, `failed`, or `error`. Unsupported values are ignored.'
        - name: type
          in: query
          schema:
            type: string
            enum:
              - direct
              - ocr
          example: direct
          description: |-

            Filtro — Modalidad: `direct` para validación manual; `ocr` para validación a partir de una imagen.
          x-translations:
            en:
              description: 'Mode: `direct` for manual validation; `ocr` for image-based validation.'
        - name: from
          in: query
          schema:
            type: string
            format: date
          example: '2025-01-01'
          description: Filtro — Fecha inicial inclusiva, aplicada sobre la creación de la validación.
          x-translations:
            en:
              description: Inclusive start date, applied to the validation creation date.
        - name: to
          in: query
          schema:
            type: string
            format: date
          example: '2025-03-31'
          description: Filtro — Fecha final inclusiva, aplicada sobre la creación de la validación.
          x-translations:
            en:
              description: Inclusive end date, applied to the validation creation date.
        - name: search
          in: query
          schema:
            type: string
            maxLength: 100
          example: MXBA20250315001234
          description: |-

            Filtro — Texto buscado, sin distinguir mayúsculas y minúsculas, en los datos enviados, normalizados y devueltos por Banxico. `%` y `_` se tratan como caracteres literales.
          x-translations:
            en:
              description: Case-insensitive text search over the submitted, normalized, and Banxico response data. `%` and `_` are treated as literal characters.
        - name: client_ref
          in: query
          schema:
            type: string
            minLength: 1
            maxLength: 64
          example: orden-4812
          description: Coincidencia exacta con la `client_ref` enviada al validar. Distingue mayúsculas, acentos y espacios internos. Un valor vacío o que no cumple las reglas de `client_ref` devuelve un estado HTTP `422` (con `invalid_filter` en el cuerpo).
          x-translations:
            en:
              description: Exact match with the `client_ref` sent when validating. It distinguishes case, accents, and inner spaces. An empty value, or one that does not meet the `client_ref` rules, returns an HTTP status `422` (with `invalid_filter` in the body).
        - name: playground
          in: query
          schema:
            type: string
            enum:
              - '1'
          example: '1'
          description: Filtro — Muestra solo las validaciones del banco de pruebas (Playground). Si se envía, el único valor admitido es `1`; cualquier otro devuelve un estado HTTP `422`.
          x-translations:
            en:
              description: Keeps only playground validations. The only accepted value is `1`; any other value returns HTTP status `422`.
        - name: with_deleted
          in: query
          schema:
            type: string
            enum:
              - '0'
              - '1'
          example: '0'
          description: |-

            Filtro — Alcance de los registros retirados: omitido incluye activos y retirados; `0` deja solo activos y `1` solo retirados.
          x-translations:
            en:
              description: 'Scope for withdrawn records: omitted includes active and withdrawn records; `0` keeps active records only and `1`, withdrawn records only.'
        - name: batch_id
          in: query
          schema:
            type: integer
            minimum: 1
          example: 42
          description: Filtro — Identificador del trabajo de importación que generó las validaciones.
          x-translations:
            en:
              description: Identifier of the import job that generated the validations.
        - name: retry_state
          in: query
          schema:
            type: string
            enum:
              - pending
              - resolved
              - exhausted
              - cancelled
          example: pending
          description: |-

            Filtro — Estado del ciclo de reintentos: `pending`, `resolved`, `exhausted` o `cancelled`. Cualquier otro valor devuelve un estado HTTP `422` con `invalid_filter`.
          x-translations:
            en:
              description: 'Retry cycle state: `pending`, `resolved`, `exhausted`, or `cancelled`. Any other value returns HTTP status `422` with `invalid_filter`.'
        - name: bank
          in: query
          schema:
            type: string
            pattern: ^\d{1,5}$
          example: '012'
          description: Filtro — Código SPEI del banco emisor o receptor. (1-5 dígitos. Más o menos dígitos provoca que el campo sea ignorado).
          x-translations:
            en:
              description: SPEI code of the sending or receiving bank in the original request. Values outside 1 to 5 digits are ignored.
        - name: amount_min
          in: query
          schema:
            type: number
            format: float
          example: 1000.5
          description: Filtro — Importe mínimo inclusivo. Se aplica de forma independiente de `amount_max`.
          x-translations:
            en:
              description: Inclusive minimum amount. Applied independently of `amount_max`.
        - name: amount_max
          in: query
          schema:
            type: number
            format: float
          example: 50000
          description: Filtro — Importe máximo inclusivo. Se aplica de forma independiente de `amount_min`.
          x-translations:
            en:
              description: Inclusive maximum amount. Applied independently of `amount_min`.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100000
          example: 500
          description: Filtro — Máximo de filas solicitado. Solo reduce el límite general de `100000`; los valores fuera del intervalo conservan el límite general.
          x-translations:
            en:
              description: Requested row maximum. It only lowers the `100000`-row general limit; out-of-range values keep the general limit.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/validations/export' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Archivo CSV o XLSX según `format`. El nombre del archivo es `<slug>_validaciones_<YYYY-MM-DD>.<ext>`, donde `<slug>` identifica al despliegue.
          x-translations:
            en:
              description: CSV or XLSX file, based on `format`. The filename is `<slug>_validaciones_<YYYY-MM-DD>.<ext>`, where `<slug>` identifies the deployment.
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="<slug>_validaciones_2025-01-15.csv"
              description: Cabecera de descarga con el nombre canónico del archivo.
              x-translations:
                en:
                  description: Download header with the canonical filename.
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Un filtro trae un valor fuera de su lista admitida. El código es `invalid_filter` y el `detail` nombra el parámetro.
          x-translations:
            en:
              description: A filter carries a value outside its allowed list. The code is `invalid_filter` and the `detail` names the parameter.
              examples:
                invalid_filter:
                  value:
                    errors:
                      - detail: The `retry_state` filter has an unsupported value.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalid_filter:
                  summary: Filtro fuera de la lista admitida
                  x-translations:
                    en:
                      summary: Filter outside the allowed list
                  value:
                    errors:
                      - status: '422'
                        code: invalid_filter
                        detail: El filtro `retry_state` trae un valor no admitido.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: d4e5f6a1b2c3
      x-translations:
        en:
          summary: Export validations
          description: |-
            Downloads to a file the validations matching the filters.

            {% concept slug="exports-and-formats" %}The format is chosen{% /concept %} with `format`: `csv` by default, or `xlsx`.

            The export admits up to 100,000 rows, and `limit` lowers that maximum.
      security:
        - ApiKeyAuth: []
  /validations/{id}:
    get:
      tags:
        - Validations
      summary: Obtener una validación
      description: |-
        Devuelve el registro completo de una validación, con su veredicto, los datos normalizados y el estado de los reintentos.

        El recorrido desde la solicitud hasta el veredicto está descrito en {% concept slug="validation-flow" %}el flujo de validación{% /concept %}.

        {% callout type="info" %}
        **Validaciones en proceso:**\
        Para una validación, una respuesta con estado HTTP `200` contiene la cabecera `Retry-After` y el campo `meta.next_poll_after_seconds` en el cuerpo, que indican cuándo volver a consultar si el estado de la validación es `queued` (por procesar) o `processing` (procesando).\
        \
        Si el `ETag` enviado en `If-None-Match` sigue vigente, responde con un estado HTTP `304` sin cuerpo, como describe {% concept slug="conditional-caching" %}la caché condicional{% /concept %}.

        {% /callout %}

        {% callout type="info" %}
        **Validaciones purgadas:**\
        Una validación purgada con `POST /v1/validations/{id}/purge/execute` responde con un estado HTTP `200` y queda como una lápida: conserva el veredicto, las fechas y el monto, y trae `purged_at`. Ya no tiene `request_data`, `ocr_result`, `banxico_result` ni archivos.

        {% /callout %}
      operationId: getValidation
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - $ref: '#/components/parameters/ValidationId'
        - $ref: '#/components/parameters/IfNoneMatchHeader'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/validations/{id}' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Detalle de la validación. En estados pre-terminales (`queued`, `processing`) incluye la cabecera `Retry-After` y el campo `meta.next_poll_after_seconds` del cuerpo.
          x-translations:
            en:
              description: Full validation details. In pre-terminal states (`queued`, `processing`) includes `Retry-After` and `meta.next_poll_after_seconds`.
          headers:
            ETag:
              schema:
                type: string
                example: W/"3-processing"
              description: ETag débil que cambia en cada transición de estado. Sigue el formato `W/"{etag_version}-{status}"` y puede incluirse en `If-None-Match` para recibir un estado HTTP `304` si nada ha cambiado.
              x-translations:
                en:
                  description: Weak ETag that changes on each state transition. It follows the `W/"{etag_version}-{status}"` format and can be included in `If-None-Match` to receive HTTP status `304` when nothing has changed.
            Retry-After:
              schema:
                type: integer
              description: Segundos de espera antes de la siguiente petición. Solo está presente en `queued` y `processing`.
              x-translations:
                en:
                  description: Seconds to wait before the next poll. Only present in `queued` and `processing` states.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/Validation'
              examples:
                valid_with_cep:
                  summary: Validación confirmada con certificado CEP disponible
                  value:
                    data:
                      id: f47ac10b-58cc-4372-a567-0e02b2c3d479
                      type: validation
                      attributes:
                        validation_type: direct
                        is_playground: false
                        status: valid
                        banxico_status: valid
                        processing_time_ms: 1320
                        created_at: '2025-03-15T14:22:10Z'
                        completed_at: '2025-03-15T14:22:11Z'
                        request_data:
                          fecha: '2025-03-15'
                          monto: 15000.5
                          clave_rastreo: MXBA20250315001234
                          cuenta_beneficiaria: '012180004412345678'
                        retry_state:
                          enabled: false
                          max_retries: null
                          interval_seconds: null
                          outcomes: null
                          attempts_completed: 0
                          next_attempt_at: null
                          resolved_at: null
                          exhausted_at: null
                          cancelled_at: null
                          terminal_state: null
                      links:
                        self: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479
                        cep_xml: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479/cep?format=xml
                        cep_pdf: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479/cep?format=pdf
                    meta:
                      version: 1.51.0
                      request_id: b3c4d5e6f7a8
                queued_async:
                  summary: Validación asíncrona en cola — consulta periódica activa
                  x-translations:
                    en:
                      summary: Async validation queued — polling active
                  value:
                    data:
                      id: c3d4e5f6-a7b8-9012-cdef-123456789abc
                      type: validation
                      attributes:
                        validation_type: direct
                        is_playground: false
                        status: queued
                        enqueued_at: '2025-03-17T08:00:01Z'
                        expires_at: '2025-03-17T09:00:01Z'
                        etag_version: 0
                    meta:
                      version: 1.51.0
                      request_id: a9b0c1d2e3f4
                      next_poll_after_seconds: 2
                not_found:
                  summary: Transferencia no encontrada en el CEP
                  value:
                    data:
                      id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                      type: validation
                      attributes:
                        validation_type: direct
                        is_playground: false
                        status: not_found
                        banxico_status: not_found
                        created_at: '2025-03-16T09:10:00Z'
                        completed_at: '2025-03-16T09:10:01Z'
                    meta:
                      version: 1.51.0
                      request_id: c4d5e6f7a8b9
        '304':
          $ref: '#/components/responses/ValidationNotModified'
          headers:
            ETag:
              schema:
                type: string
                example: W/"3-processing"
              description: '`ETag` actual; idéntico al `If-None-Match` enviado.'
              x-translations:
                en:
                  description: Current `ETag`; matches the `If-None-Match` sent.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Validación no encontrada o ajena a la cuenta de usuario (`not_found`).
          x-translations:
            en:
              description: Validation not found or not owned by the account (`not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: UUID inválido en la ruta (`invalid_uuid`).
          x-translations:
            en:
              description: Invalid UUID in path (`invalid_uuid`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-related:
        - GET /v1/validations/{id}/cep
        - GET /v1/validations/{id}/image
        - PUT /v1/validations/{id}/retry-policy
        - GET /v1/validations/{id}/retry-attempts
        - POST /v1/validations/{id}/recheck
        - GET /v1/validations
      x-translations:
        en:
          summary: Get a single validation
          description: |-
            Returns the complete record of a validation, with its verdict, the normalized data and the state of its retries.

            The path from request to verdict is described in {% concept slug="validation-flow" %}the validation flow{% /concept %}.

            {% callout type="info" %}
            **While the validation has not finished:**\
            In a response with HTTP status `200` for a validation in `queued` or `processing`, `Retry-After` and `meta.next_poll_after_seconds` say when to poll again.\
            If the `ETag` sent in `If-None-Match` is still current, it responds with an HTTP status `304` and no body, as {% concept slug="conditional-caching" %}conditional caching{% /concept %} describes.

            {% /callout %}

            {% callout type="info" %}
            **Purged validations:**\
            A validation purged with `POST /v1/validations/{id}/purge/execute` responds with an HTTP status `200` and is left as a tombstone: it keeps the verdict, the dates, and the amount, and carries `purged_at`. It no longer has `request_data`, `ocr_result`, `banxico_result`, or files.

            {% /callout %}
      security:
        - ApiKeyAuth: []
    delete:
      x-related:
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/cep
        - GET /v1/validations/{id}/image
        - GET /v1/validations/{id}/retry-attempts
        - POST /v1/validations/{id}/cancel-retries
        - POST /v1/validations/{id}/cep/send-telegram
      tags:
        - Validations
      summary: Retirar una validación del historial
      description: |-
        Retira una validación del historial de validaciones del usuario autenticado.

        La eliminación es lógica, no física. Lo que quiere decir que puede seguir apareciendo en las listas como `GET /v1/validations` si se usa el filtro `with_deleted` u otros.

        El registro, el CEP, los datos extraídos y el archivo del comprobante se conservan. Para borrarlos de forma definitiva está `POST /v1/validations/{id}/purge/prepare`, seguido de `POST /v1/validations/{id}/purge/execute`.
      operationId: deleteValidation
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - $ref: '#/components/parameters/ValidationId'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X DELETE 'https://api.veriko.mx/v1/validations/{id}' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '204':
          description: Validación retirada. Sin cuerpo.
          x-translations:
            en:
              description: Validation withdrawn. Empty body.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Validación no encontrada o ajena a la cuenta de usuario.
          x-translations:
            en:
              description: Validation not found or not owned by the account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: UUID inválido en la ruta (`invalid_uuid`).
          x-translations:
            en:
              description: Invalid UUID in path (`invalid_uuid`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Withdraw a validation
          description: |-
            Withdraws a validation from the history through a soft delete.

            The record keeps its result and can reappear in `GET /v1/validations` with the `with_deleted` filter. Only the owning account can withdraw it.

            The record, the CEP, the extracted data, and the receipt file are kept. To delete them permanently, there is `POST /v1/validations/{id}/purge/prepare`, followed by `POST /v1/validations/{id}/purge/execute`.
      security:
        - ApiKeyAuth: []
  /validations/{id}/cep:
    get:
      x-related:
        - POST /v1/validations/{id}/cep/send-telegram
        - DELETE /v1/validations/{id}
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/image
        - GET /v1/validations/{id}/retry-attempts
        - POST /v1/validations/{id}/cancel-retries
      tags:
        - Validations
      summary: Descargar el CEP oficial
      description: |-
        Descarga el {% concept slug="cep-concept" %}Comprobante Electrónico de Pago (CEP){% /concept %} emitido por Banxico de una validación con veredicto `valid`.

        El formato predeterminado es `xml`; con `format=pdf` se solicita el documento en PDF.

        Si el comprobante, o el formato pedido, no está disponible, responde con un estado HTTP `404` (con `cep_not_available` en el cuerpo).
      operationId: downloadCep
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - $ref: '#/components/parameters/ValidationId'
        - name: format
          in: query
          schema:
            type: string
            enum:
              - xml
              - pdf
            default: xml
          example: xml
          description: 'Formato del certificado: `xml` o `pdf`.'
          x-translations:
            en:
              description: 'Certificate format: `xml` (default) or `pdf`.'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/validations/{id}/cep' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Archivo del certificado CEP. El `Content-Type` es `application/xml` o `application/pdf` según el formato solicitado.
          x-translations:
            en:
              description: CEP certificate file. `Content-Type` is `application/xml` or `application/pdf` based on the requested format.
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="CEP-f47ac10b-58cc-4372-a567-0e02b2c3d479.xml"
              description: Nombre de descarga con el formato `CEP-{validation_id}.{ext}`.
              x-translations:
                en:
                  description: Download name in the `CEP-{validation_id}.{ext}` format.
          content:
            application/xml:
              schema:
                type: string
                format: binary
            application/pdf:
              schema:
                type: string
                format: binary
        '400':
          description: Formato fuera de la lista admitida (`invalid_export_format`).
          x-translations:
            en:
              description: Format outside the allowlist (`invalid_export_format`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Validación no encontrada (`not_found`) o el CEP no está disponible para esta validación (`cep_not_available`). Este último código se devuelve cuando `banxico_status` no es `valid` o falta el archivo solicitado.
          x-translations:
            en:
              description: Validation not found (`not_found`) or the CEP is not available for this validation (`cep_not_available`). The latter code is returned when `banxico_status` is not `valid` or the requested file is missing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          $ref: '#/components/responses/ValidationPurged'
        '422':
          description: UUID inválido en la ruta (`invalid_uuid`).
          x-translations:
            en:
              description: Invalid UUID in path (`invalid_uuid`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Download the official CEP
          description: |-
            Downloads {% concept slug="cep-concept" %}the Electronic Payment Receipt (CEP) issued by Banxico{% /concept %} for a validation with a `valid` verdict.

            The default format is `xml`; `format=pdf` requests the document as a PDF.

            If the receipt, or the requested format, is not available, it responds with an HTTP status `404` (with `cep_not_available` in the body).
      security:
        - ApiKeyAuth: []
  /validations/{id}/cep/send-telegram:
    post:
      x-related:
        - GET /v1/validations/{id}/cep
        - DELETE /v1/validations/{id}
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/image
        - GET /v1/validations/{id}/retry-attempts
        - POST /v1/validations/{id}/cancel-retries
      tags:
        - Validations
      summary: Enviar el comprobante al chat de Telegram vinculado
      description: |-
        Inicia el envío del CEP en PDF al chat de Telegram vinculado con la cuenta de usuario propietaria.

        Un estado HTTP `202` (con `queued: true` en el cuerpo) confirma que la entrega quedó pendiente, no que se completó.

        Requiere un comprobante disponible y una cuenta de Telegram vinculada:

        - Sin el documento, responde con un estado HTTP `409` (con `cep_not_available` en el cuerpo).
        - Sin la vinculación, responde con un estado HTTP `409` (con `telegram_not_linked` en el cuerpo).
      operationId: sendCepToTelegram
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - $ref: '#/components/parameters/ValidationId'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/validations/{id}/cep/send-telegram' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '202':
          description: La entrega del CEP fue aceptada y quedó pendiente de envío al chat de Telegram vinculado.
          x-translations:
            en:
              description: CEP delivery was accepted and is pending delivery to the linked Telegram chat.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        allOf:
                          - $ref: '#/components/schemas/JsonApiResourceBase'
                          - type: object
                            properties:
                              type:
                                type: string
                                example: validation_cep_telegram
                              id:
                                type: string
                                format: uuid
                                example: f47ac10b-58cc-4372-a567-0e02b2c3d479
                              attributes:
                                type: object
                                properties:
                                  queued:
                                    type: boolean
                                    example: true
              examples:
                queued:
                  summary: Entrega del CEP encolada
                  value:
                    data:
                      type: validation_cep_telegram
                      id: f47ac10b-58cc-4372-a567-0e02b2c3d479
                      attributes:
                        queued: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Validación no encontrada o ajena a la cuenta de usuario (`not_found`).
          x-translations:
            en:
              description: Validation not found or not owned by the account (`not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: El CEP no está disponible (`cep_not_available`) o la cuenta de usuario no tiene un chat de Telegram vinculado (`telegram_not_linked`).
          x-translations:
            en:
              description: The CEP is unavailable (`cep_not_available`) or the account has no linked Telegram chat (`telegram_not_linked`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          $ref: '#/components/responses/ValidationPurged'
        '422':
          description: UUID inválido en la ruta (`invalid_uuid`).
          x-translations:
            en:
              description: Invalid UUID in path (`invalid_uuid`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: No se pudo iniciar la entrega (`dispatch_failed`).
          x-translations:
            en:
              description: Delivery could not be started (`dispatch_failed`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Send the CEP to Telegram
          description: |-
            Starts delivery of the CEP as a PDF to the Telegram chat linked to the owning account.

            An HTTP status `202` (with `queued: true` in the body) confirms that delivery is pending, not that it completed.

            It requires an available receipt and a linked Telegram account:

            - Without the document, it responds with an HTTP status `409` (with `cep_not_available` in the body).
            - Without the link, it responds with an HTTP status `409` (with `telegram_not_linked` in the body).
      security:
        - ApiKeyAuth: []
  /validations/{id}/image:
    get:
      x-related:
        - DELETE /v1/validations/{id}
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/cep
        - GET /v1/validations/{id}/retry-attempts
        - POST /v1/validations/{id}/cancel-retries
        - POST /v1/validations/{id}/cep/send-telegram
      tags:
        - Validations
      summary: Obtener la imagen del comprobante
      description: |-
        Devuelve el comprobante conservado de una validación por OCR, con caché privada:

        - Imagen: se entrega con disposición `inline`.
        - PDF: se entrega siempre como adjunto (`attachment`) y con `Content-Security-Policy: sandbox`,
          para que un navegador no lo abra dentro del propio origen.

        Si la validación no tiene imagen disponible, responde con un estado HTTP `404` (con `image_not_available` en el cuerpo).

        Si la validación se creó con `retain_image=false`, responde con un estado HTTP `410` (con `image_not_retained` en el cuerpo); si se purgó, con un estado HTTP `410` (con `validation_purged` en el cuerpo). Un `410` no se resuelve reintentando: el archivo no existe.
      operationId: getValidationImage
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - $ref: '#/components/parameters/ValidationId'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/validations/{id}/image' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Comprobante. `Content-Type` indica `image/png`, `image/jpeg`, `image/webp` o `application/pdf` según el formato; cualquier otro contenido se entrega como `application/octet-stream`.
          x-translations:
            en:
              description: Receipt. `Content-Type` is `image/png`, `image/jpeg`, `image/webp`, or `application/pdf`; other content is returned as `application/octet-stream`.
          headers:
            Content-Disposition:
              schema:
                type: string
                example: inline; filename="comprobante-c3d4e5f6-a7b8-9012-cdef-123456789012.png"
              description: '`inline` para una imagen y `attachment` para un PDF, con un nombre derivado del identificador de la validación y la extensión del formato.'
              x-translations:
                en:
                  description: '`inline` for an image and `attachment` for a PDF, with a filename derived from the validation identifier and the format extension.'
            Cache-Control:
              schema:
                type: string
                example: private, max-age=3600
              description: Permite un caché privado durante 1 hora.
              x-translations:
                en:
                  description: Privately cacheable for 1 hour.
            Cross-Origin-Resource-Policy:
              schema:
                type: string
                example: same-origin
              description: Restringe el uso del recurso al mismo origen.
              x-translations:
                en:
                  description: Restricts use of the resource to the same origin.
            Content-Security-Policy:
              schema:
                type: string
                example: sandbox
              description: Sólo con un PDF. `sandbox` impide ejecutar scripts y trata el documento como de otro origen si un visor lo abre.
              x-translations:
                en:
                  description: Only with a PDF. `sandbox` blocks scripts and treats the document as a different origin if a viewer opens it.
            X-Content-Type-Options:
              schema:
                type: string
                example: nosniff
              description: Impide que el navegador deduzca un tipo de contenido distinto.
              x-translations:
                en:
                  description: Prevents the browser from inferring a different content type.
          content:
            image/png:
              schema:
                type: string
                format: binary
            image/jpeg:
              schema:
                type: string
                format: binary
            image/webp:
              schema:
                type: string
                format: binary
            application/pdf:
              schema:
                type: string
                format: binary
            application/octet-stream:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Validación no encontrada (`not_found`) o sin imagen disponible (`image_not_available`).
          x-translations:
            en:
              description: Validation not found (`not_found`) or no image is available (`image_not_available`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          description: 'La imagen no existe por decisión del cliente: la validación se creó con `retain_image=false` (`image_not_retained`) o se purgó (`validation_purged`).'
          x-translations:
            en:
              description: 'The image does not exist by the client''s decision: the validation was created with `retain_image=false` (`image_not_retained`) or was purged (`validation_purged`).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: UUID inválido en la ruta (`invalid_uuid`).
          x-translations:
            en:
              description: Invalid UUID in path (`invalid_uuid`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Get the receipt image
          description: |-
            Returns the receipt kept for an OCR validation, with private caching:

            - Image: delivered with `inline` disposition.
            - PDF: always delivered as an attachment (`attachment`) and with `Content-Security-Policy: sandbox`,
              so a browser does not open it within the same origin.

            If the validation has no image available, it responds with an HTTP status `404` (with `image_not_available` in the body).

            If the validation was created with `retain_image=false`, it responds with an HTTP status `410` (with `image_not_retained` in the body); if it was purged, with an HTTP status `410` (with `validation_purged` in the body). A `410` is not resolved by retrying: the file does not exist.
      security:
        - ApiKeyAuth: []
  /validations/{id}/retry-attempts:
    get:
      x-related:
        - DELETE /v1/validations/{id}
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/cep
        - GET /v1/validations/{id}/image
        - POST /v1/validations/{id}/cancel-retries
        - POST /v1/validations/{id}/cep/send-telegram
      tags:
        - Validations
      summary: Listar intentos de reintento
      description: |-
        Devuelve el historial cronológico de los reintentos automáticos de una validación.

        Cada entrada identifica el intento, sus tiempos, los estados anterior y posterior, y el error cuando lo hubo.

        Los criterios que originan estos intentos están descritos en {% concept slug="retry-policy" %}la política de reintentos{% /concept %}.
      operationId: listValidationRetryAttempts
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - $ref: '#/components/parameters/ValidationId'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/validations/{id}/retry-attempts' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Lista de intentos de reintento ordenada cronológicamente por `attempt_number` ascendente.
          x-translations:
            en:
              description: List of retry attempts in chronological order by ascending `attempt_number`.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/RetryAttempt'
              examples:
                with_attempts:
                  summary: Historial con 2 intentos — segundo resuelve la validación
                  x-translations:
                    en:
                      summary: History with 2 attempts — second one resolves the validation
                  value:
                    data:
                      - attempt_number: 1
                        dispatched_at: '2025-03-17T08:10:00Z'
                        started_at: '2025-03-17T08:10:02Z'
                        finished_at: '2025-03-17T08:10:03Z'
                        prev_banxico_status: not_found
                        new_banxico_status: not_found
                        prev_status: not_found
                        new_status: not_found
                        processing_time_ms: 1100
                        error_code: null
                        proxy_pool_member: proxy-01
                      - attempt_number: 2
                        dispatched_at: '2025-03-17T08:20:00Z'
                        started_at: '2025-03-17T08:20:01Z'
                        finished_at: '2025-03-17T08:20:02Z'
                        prev_banxico_status: not_found
                        new_banxico_status: valid
                        prev_status: not_found
                        new_status: valid
                        processing_time_ms: 1250
                        error_code: null
                        proxy_pool_member: proxy-03
                    meta:
                      version: 1.51.0
                      request_id: b6c7d8e9f0a1
                no_attempts:
                  summary: Sin intentos — política recién configurada
                  x-translations:
                    en:
                      summary: No attempts — policy just configured
                  value:
                    data: []
                    meta:
                      version: 1.51.0
                      request_id: c7d8e9f0a1b2
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Validación no encontrada (`not_found`).
          x-translations:
            en:
              description: Validation not found (`not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          $ref: '#/components/responses/ValidationPurged'
        '422':
          description: UUID inválido en la ruta (`invalid_uuid`).
          x-translations:
            en:
              description: Invalid UUID in path (`invalid_uuid`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: List retry attempts
          description: |-
            Returns the chronological history of a validation's automatic retries.

            Each entry identifies the attempt, its timestamps, the statuses before and after, and the error when there was one.

            The criteria that trigger these attempts are described in {% concept slug="retry-policy" %}the retry policy{% /concept %}.
      security:
        - ApiKeyAuth: []
  /validations/{id}/retry-policy:
    put:
      x-related:
        - DELETE /v1/validations/{id}
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/cep
        - GET /v1/validations/{id}/image
        - GET /v1/validations/{id}/retry-attempts
        - POST /v1/validations/{id}/cancel-retries
      tags:
        - Validations
      summary: Configurar política de reintentos
      description: |-
        Activa una política de reintentos automáticos para una validación con veredicto `not_found`, `cep_unavailable` o `error`.

        Este tipo de veredictos pueden aparecer cuando la operación aún NO es procesada por Banxico, de tal modo que el sistema puede re-validar la transferencia en automático tras un periodo de tiempo establecido.

        {% callout type="warning" %}
        **No se admite en tres casos:**\
        — Validaciones que llegaron por importación masiva.\
        — Validaciones ya resueltas.\
        — Validaciones fuera de la antigüedad que permite el plan.

        {% /callout %}

        Un ciclo de reintentos puede devolver `terminal_state="exhausted"` cuando se alcanzan el máximo de reintentos configurado. También puede devolver `terminal_state="cancelled"` cuando el usuario cancela manualmente los reintentos de la validación.

        En ambos casos, el ciclo puede reactivarse mientras conserve reintentos disponibles.

        El estado completo de una validación se obtiene con `GET /v1/validations/{id}`, y los límites están descritos en {% concept slug="retry-policy" %}la política de reintentos{% /concept %}.
      operationId: updateValidationRetryPolicy
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - $ref: '#/components/parameters/ValidationId'
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateValidationRetryPolicyRequest'
            examples:
              activate_basic:
                summary: Activar reintentos — 3 intentos cada 10 min
                x-translations:
                  en:
                    summary: Activate retries — 3 attempts every 10 min
                value:
                  retry_policy:
                    enabled: true
                    max_retries: 3
                    interval_seconds: 600
                    outcomes:
                      - not_found
                      - cep_unavailable
              activate_cep_only:
                summary: Reintentos solo para cep_unavailable — 5 intentos cada 5 min
                x-translations:
                  en:
                    summary: Retries for cep_unavailable only — 5 attempts every 5 min
                value:
                  retry_policy:
                    enabled: true
                    max_retries: 5
                    interval_seconds: 300
                    outcomes:
                      - cep_unavailable
              activate_for_error:
                summary: Reintentos amplios — incluye fallos transitorios Banxico
                x-translations:
                  en:
                    summary: Broad retries — includes transient Banxico failures
                value:
                  retry_policy:
                    enabled: true
                    max_retries: 3
                    interval_seconds: 600
                    outcomes:
                      - not_found
                      - cep_unavailable
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X PUT 'https://api.veriko.mx/v1/validations/{id}/retry-policy' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json' \
              -d '{
                "retry_policy": {
                  "enabled": true,
                  "max_retries": 3,
                  "interval_seconds": 600,
                  "outcomes": [
                    "not_found",
                    "cep_unavailable"
                  ]
                }
              }'
      responses:
        '200':
          description: Política de reintentos activada. Devuelve el estado actual completo del ciclo.
          x-translations:
            en:
              description: Retry policy activated. Returns the full current state of the retry cycle.
          headers:
            Idempotent-Replayed:
              schema:
                type: string
                enum:
                  - 'true'
                  - 'false'
              description: Solo está presente cuando la petición incluyó `Idempotency-Key`. `true` indica que la respuesta se reutilizó desde el caché de idempotencia, cuya vigencia es de 24 horas por cuenta, endpoint y clave.
              x-translations:
                en:
                  description: Only present when the request included `Idempotency-Key`. `true` marks a response reused from the idempotency cache, which remains valid for 24 hours per account, endpoint, and key.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Recurso JSON:API con el estado actualizado de la política.
                        x-translations:
                          en:
                            description: JSON:API resource with the updated policy state.
                        allOf:
                          - $ref: '#/components/schemas/JsonApiResourceBase'
                          - type: object
                            properties:
                              type:
                                type: string
                                example: validation
                              id:
                                type: string
                                format: uuid
                              attributes:
                                $ref: '#/components/schemas/UpdateValidationRetryPolicyAttributes'
        '400':
          description: El cuerpo de la petición está vacío o no es JSON válido.
          x-translations:
            en:
              description: The request body is empty or is not valid JSON.
              examples:
                body_empty:
                  value:
                    errors:
                      - detail: The request body is empty.
                invalid_json:
                  value:
                    errors:
                      - detail: The body is not valid JSON.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                body_empty:
                  summary: Cuerpo vacío
                  x-translations:
                    en:
                      summary: Empty body
                  value:
                    errors:
                      - status: '400'
                        code: body_empty
                        detail: El cuerpo de la petición está vacío.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: c3d4e5f6a1b3
                invalid_json:
                  summary: JSON mal formado
                  x-translations:
                    en:
                      summary: Malformed JSON
                  value:
                    errors:
                      - status: '400'
                        code: invalid_json
                        detail: El cuerpo no es JSON válido.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: d4e5f6a1b2c4
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Validación no encontrada o ajena a la cuenta de usuario (`not_found`).
          x-translations:
            en:
              description: Validation not found or not owned by the account (`not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          $ref: '#/components/responses/ValidationPurged'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: Política inválida o precondición no cumplida. `retry_policy_invalid`, la forma, el rango o el resultado no se admiten; `retry_not_supported_for_bulk`, la validación nació de una importación; `retry_not_applicable`, su estado queda fuera de los tres que admiten reintento (`not_found`, `cep_unavailable`, `error`) o cambió mientras tanto; `retry_already_resolved`, el ciclo ya cerró; `retry_age_exceeded`, la validación es más vieja que `max_age_seconds`; `retry_pending_cap_exceeded`, se alcanzó el tope de pendientes de la cuenta de usuario o el de despachados en 24 horas; `reactivation_cap_exceeded`, se alcanzó el tope de reactivaciones de esa validación.
          x-translations:
            en:
              description: Invalid policy or precondition not met. `retry_policy_invalid`, the shape, range, or outcome is not allowed; `retry_not_supported_for_bulk`, the validation came from an import; `retry_not_applicable`, its status is outside the three that admit a retry (`not_found`, `cep_unavailable`, `error`) or it changed meanwhile; `retry_already_resolved`, the cycle already closed; `retry_age_exceeded`, the validation is older than `max_age_seconds`; `retry_pending_cap_exceeded`, the account hit its pending cap or its 24-hour dispatched cap; `reactivation_cap_exceeded`, that validation hit its reactivation cap.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Configure retry policy
          description: |-
            Activates an automatic retry policy for a validation with a `not_found`, `cep_unavailable` or `error` verdict.

            Verdicts of that kind can appear while the operation has NOT yet been processed by Banxico, so the system can re-validate the transfer automatically after a set period.

            {% callout type="warning" %}
            **Not admitted in three cases:**\
            — Validations that arrived through a bulk import.\
            — Validations already resolved.\
            — Validations older than the age the plan allows.

            {% /callout %}

            A retry cycle can return `terminal_state="exhausted"` when the configured maximum number of retries is reached. It can also return `terminal_state="cancelled"` when the user cancels the validation's retries manually.

            In both cases the cycle can be reactivated while it keeps retries available.

            The full state of a validation is obtained with `GET /v1/validations/{id}`, and the caps are described in {% concept slug="retry-policy" %}the retry policy{% /concept %}.
      security:
        - ApiKeyAuth: []
  /validations/{id}/cancel-retries:
    post:
      x-related:
        - DELETE /v1/validations/{id}
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/cep
        - GET /v1/validations/{id}/image
        - GET /v1/validations/{id}/retry-attempts
        - POST /v1/validations/{id}/cep/send-telegram
      tags:
        - Validations
      summary: Cancelar reintentos pendientes
      description: |-
        Cancela el ciclo activo de **reintentos automáticos** de una validación y devuelve `terminal_state="cancelled"`.

        Si el ciclo ya no está activo, responde con un estado HTTP `422` (con `retry_not_active` en el cuerpo).

        La cabecera opcional `Idempotency-Key` permite repetir la misma petición sin duplicar la cancelación.
      operationId: cancelValidationRetries
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - $ref: '#/components/parameters/ValidationId'
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/validations/{id}/cancel-retries' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '200':
          description: Ciclo de reintentos cancelado. `retry_state.terminal_state` queda `cancelled`.
          x-translations:
            en:
              description: Retry cycle cancelled. `retry_state.terminal_state` is now `cancelled`.
          headers:
            Idempotent-Replayed:
              schema:
                type: string
                enum:
                  - 'true'
                  - 'false'
              description: Solo está presente cuando la petición incluyó `Idempotency-Key`. `true` indica que la respuesta se reutilizó desde el caché de idempotencia, cuya vigencia es de 24 horas por cuenta, endpoint y clave.
              x-translations:
                en:
                  description: Only present when the request included `Idempotency-Key`. `true` marks a response reused from the idempotency cache, which remains valid for 24 hours per account, endpoint, and key.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Recurso JSON:API con el estado actualizado del ciclo.
                        x-translations:
                          en:
                            description: JSON:API resource with the updated cycle state.
                        allOf:
                          - $ref: '#/components/schemas/JsonApiResourceBase'
                          - type: object
                            properties:
                              type:
                                type: string
                                example: validation
                              id:
                                type: string
                                format: uuid
                              attributes:
                                $ref: '#/components/schemas/CancelValidationRetriesAttributes'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Validación no encontrada o ajena a la cuenta de usuario (`not_found`).
          x-translations:
            en:
              description: Validation not found or not owned by the account (`not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          $ref: '#/components/responses/ValidationPurged'
        '422':
          description: No hay ciclo de reintentos activo que cancelar (código `retry_not_active`). UUID inválido (`invalid_uuid`).
          x-translations:
            en:
              description: No active retry cycle to cancel (code `retry_not_active`). Invalid UUID (`invalid_uuid`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Cancel pending retries
          description: |-
            Cancels the active **automatic retry** cycle of a validation and returns `terminal_state="cancelled"`.

            If the cycle is no longer active, it responds with an HTTP status `422` (with `retry_not_active` in the body).

            The optional `Idempotency-Key` header allows repeating the same request without duplicating the cancellation.
      security:
        - ApiKeyAuth: []
  /validations/{id}/recheck:
    post:
      x-related:
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/cep
        - GET /v1/validations/{id}/retry-attempts
        - POST /v1/webhooks
        - GET /v1/webhooks/deliveries
      tags:
        - Validations
      summary: Volver a consultar el estado de pago de una validación
      description: |-
        Vuelve a consultar a Banxico el estado del pago de una validación `valid` y, si lo reporta devuelto, la pasa a `returned`.

        Un veredicto `valid` es la foto del momento de la consulta: el CEP acredita que la transferencia se liquidó, no que el dinero siguiera en la cuenta del beneficiario, y la institución receptora puede devolverlo horas o días después. Esta operación hace sólo esa consulta, con los datos que la validación ya guardó:

        - No usa IA ni descarga otro CEP.
        - **No consume cuota** ni cuenta como una validación nueva.
        - Guarda la respuesta de Banxico en `banxico_result._payment_status`.
        - Con el estado `devuelto` o `en_proceso_devolucion`, cambia `status` y `banxico_status` a `returned`, conserva el CEP y emite el webhook `validation.returned`.

        Qué admite:

        - `valid` creada hace 72 horas como máximo: se consulta. Pasado ese plazo responde con un estado HTTP `422` (con `recheck_window_expired` en el cuerpo).
        - `returned`: responde su estado actual sin consultar a Banxico, con `meta.recheck.checked_at` en `null`.
        - Cualquier otro estado: responde con un estado HTTP `422` (con `recheck_not_eligible` en el cuerpo).

        Cada validación admite una consulta cada 10 minutos. Si se repite antes, responde con un estado HTTP `429` (con `recheck_rate_limited` en el cuerpo) y la cabecera `Retry-After` indica los segundos que faltan. Ese tope se suma a los de la clave de API.

        La respuesta es la misma validación que devuelve `GET /v1/validations/{id}`, con el resultado de la revisión en `meta.recheck`:

        - `checked_at`: Fecha y hora de la consulta a Banxico, en ISO 8601 UTC. `null` cuando no se consultó nada porque la validación ya estaba `returned`.
        - `changed`: `true` cuando esta consulta pasó la validación de `valid` a `returned`.
        - `previous_status`: Veredicto de Banxico antes de la consulta, `valid` o `returned`.

        {% callout type="info" %}
        **Si Banxico no responde:**\
        Cuando Banxico no entrega un estado legible, responde con un estado HTTP `503` (con `recheck_unavailable` en el cuerpo) y la validación conserva su intervalo: se puede reintentar de inmediato. Un `503` no dice nada sobre la transferencia.

        {% /callout %}

        El significado de `valid` y de `returned` está en {% concept slug="cep-concept" %}el CEP y sus veredictos{% /concept %}, y la entrega del evento en {% concept slug="webhooks-architecture" %}la arquitectura de webhooks{% /concept %}.
      operationId: recheckValidation
      externalDocs:
        url: https://docs.veriko.mx/how-to/inspect-validations
        description: Browse and manage validation records
      parameters:
        - $ref: '#/components/parameters/ValidationId'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/validations/{id}/recheck' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '200':
          description: Estado de pago consultado. Devuelve la validación y, en `meta.recheck`, cuándo se consultó, si cambió a `returned` y qué veredicto tenía antes.
          x-translations:
            en:
              description: Payment status queried. Returns the validation and, in `meta.recheck`, when it was queried, whether it changed to `returned`, and what verdict it had before.
          headers:
            ETag:
              schema:
                type: string
                example: W/"4-valid"
              description: ETag débil de la validación después de la consulta. Sigue el formato `W/"{etag_version}-{status}"` y sube en cada consulta, aunque el veredicto no cambie.
              x-translations:
                en:
                  description: Weak ETag of the validation after the query. It follows the `W/"{etag_version}-{status}"` format and rises on every query, even when the verdict does not change.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/Validation'
                      meta:
                        type: object
                        properties:
                          recheck:
                            $ref: '#/components/schemas/ValidationRecheckMeta'
              examples:
                unchanged:
                  summary: El pago sigue liquidado
                  x-translations:
                    en:
                      summary: The payment is still settled
                  value:
                    data:
                      id: f47ac10b-58cc-4372-a567-0e02b2c3d479
                      type: validation
                      attributes:
                        validation_type: direct
                        is_playground: false
                        status: valid
                        banxico_status: valid
                        processing_time_ms: 1320
                        created_at: '2026-10-01T14:22:10Z'
                        completed_at: '2026-10-01T14:22:11Z'
                        banxico_result:
                          trackingKey: MXBA20261001001234
                          amount: 15000.5
                          _payment_status:
                            code: liquidado
                            label: Liquidado
                            settled: true
                            reversed: false
                            checked_at: '2026-10-01T18:42:07Z'
                      links:
                        self: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479
                        cep_xml: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479/cep?format=xml
                        cep_pdf: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479/cep?format=pdf
                    meta:
                      version: 1.60.0
                      request_id: b3c4d5e6f7a8
                      recheck:
                        checked_at: '2026-10-01T18:42:07Z'
                        changed: false
                        previous_status: valid
                returned:
                  summary: Banxico reporta la devolución
                  x-translations:
                    en:
                      summary: Banxico reports the return
                  value:
                    data:
                      id: f47ac10b-58cc-4372-a567-0e02b2c3d479
                      type: validation
                      attributes:
                        validation_type: direct
                        is_playground: false
                        status: returned
                        banxico_status: returned
                        processing_time_ms: 1320
                        created_at: '2026-10-01T14:22:10Z'
                        completed_at: '2026-10-01T14:22:11Z'
                        banxico_result:
                          trackingKey: MXBA20261001001234
                          amount: 15000.5
                          _payment_status:
                            code: devuelto
                            label: Devuelto
                            settled: false
                            reversed: true
                            checked_at: '2026-10-02T09:15:44Z'
                      links:
                        self: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479
                        cep_xml: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479/cep?format=xml
                        cep_pdf: /v1/validations/f47ac10b-58cc-4372-a567-0e02b2c3d479/cep?format=pdf
                    meta:
                      version: 1.60.0
                      request_id: c4d5e6f7a8b9
                      recheck:
                        checked_at: '2026-10-02T09:15:44Z'
                        changed: true
                        previous_status: valid
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Validación no encontrada, retirada o ajena a la cuenta de usuario (`not_found`).
          x-translations:
            en:
              description: Validation not found, withdrawn, or not owned by the account (`not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          $ref: '#/components/responses/ValidationPurged'
        '422':
          description: 'La validación no se puede consultar: su estado no es `valid` ni `returned` (código `recheck_not_eligible`, con el estado en `meta.status`) o tiene más horas que la ventana (código `recheck_window_expired`, con la ventana en `meta.window_hours`). UUID inválido (`invalid_uuid`).'
          x-translations:
            en:
              description: 'The validation cannot be queried: its status is neither `valid` nor `returned` (code `recheck_not_eligible`, with the status in `meta.status`) or it is older than the window (code `recheck_window_expired`, with the window in `meta.window_hours`). Invalid UUID (`invalid_uuid`).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: La validación se consultó hace menos de 10 minutos (código `recheck_rate_limited`). `Retry-After` y `meta.retry_after` dan los segundos que faltan. También responde así el límite de peticiones de la clave de API (`rate_limit_exceeded`).
          x-translations:
            en:
              description: The validation was queried less than 10 minutes ago (code `recheck_rate_limited`). `Retry-After` and `meta.retry_after` give the seconds left. The API key request limit (`rate_limit_exceeded`) responds the same way.
          headers:
            Retry-After:
              schema:
                type: integer
                example: 420
              description: Segundos que faltan para poder consultar esta validación de nuevo.
              x-translations:
                en:
                  description: Seconds left before this validation can be queried again.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Banxico no entregó un estado legible, o la consulta de estado está apagada (código `recheck_unavailable`). La validación no cambia y conserva su intervalo. `Retry-After` y `meta.retry_after` sugieren esperar 60 segundos.
          x-translations:
            en:
              description: Banxico did not return a readable status, or the status query is switched off (code `recheck_unavailable`). The validation does not change and keeps its interval. `Retry-After` and `meta.retry_after` suggest waiting 60 seconds.
          headers:
            Retry-After:
              schema:
                type: integer
                example: 60
              description: Segundos que se sugiere esperar antes de reintentar.
              x-translations:
                en:
                  description: Seconds suggested before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Query the payment status of a validation again
          description: |-
            Queries Banxico again for the payment status of a `valid` validation and, if it reports the payment as returned, moves it to `returned`.

            A `valid` verdict is a snapshot of the moment of the query: the CEP proves that the transfer settled, not that the money stayed in the beneficiary's account, and the receiving institution can return it hours or days later. This operation makes only that query, with the data the validation already stored:

            - It uses no AI and does not download another CEP.
            - **It consumes no quota** and does not count as a new validation.
            - It stores Banxico's answer in `banxico_result._payment_status`.
            - With the status `devuelto` or `en_proceso_devolucion`, it changes `status` and `banxico_status` to `returned`, keeps the CEP, and emits the `validation.returned` webhook.

            What it accepts:

            - `valid` created at most 72 hours ago: it is queried. Past that period it responds with an HTTP status `422` (with `recheck_window_expired` in the body).
            - `returned`: it responds with its current state without querying Banxico, with `meta.recheck.checked_at` set to `null`.
            - Any other status: it responds with an HTTP status `422` (with `recheck_not_eligible` in the body).

            Each validation accepts one query every 10 minutes. If it is repeated sooner, it responds with an HTTP status `429` (with `recheck_rate_limited` in the body) and the `Retry-After` header gives the seconds left. That cap adds to the API key ones.

            The response is the same validation that `GET /v1/validations/{id}` returns, with the result of the check in `meta.recheck`:

            - `checked_at`: Date and time of the query to Banxico, in ISO 8601 UTC. `null` when nothing was queried because the validation was already `returned`.
            - `changed`: `true` when this query moved the validation from `valid` to `returned`.
            - `previous_status`: Banxico verdict before the query, `valid` or `returned`.

            {% callout type="info" %}
            **If Banxico does not respond:**\
            When Banxico does not return a readable status, it responds with an HTTP status `503` (with `recheck_unavailable` in the body) and the validation keeps its interval: it can be retried right away. A `503` says nothing about the transfer.

            {% /callout %}

            The meaning of `valid` and `returned` is in {% concept slug="cep-concept" %}the CEP and its verdicts{% /concept %}, and event delivery in {% concept slug="webhooks-architecture" %}the webhooks architecture{% /concept %}.
      security:
        - ApiKeyAuth: []
  /validations/{id}/purge/prepare:
    post:
      x-related:
        - POST /v1/validations/{id}/purge/execute
        - DELETE /v1/validations/{id}
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/image
        - GET /v1/validations/{id}/cep
      tags:
        - Validations
      summary: Preparar el borrado definitivo de una validación
      description: |-
        Describe lo que borraría el borrado definitivo de una validación propia y emite el token con el que `POST /v1/validations/{id}/purge/execute` lo confirma. No cambia nada: la validación y sus archivos quedan como estaban.

        `DELETE /v1/validations/{id}` solo retira la validación del historial; el registro, el CEP, los datos extraídos y el archivo del comprobante siguen existiendo. El borrado definitivo, en dos pasos, los elimina.

        La respuesta incluye:

        - `confirmation_token`: Token de un solo uso, atado a la cuenta y a esta validación, que caduca en `expires_in` segundos.
        - `will_delete`: Lo que se borrará: el archivo, el CEP, los campos con contenido y la cantidad de registros derivados.
        - `will_keep`: Los campos que conserva la lápida en que queda la validación.
        - `cancels_pending_retries`: `true` cuando hay un ciclo de reintentos abierto, que el borrado cancela.
        - `same_image_validation_ids`: Otras validaciones de la cuenta con el mismo comprobante, que no se tocan.
        - `irreversible`: `true` siempre.

        Solo se admite una validación propia en un estado terminal. Una validación en curso responde un estado HTTP `409` (con `purge_validation_in_progress` en el cuerpo), y una ya purgada, un estado HTTP `410` (con `validation_purged` en el cuerpo).

        {% callout type="warning" %}
        **El borrado es irreversible:**\
        Lo borrado no se recupera, ni siquiera desde la plataforma. La validación queda como una lápida y **no devuelve cuota**.\
        Los respaldos de infraestructura no se alteran: conservan una copia hasta que el ciclo de respaldos la retira.

        {% /callout %}

        La lápida, lo que se borra y las limitaciones están en {% concept slug="data-retention" %}la retención de comprobantes y datos{% /concept %}.
      operationId: prepareValidationPurge
      externalDocs:
        url: https://docs.veriko.mx/how-to/validate-ocr
        description: Validate a SPEI transfer from a receipt image or PDF
      parameters:
        - $ref: '#/components/parameters/ValidationId'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/validations/{id}/purge/prepare' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '200':
          description: Resumen de lo que se borraría y token de confirmación. La validación no cambió.
          x-translations:
            en:
              description: Summary of what would be deleted and the confirmation token. The validation did not change.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - $ref: '#/components/schemas/ValidationPurgePrepareResponse'
              examples:
                ocr_with_cep:
                  summary: Validación por imagen con CEP
                  x-translations:
                    en:
                      summary: Image validation with a CEP
                  value:
                    data:
                      type: validation_purge
                      id: f47ac10b-58cc-4372-a567-0e02b2c3d479
                      attributes:
                        confirmation_token: eyJhZG1pbl9pZCI6Ii4uLiJ9.q1w2e3r4t5y6u7i8o9p0
                        expires_in: 120
                        irreversible: true
                        will_delete:
                          image: true
                          cep: true
                          stored_data:
                            - request_data
                            - ocr_result
                            - normalized_data
                            - banxico_result
                          retry_attempts: 0
                          probe_attempts: 0
                          webhook_deliveries: 1
                          notifications: 0
                          idempotency_responses: 1
                          audit_traces: true
                        will_keep:
                          - id
                          - validation_type
                          - status
                          - banxico_status
                          - amount
                          - error_code
                          - processing_time_ms
                          - created_at
                          - completed_at
                          - deleted_at
                          - purged_at
                        cancels_pending_retries: false
                        refunds_quota: false
                        same_image_validation_ids: []
                    meta:
                      version: 1.61.0
                      request_id: b3c4d5e6f7a8
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Validación no encontrada, o ajena a la cuenta de usuario (`not_found`).
          x-translations:
            en:
              description: Validation not found, or not owned by the account (`not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: La validación sigue en curso (`queued` o `processing`) y todavía no se puede purgar (`purge_validation_in_progress`, con el estado en `meta.status`).
          x-translations:
            en:
              description: The validation is still in progress (`queued` or `processing`) and cannot be purged yet (`purge_validation_in_progress`, with the status in `meta.status`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          $ref: '#/components/responses/ValidationPurged'
        '422':
          description: UUID inválido en la ruta (`invalid_uuid`).
          x-translations:
            en:
              description: Invalid UUID in path (`invalid_uuid`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: Prepare the permanent deletion of a validation
          description: |-
            Describes what the permanent deletion of one of your own validations would delete and issues the token with which `POST /v1/validations/{id}/purge/execute` confirms it. It changes nothing: the validation and its files stay as they were.

            `DELETE /v1/validations/{id}` only removes the validation from the history; the record, the CEP, the extracted data, and the receipt file keep existing. The permanent deletion, in two steps, eliminates them.

            The response includes:

            - `confirmation_token`: Single-use token, bound to the account and to this validation, that expires in `expires_in` seconds.
            - `will_delete`: What will be deleted: the file, the CEP, the fields with content, and the number of derived records.
            - `will_keep`: The fields the tombstone the validation is left as keeps.
            - `cancels_pending_retries`: `true` when there is an open retry cycle, which the deletion cancels.
            - `same_image_validation_ids`: Other validations of the account with the same receipt, which are not touched.
            - `irreversible`: `true` always.

            Only one of your own validations in a terminal status is accepted. A validation in progress responds with an HTTP status `409` (with `purge_validation_in_progress` in the body), and one already purged, with an HTTP status `410` (with `validation_purged` in the body).

            {% callout type="warning" %}
            **The deletion is irreversible:**\
            What is deleted cannot be recovered, not even from the platform. The validation is left as a tombstone and **does not refund quota**.\
            Infrastructure backups are not altered: they keep a copy until the backup cycle retires it.

            {% /callout %}

            The tombstone, what is deleted, and the limitations are in {% concept slug="data-retention" %}the retention of receipts and data{% /concept %}.
      security:
        - ApiKeyAuth: []
  /validations/{id}/purge/execute:
    post:
      x-related:
        - POST /v1/validations/{id}/purge/prepare
        - DELETE /v1/validations/{id}
        - GET /v1/validations/{id}
        - GET /v1/validations/{id}/image
        - GET /v1/validations/{id}/cep
      tags:
        - Validations
      summary: Ejecutar el borrado definitivo de una validación
      description: |-
        Borra de forma definitiva el contenido de una validación propia, con el token que emitió `POST /v1/validations/{id}/purge/prepare`. Es el segundo paso de un flujo de dos pasos: sin ese token no se borra nada.

        Se borra:

        - El archivo del comprobante, imagen o PDF.
        - El CEP, en XML y en PDF.
        - `request_data`, `ocr_result`, `banxico_result` y las advertencias de normalización. De `normalized_data` solo se conserva el monto.
        - Los registros derivados con datos personales: intentos de reintento, sondeos de cuenta, respuestas guardadas de `Idempotency-Key` y notificaciones de la bandeja.
        - El cuerpo enviado y la respuesta del receptor en las entregas de webhook de esa validación, y el contenido de sus notificaciones. El registro de cada entrega se conserva.
        - Los cuerpos de las filas de auditoría que nombran la validación, y el contexto libre de sus eventos de seguridad.

        La validación queda como una lápida: conserva `id`, `validation_type`, `status`, `banxico_status`, las fechas, `processing_time_ms`, `error_code`, el monto y `purged_at`. Con eso la cuota, las estadísticas, finanzas y el historial siguen cuadrando. **El borrado no devuelve cuota.**

        Después del borrado, `GET /v1/validations/{id}` responde la lápida con un estado HTTP `200` y `purged_at`. La imagen, el CEP, los reintentos y la revisión posterior responden un estado HTTP `410` (con `validation_purged` en el cuerpo), y los webhooks, los reintentos y los barridos la ignoran. Un ciclo de reintentos abierto se cancela.

        El token se verifica antes de borrar:

        - Falta, está mal formado, su firma no es válida o venció: responde un estado HTTP `422` con su código (`confirmation_token_missing`, `confirmation_token_malformed`, `confirmation_token_signature_invalid` o `confirmation_token_expired`).
        - Es de otra validación, de otra cuenta o de otra operación: responde un estado HTTP `422` (con `purge_token_mismatch` o `confirmation_token_operation_mismatch` en el cuerpo).
        - Ya se usó: responde un estado HTTP `409` (con `confirmation_token_already_used` en el cuerpo).

        Si la respuesta no llega, `GET /v1/validations/{id}` dice si el borrado se completó: una validación purgada trae `purged_at`. Repetir este paso sobre una validación purgada responde un estado HTTP `410`.

        {% callout type="warning" %}
        **El borrado es irreversible:**\
        Lo borrado no se recupera, ni siquiera desde la plataforma. Los respaldos de infraestructura no se alteran y el catálogo global de cuentas, que no guarda vínculo con la validación, no se toca.

        {% /callout %}

        Lo que se borra, la lápida y las limitaciones están en {% concept slug="data-retention" %}la retención de comprobantes y datos{% /concept %}.
      operationId: executeValidationPurge
      externalDocs:
        url: https://docs.veriko.mx/how-to/purge-a-validation
        description: Permanently delete a validation, its receipt, and its CEP
      parameters:
        - $ref: '#/components/parameters/ValidationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidationPurgeExecuteRequest'
            example:
              confirmation_token: eyJhZG1pbl9pZCI6Ii4uLiJ9.q1w2e3r4t5y6u7i8o9p0
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/validations/{id}/purge/execute' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json' \
              -d '{
                "confirmation_token": "eyJhZG1pbl9pZCI6Ii4uLiJ9.q1w2e3r4t5y6u7i8o9p0"
              }'
      responses:
        '200':
          description: Validación purgada. Devuelve cuándo se purgó, el estado del borrado de los archivos y lo que se borró.
          x-translations:
            en:
              description: Validation purged. Returns when it was purged, the state of the file deletion, and what was deleted.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - $ref: '#/components/schemas/ValidationPurgeExecuteResponse'
              examples:
                purged:
                  summary: Validación purgada
                  x-translations:
                    en:
                      summary: Validation purged
                  value:
                    data:
                      type: validation_purge
                      id: f47ac10b-58cc-4372-a567-0e02b2c3d479
                      attributes:
                        purged_at: '2026-10-02T09:30:00Z'
                        file_removal: complete
                        deleted:
                          image: true
                          cep_pdf: true
                          retry_cycle_cancelled: false
                          retry_attempts: 0
                          probe_attempts: 0
                          webhook_deliveries: 1
                          notification_deliveries: 0
                          notifications: 0
                          idempotency_responses: 1
                          import_rows: 0
                          audit_log_rows: 2
                          security_events_rows: 1
                    meta:
                      version: 1.61.0
                      request_id: c4d5e6f7a8b9
        '400':
          $ref: '#/components/responses/BodyEmpty'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Validación no encontrada, o ajena a la cuenta de usuario (`not_found`).
          x-translations:
            en:
              description: Validation not found, or not owned by the account (`not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: El token ya se usó (`confirmation_token_already_used`), o la validación sigue en curso y todavía no se puede purgar (`purge_validation_in_progress`, con el estado en `meta.status`).
          x-translations:
            en:
              description: The token was already used (`confirmation_token_already_used`), or the validation is still in progress and cannot be purged yet (`purge_validation_in_progress`, with the status in `meta.status`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '410':
          $ref: '#/components/responses/ValidationPurged'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: 'El token no sirve. Códigos posibles: `confirmation_token_missing`, `confirmation_token_malformed`, `confirmation_token_signature_invalid`, `confirmation_token_expired`, `confirmation_token_operation_mismatch` y `purge_token_mismatch` (el token es de otra validación o de otra cuenta). UUID inválido en la ruta (`invalid_uuid`).'
          x-translations:
            en:
              description: 'The token is not valid. Possible codes: `confirmation_token_missing`, `confirmation_token_malformed`, `confirmation_token_signature_invalid`, `confirmation_token_expired`, `confirmation_token_operation_mismatch`, and `purge_token_mismatch` (the token belongs to another validation or another account). Invalid UUID in path (`invalid_uuid`).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: Execute the permanent deletion of a validation
          description: |-
            Permanently deletes the content of one of your own validations, with the token issued by `POST /v1/validations/{id}/purge/prepare`. It is the second step of a two-step flow: without that token nothing is deleted.

            What is deleted:

            - The receipt file, image or PDF.
            - The CEP, as XML and as PDF.
            - `request_data`, `ocr_result`, `banxico_result`, and the normalization warnings. Of `normalized_data`, only the amount is kept.
            - The derived records with personal data: retry attempts, account probes, stored `Idempotency-Key` responses, and inbox notifications.
            - The sent body and the receiver response in the webhook deliveries of that validation, and the content of its notifications. The record of each delivery is kept.
            - The bodies of the audit rows that name the validation, and the free-form context of its security events.

            The validation is left as a tombstone: it keeps `id`, `validation_type`, `status`, `banxico_status`, the dates, `processing_time_ms`, `error_code`, the amount, and `purged_at`. With that, quota, statistics, finance, and history keep adding up. **The deletion does not refund quota.**

            After the deletion, `GET /v1/validations/{id}` responds with the tombstone with an HTTP status `200` and `purged_at`. The image, the CEP, the retries, and the follow-up check respond with an HTTP status `410` (with `validation_purged` in the body), and webhooks, retries, and sweeps ignore it. An open retry cycle is cancelled.

            The token is verified before deleting:

            - It is missing, malformed, its signature is not valid, or it expired: it responds with an HTTP status `422` with its code (`confirmation_token_missing`, `confirmation_token_malformed`, `confirmation_token_signature_invalid`, or `confirmation_token_expired`).
            - It belongs to another validation, another account, or another operation: it responds with an HTTP status `422` (with `purge_token_mismatch` or `confirmation_token_operation_mismatch` in the body).
            - It was already used: it responds with an HTTP status `409` (with `confirmation_token_already_used` in the body).

            If the response does not arrive, `GET /v1/validations/{id}` says whether the deletion completed: a purged validation carries `purged_at`. Repeating this step on a purged validation responds with an HTTP status `410`.

            {% callout type="warning" %}
            **The deletion is irreversible:**\
            What is deleted cannot be recovered, not even from the platform. Infrastructure backups are not altered and the global account catalog, which keeps no link to the validation, is not touched.

            {% /callout %}

            What is deleted, the tombstone, and the limitations are in {% concept slug="data-retention" %}the retention of receipts and data{% /concept %}.
      security:
        - ApiKeyAuth: []
  /public/banks:
    get:
      x-related:
        - GET /v1/public/bin-lookup/{bin}
        - POST /v1/validate
        - POST /v1/validate-ocr
      tags:
        - Public
      summary: Listar los bancos participantes con su código
      description: |-
        Devuelve el catálogo actual de instituciones bancarias participantes del sistema SPEI. El catálogo es actualizado semanalmente desde las listas oficiales del **Banco de México (Banxico).**

        El atributo `code` identifica a cada institución bancaria.

        La lista es cacheable: incluye la cabecera `ETag`, que puede enviarse en otra petición como `If-None-Match`; si el catálogo no ha cambiado, responde con un estado HTTP `304` (sin cuerpo).
      operationId: listBanks
      x-codeSamples:
        - lang: curl
          label: Shell
          source: curl -X GET 'https://api.veriko.mx/v1/public/banks'
        - lang: python
          label: Python
          source: |
            import requests

            response = requests.get("https://api.veriko.mx/v1/public/banks")
            print(response.json())
        - lang: javascript
          label: JavaScript
          source: |
            fetch("https://api.veriko.mx/v1/public/banks")
              .then(response => response.json())
              .then(data => console.log(data));
        - lang: php
          label: PHP
          source: |
            <?php

            $ch = curl_init("https://api.veriko.mx/v1/public/banks");

            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $response = curl_exec($ch);
            curl_close($ch);

            echo $response;
      parameters:
        - $ref: '#/components/parameters/IfNoneMatchHeader'
      responses:
        '200':
          description: Catálogo completo de instituciones participantes del sistema SPEI.
          x-translations:
            en:
              description: Full catalog of participant institutions in the SPEI system.
          headers:
            ETag:
              schema:
                type: string
                example: '"a1b2c3d4e5f6..."'
              description: Firma de la petición actual. Pasar como `If-None-Match` en peticiones posteriores.
              x-translations:
                en:
                  description: Fingerprint of the current request. Pass as `If-None-Match` on subsequent requests.
            Cache-Control:
              schema:
                type: string
                example: private, max-age=3600
              description: TTL de caché en el cliente (1 h).
              x-translations:
                en:
                  description: Client cache TTL (1 h).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListBanksResponse'
              examples:
                catalog:
                  summary: Catálogo SPEI (muestra de 3 bancos representativos)
                  x-translations:
                    en:
                      summary: SPEI catalog (sample of 3 representative banks)
                  value:
                    data:
                      - type: bank
                        id: '40012'
                        attributes:
                          code: '40012'
                          name: BBVA MEXICO
                      - type: bank
                        id: '40021'
                        attributes:
                          code: '40021'
                          name: HSBC
                      - type: bank
                        id: '40002'
                        attributes:
                          code: '40002'
                          name: BANCO NACIONAL DE MEXICO
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: e7f8a9b0c1d2
        '304':
          description: No modificado — los datos no han cambiado según el ETag enviado.
          x-translations:
            en:
              description: Not modified — the data has not changed per the sent ETag.
          headers:
            ETag:
              schema:
                type: string
                example: '"a1b2c3d4e5f6..."'
              description: ETag actual de la instantánea.
              x-translations:
                en:
                  description: Current snapshot ETag.
            Cache-Control:
              schema:
                type: string
                example: private, max-age=3600
              description: TTL de caché en el cliente.
              x-translations:
                en:
                  description: Client cache TTL.
      x-translations:
        en:
          summary: Codes and list of participant banks
          description: |-
            Returns the current catalog of banking institutions participating in the SPEI system. The catalog is updated weekly from the official lists of the **Bank of Mexico (Banxico).**

            The `code` identifies each banking institution participating in the SPEI system.

            The response is cacheable: it includes the `ETag` header, which can be sent in another request as `If-None-Match`; if the catalog has not changed, it returns `304` (no body).
      security: []
  /public/bin-lookup/{bin}:
    get:
      x-related:
        - GET /v1/public/banks
        - POST /v1/validate
        - POST /v1/validate-ocr
      tags:
        - Public
      summary: Obtener el banco de una Tarjeta
      description: |-
        Resuelve el banco emisor de una tarjeta bancaria a partir de su [**BIN (Bank Identification Number)**](https://www.mercadopago.com.mx/blog/que-es-el-bin-de-una-tarjeta).

        Acepta de **6 a 16 dígitos**, aunque **solo se usan los primeros 6-8**. El BIN que aparece en la respuesta, es el prefijo que coincidió en la búsqueda, junto con la información de la institución bancaria.

        El `banxico_code` devuelto es el código de 5 dígitos que identifica a cada institución bancaria participante de SPEI.
      operationId: lookupBin
      parameters:
        - name: bin
          in: path
          required: true
          description: BIN o número de tarjeta (6 a 16 dígitos). Solo se usan los primeros 6-8.
          x-translations:
            en:
              description: BIN or card number (6 to 16 digits). Only the leading 6-8 are used.
          schema:
            type: string
            pattern: ^\d{6,16}$
            minLength: 6
            maxLength: 16
            example: '45320151'
          example: '45320151'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: curl -X GET 'https://api.veriko.mx/v1/public/bin-lookup/{bin}'
        - lang: python
          label: Python
          source: |
            import requests

            bin = "424242"
            response = requests.get(f"https://api.veriko.mx/v1/public/bin-lookup/{bin}")
            print(response.json())
        - lang: javascript
          label: JavaScript
          source: |
            const bin = "424242";

            fetch(`https://api.veriko.mx/v1/public/bin-lookup/${bin}`)
              .then(response => response.json())
              .then(data => console.log(data));
        - lang: php
          label: PHP
          source: |
            <?php

            $bin = "424242";
            $ch = curl_init("https://api.veriko.mx/v1/public/bin-lookup/$bin");

            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $response = curl_exec($ch);
            curl_close($ch);

            echo $response;
      responses:
        '200':
          description: 'Información del BIN: Banco emisor y metadatos.'
          x-translations:
            en:
              description: 'BIN information: issuing bank and metadata.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BinLookupResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: Get the bank of a card
          description: |-
            Resolves the issuing bank of a payment card from its [**BIN (Bank Identification Number)**](https://www.mercadopago.com.mx/blog/que-es-el-bin-de-una-tarjeta).

            Accepts **6 to 16 digits**, although **only the leading 6-8 are used**. The BIN returned in the response is the prefix that matched in the lookup, along with the banking institution's information.

            The returned `banxico_code` is the 5-digit code that identifies each banking institution participating in SPEI.
      security: []
  /public/spei-calendar/{year}:
    get:
      x-related:
        - GET /v1/public/banks
        - POST /v1/validate
        - GET /v1/validations/{id}
      tags:
        - Public
      summary: Obtener el calendario de días inhábiles SPEI de un año
      description: |-
        Devuelve el calendario de días inhábiles SPEI de un año, con la disposición oficial de la que sale.

        La respuesta incluye:

        - `non_business_days`: Los días inhábiles que caen de lunes a viernes, en orden y con formato `YYYY-MM-DD`.
        - `source`: La disposición de la **Comisión Nacional Bancaria y de Valores (CNBV)** publicada en el **Diario Oficial de la Federación (DOF)**, con su título y su fecha de publicación.

        Los sábados y los domingos también son inhábiles, siempre, y no se repiten en `non_business_days`.

        El calendario determina el día de operación de una transferencia. El SPEI opera todos los días, pero el día hábil cambia a las 18:00 (hora del centro de México): lo enviado a partir de esa hora, en sábado, en domingo o en un día inhábil opera el siguiente día hábil. Ese día es el `operationDate` de `banxico_result`, y puede diferir de la fecha de envío.

        La consulta no consume cuota de validaciones. Un año sin calendario responde un estado HTTP `404` (con `spei_calendar_year_not_covered` en el cuerpo), y `meta.covered_years` lista los años disponibles. El calendario de cada año se añade cuando la CNBV publica su disposición, normalmente en diciembre del año anterior.

        La respuesta es cacheable: incluye la cabecera `ETag`, que puede enviarse en otra petición como `If-None-Match`; si el calendario no ha cambiado, responde con un estado HTTP `304` (sin cuerpo).

        La diferencia entre la fecha de envío y la de operación está en {% concept slug="timezone-handling" %}fechas y zona horaria{% /concept %}.
      operationId: getSpeiCalendar
      parameters:
        - name: year
          in: path
          required: true
          description: Año del calendario, de cuatro dígitos.
          x-translations:
            en:
              description: Calendar year, four digits.
          schema:
            type: integer
            minimum: 1000
            maximum: 9999
            example: 2026
          example: 2026
        - $ref: '#/components/parameters/IfNoneMatchHeader'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: curl -X GET 'https://api.veriko.mx/v1/public/spei-calendar/{year}'
        - lang: python
          label: Python
          source: |
            import requests

            year = 2026
            response = requests.get(f"https://api.veriko.mx/v1/public/spei-calendar/{year}")
            print(response.json())
        - lang: javascript
          label: JavaScript
          source: |
            const year = 2026;

            fetch(`https://api.veriko.mx/v1/public/spei-calendar/${year}`)
              .then(response => response.json())
              .then(data => console.log(data));
        - lang: php
          label: PHP
          source: |
            <?php

            $year = 2026;
            $ch = curl_init("https://api.veriko.mx/v1/public/spei-calendar/$year");

            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $response = curl_exec($ch);
            curl_close($ch);

            echo $response;
      responses:
        '200':
          description: Calendario de días inhábiles SPEI del año, con su fuente.
          x-translations:
            en:
              description: SPEI non-business-day calendar of the year, with its source.
          headers:
            ETag:
              schema:
                type: string
                example: '"a1b2c3d4e5f6..."'
              description: Firma del calendario actual. Pasar como `If-None-Match` en peticiones posteriores.
              x-translations:
                en:
                  description: Fingerprint of the current calendar. Pass as `If-None-Match` on subsequent requests.
            Cache-Control:
              schema:
                type: string
                example: private, max-age=3600
              description: TTL de caché en el cliente (1 h).
              x-translations:
                en:
                  description: Client cache TTL (1 h).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SpeiCalendarResponse'
              examples:
                calendar:
                  summary: Calendario de 2026
                  x-translations:
                    en:
                      summary: Calendar of 2026
                  value:
                    data:
                      type: spei_calendar
                      id: '2026'
                      attributes:
                        year: 2026
                        non_business_days:
                          - '2026-01-01'
                          - '2026-02-02'
                          - '2026-03-16'
                          - '2026-04-02'
                          - '2026-04-03'
                          - '2026-05-01'
                          - '2026-09-16'
                          - '2026-11-02'
                          - '2026-11-16'
                          - '2026-12-25'
                        source:
                          issuer: CNBV
                          published_in: DOF
                          published_on: '2025-12-10'
                          title: Disposiciones de carácter general que señalan los días del año 2026 en que las entidades financieras sujetas a la supervisión de la Comisión Nacional Bancaria y de Valores deberán cerrar sus puertas y suspender operaciones
                    meta:
                      version: 1.63.0
                      api_version: v1
                      request_id: e7f8a9b0c1d2
                      covered_years:
                        - 2024
                        - 2025
                        - 2026
        '304':
          description: 'No modificado: el calendario no ha cambiado según el ETag enviado.'
          x-translations:
            en:
              description: 'Not modified: the calendar has not changed per the sent ETag.'
          headers:
            ETag:
              schema:
                type: string
                example: '"a1b2c3d4e5f6..."'
              description: ETag actual del calendario.
              x-translations:
                en:
                  description: Current calendar ETag.
            Cache-Control:
              schema:
                type: string
                example: private, max-age=3600
              description: TTL de caché en el cliente (1 h).
              x-translations:
                en:
                  description: Client cache TTL (1 h).
        '404':
          description: El año no tiene calendario (código `spei_calendar_year_not_covered`). `meta.covered_years` lista los años disponibles.
          x-translations:
            en:
              description: The year has no calendar (code `spei_calendar_year_not_covered`). `meta.covered_years` lists the available years.
              example:
                errors:
                  - status: '404'
                    code: spei_calendar_year_not_covered
                    detail: We do not have the calendar for 2030. The available years run from 2024 to 2026.
                meta:
                  version: 1.63.0
                  request_id: c1d2e3f4a5b6
                  covered_years:
                    - 2024
                    - 2025
                    - 2026
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorResponse'
                  - type: object
                    properties:
                      meta:
                        type: object
                        properties:
                          covered_years:
                            type: array
                            items:
                              type: integer
                            description: Años con calendario publicado, de menor a mayor.
                            x-translations:
                              en:
                                description: Years with a published calendar, from lowest to highest.
              example:
                errors:
                  - status: '404'
                    code: spei_calendar_year_not_covered
                    detail: No tenemos el calendario de 2030. Los años disponibles van de 2024 a 2026.
                meta:
                  version: 1.63.0
                  request_id: c1d2e3f4a5b6
                  covered_years:
                    - 2024
                    - 2025
                    - 2026
        '422':
          description: El año no tiene cuatro dígitos (código `spei_calendar_year_invalid`).
          x-translations:
            en:
              description: The year does not have four digits (code `spei_calendar_year_invalid`).
              example:
                errors:
                  - status: '422'
                    code: spei_calendar_year_invalid
                    detail: The year must have four digits, for example 2026.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - status: '422'
                    code: spei_calendar_year_invalid
                    detail: El año debe tener cuatro dígitos, por ejemplo 2026.
                meta:
                  version: 1.63.0
                  request_id: d2e3f4a5b6c7
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: Get the calendar of SPEI non-business days for a year
          description: |-
            Returns the calendar of SPEI non-business days for a year, with the official provision it comes from.

            The response includes:

            - `non_business_days`: The non-business days that fall from Monday to Friday, in order and in `YYYY-MM-DD` format.
            - `source`: The provision of the **Comisión Nacional Bancaria y de Valores (CNBV)** published in the **Diario Oficial de la Federación (DOF)**, with its title and publication date.

            Saturdays and Sundays are also non-business days, always, and are not repeated in `non_business_days`.

            The calendar determines the operation day of a transfer. SPEI operates every day, but the business day changes at 18:00 (Mexico central time): what is sent from that hour on, on a Saturday, on a Sunday, or on a non-business day operates on the next business day. That day is the `operationDate` of `banxico_result`, and it can differ from the sending date.

            The request does not consume validation quota. A year without a calendar responds with an HTTP status `404` (with `spei_calendar_year_not_covered` in the body), and `meta.covered_years` lists the available years. The calendar of each year is added when the CNBV publishes its provision, normally in December of the previous year.

            The response is cacheable: it includes the `ETag` header, which can be sent in another request as `If-None-Match`; if the calendar has not changed, it returns an HTTP status `304` (no body).

            The difference between the sending date and the operation date is in {% concept slug="timezone-handling" %}dates and time zone{% /concept %}.
      security: []
  /beneficiaries/export:
    get:
      x-related:
        - DELETE /v1/beneficiaries/{id}
        - DELETE /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries
        - GET /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}/preview
        - GET /v1/beneficiaries/imports/template
      tags:
        - Beneficiaries
      summary: Exportar los beneficiarios de la cuenta de usuario
      description: |-
        Descarga las cuentas beneficiarias del usuario autenticado como archivo, usa el mismo filtro `with_archived` que `GET /v1/beneficiaries/list-beneficiaries`.

        El formato de descarga se elige con `?format` (`csv` por defecto, `xlsx` como alternativa). El tope de filas es de 100 000, y `?limit` sirve para pedir menos, nunca más.

        **Los números de cuenta salen enmascarados (salvo la CLABE):** Una tarjeta se exporta como `4111 •••• •••• 1111` y un celular como `••••5678`; la CLABE se exporta completa. El criterio y el resto de las superficies enmascaradas están en {% concept slug="pii-masking" %}el enmascarado de datos{% /concept %}.
      operationId: exportBeneficiaries
      externalDocs:
        url: https://docs.veriko.mx/how-to/manage-beneficiaries
        description: Create, update, and export beneficiaries
      parameters:
        - name: with_archived
          in: query
          required: false
          schema:
            type: string
            enum:
              - ''
              - '0'
              - '1'
          example: '0'
          description: 'Filtro de archivados: vacío u omitido = todas; `0` = solo activas; `1` = solo archivadas. Mismo contrato que `GET /v1/beneficiaries`.'
          x-translations:
            en:
              description: 'Archived filter: empty or omitted = all; `0` = active only; `1` = archived only. Same contract as `GET /v1/beneficiaries`.'
        - name: format
          in: query
          required: false
          schema:
            type: string
            default: csv
            enum:
              - csv
              - xlsx
          example: csv
          description: Formato del archivo. `csv` por defecto; `xlsx` produce una hoja de cálculo. Cualquier otro valor se sirve como `csv`.
          x-translations:
            en:
              description: Output format. `csv` by default; `xlsx` produces a spreadsheet. Any other value is served as `csv`.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100000
          example: 500
          description: 'Recorta el número de filas del archivo. Solo baja el tope: un valor mayor que 100 000, o menor que 1, deja el tope por defecto.'
          x-translations:
            en:
              description: 'Trims the number of rows in the file. It only lowers the cap: a value above 100,000, or below 1, leaves the default cap in place.'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/beneficiaries/export' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Archivo con la lista de beneficiarios según el filtro y formato solicitados.
          x-translations:
            en:
              description: File with the beneficiary list per the requested filter and format.
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="cuentas-2026-05-16.csv"
              description: Sugerencia de nombre de archivo, derivada de la marca y de la fecha de exportación.
              x-translations:
                en:
                  description: Suggested filename, derived from the brand and the export date.
            Content-Type:
              schema:
                type: string
                example: text/csv; charset=UTF-8
              description: 'Varía según `?format`. Para CSV: `text/csv; charset=UTF-8` con BOM UTF-8 al inicio del cuerpo.'
              x-translations:
                en:
                  description: 'Varies by `?format`. For CSV: `text/csv; charset=UTF-8` with a UTF-8 BOM at the start of the body.'
          content:
            text/csv:
              schema:
                type: string
                format: binary
                description: 'Archivo CSV. Columnas: Alias, Cuenta, Tipo, Banco, Código Banco, Estado, Fecha Creación (UTC). El número de cuenta va prefijado con apóstrofe para forzar Excel a tratarlo como texto (no notación científica).'
                x-translations:
                  en:
                    description: 'CSV file. Columns: Alias, Cuenta, Tipo, Banco, Código Banco, Estado, Fecha Creación (UTC). The account number is prefixed with an apostrophe to force Excel to treat it as text (no scientific notation).'
            application/octet-stream:
              schema:
                type: string
                format: binary
                description: Cuerpo binario cuando `?format` solicita un formato non-CSV. El `Content-Type` exacto depende del formato solicitado.
                x-translations:
                  en:
                    description: Binary body when `?format` requests a non-CSV format. The exact `Content-Type` depends on the requested format.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-translations:
        en:
          summary: Export beneficiaries as CSV
          description: |-
            Downloads the beneficiary accounts of the authenticated account as a
            file, under the same `with_archived` filter as `GET
            /v1/beneficiaries/list-beneficiaries`.

            The download format is chosen with `?format` (`csv` by default,
            `xlsx` as the alternative). The row cap is 100,000, and `?limit`
            asks for fewer, never more.

            **Account numbers come out masked, except the CLABE:** a card is
            exported as `4111 •••• •••• 1111` and a phone as `••••5678`; the
            CLABE is exported in full. The criterion and the other masked
            surfaces are covered in {% concept slug="pii-masking" %}data
            masking{% /concept %}.
      security:
        - ApiKeyAuth: []
  /beneficiaries:
    get:
      tags:
        - Beneficiaries
      summary: Listar beneficiarios
      description: |-
        Devuelve {% concept slug="beneficiaries" %}las cuentas beneficiarias guardadas{% /concept %} del usuario autenticado. La lista reúne los tres tipos de beneficiarios (CLABE, tarjeta y celular/DiMo).

        El parámetro `with_archived` es opcional y controla la visibilidad de las cuentas archivadas. `0`: solo activas; `1`: solo archivadas.

        `GET /v1/beneficiaries/lookup` resuelve una cuenta concreta sin recorrer la lista.

        `POST /v1/beneficiaries/imports` da de alta varios beneficiarios a la vez, desde CSV o XLS.
      operationId: listBeneficiaries
      externalDocs:
        url: https://docs.veriko.mx/how-to/manage-beneficiaries
        description: Create, update, and export beneficiaries
      parameters:
        - name: with_archived
          in: query
          required: false
          schema:
            type: string
            enum:
              - ''
              - '0'
              - '1'
          example: '0'
          description: 'Filtro de archivados: vacío u omitido = todas; `0` = solo activas; `1` = solo archivadas.'
          x-translations:
            en:
              description: 'Archived filter: empty or omitted = all; `0` = active only; `1` = archived only.'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/beneficiaries' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Lista completa de cuentas beneficiarias del usuario según el filtro.
          x-translations:
            en:
              description: Complete list of the user's beneficiary accounts per the filter.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListBeneficiariesResponse'
              examples:
                mixed:
                  summary: Mezcla de CLABE, tarjeta y celular DiMo
                  x-translations:
                    en:
                      summary: Mix of CLABE, card, and DiMo phone
                  value:
                    data:
                      - type: beneficiary
                        id: '42'
                        attributes:
                          account_number: '012180004412345678'
                          account_type: clabe
                          bank_code: '40012'
                          bank_name: BBVA MEXICO
                          label: Proveedor ABC
                          status: active
                          created_at: '2026-04-01T10:15:00Z'
                      - type: beneficiary
                        id: '43'
                        attributes:
                          account_number: '5512345678'
                          account_type: phone
                          bank_code: '40012'
                          bank_name: BBVA MEXICO
                          label: Cuenta DiMo Banamex
                          status: active
                          created_at: '2026-05-10T09:30:00Z'
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: a1b2c3d4e5f6a7b8c9d0e1f2
                empty:
                  summary: Sin beneficiarios registrados
                  x-translations:
                    en:
                      summary: No beneficiaries registered
                  value:
                    data: []
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: b2c3d4e5f6a7
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-related:
        - POST /v1/beneficiaries
        - GET /v1/beneficiaries/lookup
        - POST /v1/beneficiaries/imports
        - GET /v1/beneficiaries/export
      x-translations:
        en:
          summary: List beneficiaries
          description: |-
            Returns {% concept slug="beneficiaries" %}the beneficiary accounts saved{% /concept %} under the authenticated account,
            unpaginated. The list gathers the three types the API recognises: CLABE,
            card, and phone (DiMo).

            The `with_archived` parameter governs archived-account visibility and
            accepts three states:

            - omitted or empty: active and archived, which is the default;
            - `0`: active only;
            - `1`: archived only.

            To resolve a single account without walking the list there is
            `GET /v1/beneficiaries/lookup`; to register many at once from CSV or
            XLS, `POST /v1/beneficiaries/imports`.
      security:
        - ApiKeyAuth: []
    post:
      tags:
        - Beneficiaries
      summary: Crear un beneficiario
      description: |-
        Registra una cuenta beneficiaria nueva. El tipo de cuenta se autodetecta por la longitud, y de eso depende qué campos se envían en la petición:

        - CLABE (18 dígitos): `bank_code` es opcional, se deriva del prefijo.
        - Tarjeta (16 dígitos): `bank_code` es opcional, se deriva del BIN.
        - Celular/DiMo (10 dígitos): `bank_code` es **obligatorio** en el cuerpo, ya que el número por sí solo no identifica a la institución bancaria.

        Si la cuenta a registrar no existe previamente, se registra y responde con un estado HTTP `201`.

        Si se intenta registrar una cuenta activa, responde con un estado HTTP `422` (con `beneficiary_already_registered` en el cuerpo).

        Si existe pero está archivada, el alta la **reactiva** y responde con un estado HTTP `200` (con `meta.reactivated=true` en el cuerpo). Si la petición no trae `label`, la reactivación conserva el alias que la cuenta ya tenía; si lo trae, lo reemplaza.
      operationId: createBeneficiary
      externalDocs:
        url: https://docs.veriko.mx/how-to/manage-beneficiaries
        description: Create, update, and export beneficiaries
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateBeneficiaryRequest'
            examples:
              flat_clabe:
                summary: CLABE — objeto plano
                x-translations:
                  en:
                    summary: CLABE — flat object
                value:
                  account_number: '012180004412345678'
                  label: Proveedor ABC
              flat_card:
                summary: Tarjeta — objeto plano
                x-translations:
                  en:
                    summary: Card — flat object
                value:
                  account_number: '4111111111111111'
                  label: Tarjeta BBVA nómina
              flat_phone:
                summary: Celular DiMo (10 dígitos) — bank_code obligatorio
                x-translations:
                  en:
                    summary: DiMo phone (10 digits) — bank_code required
                value:
                  account_number: '5512345678'
                  bank_code: '40012'
                  label: Cuenta DiMo Banamex
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/beneficiaries' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json' \
              -d '{
                "account_number": "4111111111111111",
                "label": "Tarjeta BBVA nómina"
              }'
      responses:
        '200':
          description: Beneficiario reactivado (existía en estado archivado para el mismo `account_number`). Incluye `meta.reactivated=true`.
          x-translations:
            en:
              description: Beneficiary reactivated (an archived record existed for the same `account_number`). Includes `meta.reactivated=true`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateBeneficiaryResponse'
              example:
                data:
                  type: beneficiary
                  id: '42'
                  attributes:
                    account_number: '012180004412345678'
                    account_type: clabe
                    clabe: '012180004412345678'
                    bank_code: '40012'
                    bank_name: BBVA MEXICO
                    label: Proveedor ABC
                    status: active
                    created_at: '2025-01-15T12:00:00Z'
                meta:
                  version: 1.47.0
                  api_version: v1
                  request_id: c3d4e5f6a7b8c9d0e1f2a3b4
                  reactivated: true
        '201':
          description: Beneficiario creado.
          x-translations:
            en:
              description: Beneficiary created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateBeneficiaryResponse'
              examples:
                clabe_created:
                  summary: CLABE creada — banco derivado del prefijo
                  x-translations:
                    en:
                      summary: CLABE created — bank derived from prefix
                  value:
                    data:
                      type: beneficiary
                      id: '50'
                      attributes:
                        account_number: '012180004412345678'
                        account_type: clabe
                        clabe: '012180004412345678'
                        bank_code: '40012'
                        bank_name: BBVA MEXICO
                        label: Proveedor ABC
                        status: active
                        created_at: '2026-05-16T09:00:00Z'
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: d4e5f6a7b8c9d0e1f2a3b4c5
                phone_created:
                  summary: Celular DiMo creado — bank_code obligatorio
                  x-translations:
                    en:
                      summary: DiMo phone created — bank_code required
                  value:
                    data:
                      type: beneficiary
                      id: '51'
                      attributes:
                        account_number: '5512345678'
                        account_type: phone
                        clabe: '5512345678'
                        bank_code: '40012'
                        bank_name: BBVA MEXICO
                        label: Cuenta DiMo Banamex
                        status: active
                        created_at: '2026-05-16T09:05:00Z'
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: e5f6a7b8c9d0e1f2a3b4c5d6
        '400':
          description: El cuerpo de la petición está vacío o no es JSON válido.
          x-translations:
            en:
              description: The request body is empty or is not valid JSON.
              examples:
                body_empty:
                  value:
                    errors:
                      - detail: The request body is empty.
                invalid_json:
                  value:
                    errors:
                      - detail: The body is not valid JSON.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                body_empty:
                  summary: Cuerpo vacío
                  x-translations:
                    en:
                      summary: Empty body
                  value:
                    errors:
                      - status: '400'
                        code: body_empty
                        detail: El cuerpo de la petición está vacío.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: c1d2e3f4a5b6
                invalid_json:
                  summary: JSON mal formado
                  x-translations:
                    en:
                      summary: Malformed JSON
                  value:
                    errors:
                      - status: '400'
                        code: invalid_json
                        detail: El cuerpo no es JSON válido.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: d2e3f4a5b6c7
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: La cuenta no pasó la validación, o el alta chocó con un tope. Los códigos posibles son `account_number_required`, `account_number_invalid_type`, `label_invalid_type`, `invalid_account_length`, `clabe_prefix_not_recognized`, `bank_code_required_for_phone`, `beneficiary_already_registered` y `plan_cap_exceeded`. `account_number_invalid_type` y `label_invalid_type` aparecen cuando el campo llega con un tipo distinto de cadena de texto. Todo error de campo trae `source.pointer`.
          x-translations:
            en:
              description: The account failed validation, or the request hit a cap. The possible codes are `account_number_required`, `account_number_invalid_type`, `label_invalid_type`, `invalid_account_length`, `clabe_prefix_not_recognized`, `bank_code_required_for_phone`, `beneficiary_already_registered` and `plan_cap_exceeded`. `account_number_invalid_type` and `label_invalid_type` fire when the field arrives as a type other than a string. Every field-level error carries `source.pointer`.
              examples:
                phone_missing_bank_code:
                  value:
                    errors:
                      - detail: A `bank_code` is required to register a phone-type beneficiary.
                clabe_unknown_prefix:
                  value:
                    errors:
                      - detail: The CLABE prefix does not match any SPEI participant.
                duplicate:
                  value:
                    errors:
                      - detail: A beneficiary with that account number already exists.
                plan_cap:
                  value:
                    errors:
                      - detail: The account's plan does not allow more beneficiaries.
                account_number_invalid_type:
                  value:
                    errors:
                      - detail: The account_number field must be a string.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                phone_missing_bank_code:
                  summary: Celular sin bank_code
                  x-translations:
                    en:
                      summary: Phone without bank_code
                  value:
                    errors:
                      - status: '422'
                        code: bank_code_required_for_phone
                        detail: Hace falta un `bank_code` para registrar un beneficiario de tipo celular.
                        source:
                          pointer: /data/attributes/bank_code
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: f6a7b8c9d0e1
                clabe_unknown_prefix:
                  summary: CLABE con prefijo no reconocido
                  x-translations:
                    en:
                      summary: CLABE with unknown prefix
                  value:
                    errors:
                      - status: '422'
                        code: clabe_prefix_not_recognized
                        detail: El prefijo de la CLABE no corresponde a ningún participante del SPEI.
                        source:
                          pointer: /data/attributes/account_number
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: a7b8c9d0e1f2
                duplicate:
                  summary: Cuenta activa ya existe
                  x-translations:
                    en:
                      summary: Active account already exists
                  value:
                    errors:
                      - status: '422'
                        code: beneficiary_already_registered
                        detail: Ya existe un beneficiario con ese número de cuenta.
                        source:
                          pointer: /data/attributes/account_number
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: b8c9d0e1f2a3
                plan_cap:
                  summary: El plan no admite más beneficiarios
                  x-translations:
                    en:
                      summary: The plan allows no further beneficiaries
                  value:
                    errors:
                      - status: '422'
                        code: plan_cap_exceeded
                        detail: El plan de la cuenta de usuario no admite más beneficiarios.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: f4a5b6c7d8e9
                account_number_invalid_type:
                  summary: account_number con tipo inválido
                  x-translations:
                    en:
                      summary: account_number with an invalid type
                  value:
                    errors:
                      - status: '422'
                        code: account_number_invalid_type
                        detail: El campo account_number debe ser una cadena de texto.
                        source:
                          pointer: /data/attributes/account_number
                    meta:
                      version: 1.59.1
                      api_version: v1
                      request_id: a1b2c3d4e5f6
      x-related:
        - GET /v1/beneficiaries
        - GET /v1/public/banks
        - POST /v1/validate-ocr
      x-translations:
        en:
          summary: Create a beneficiary
          description: |-
            Registers a new beneficiary account. The account type is auto-detected
            from the number's length, and which fields the request sends follows from
            it:

            - CLABE (18 digits): `bank_code` is optional, derived from the prefix.
            - Card (16 digits): `bank_code` is optional, derived from the BIN.
            - Phone/DiMo (10 digits): `bank_code` is **mandatory** in the body, since
              the number alone does not identify the banking institution.

            If the account did not exist before, it is registered and the response
            carries HTTP status `201`.

            Registering an account that is already active responds with HTTP status
            `422`, with `beneficiary_already_registered` in the body.

            If it exists but is archived, the registration **reactivates** it and
            responds with HTTP status `200`, with `meta.reactivated=true` in the body.
            If the request carries no `label`, the reactivation keeps the alias the
            account already had; if it carries one, it replaces it.
      security:
        - ApiKeyAuth: []
  /beneficiaries/{id}:
    put:
      x-related:
        - DELETE /v1/beneficiaries/{id}
        - DELETE /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries
        - GET /v1/beneficiaries/export
        - GET /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}/preview
      tags:
        - Beneficiaries
      summary: Actualizar un beneficiario
      description: |-
        Actualiza un beneficiario ya registrado. El cuerpo admite la envoltura JSON:API o un objeto plano, y lo que se envíe determina el alcance del cambio:

        - Solo `label`: Se guarda, sin tocar la cuenta.
        - Un `account_number` nuevo, o el alias antiguo: vuelven a derivarse `account_type`, `bank_code` y `bank_name`. Si el nuevo número es un celular (phone), el cuerpo debe llevar `bank_code`.
        - Solo `bank_code`: se aplica únicamente sobre un beneficiario de tipo celular. En una CLABE o una tarjeta el banco se deriva del número, así que el valor enviado se descarta sin error.

        Un cuerpo sin ningún campo editable responde con un estado HTTP `422` (con `no_valid_fields` en el cuerpo).

        Si el `account_number` nuevo ya está registrado para la misma cuenta de usuario, activo o archivado, responde con un estado HTTP `422` (con `beneficiary_already_registered` en el cuerpo) y el beneficiario no cambia.
      operationId: updateBeneficiary
      externalDocs:
        url: https://docs.veriko.mx/how-to/manage-beneficiaries
        description: Create, update, and export beneficiaries
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          example: 42
          description: ID numérico del beneficiario.
          x-translations:
            en:
              description: Numeric beneficiary ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateBeneficiaryRequest'
            examples:
              label_only:
                summary: Solo actualizar etiqueta
                x-translations:
                  en:
                    summary: Update label only
                value:
                  label: Proveedor XYZ actualizado
              new_account_clabe:
                summary: Reemplazar cuenta por CLABE
                x-translations:
                  en:
                    summary: Replace account with a CLABE
                value:
                  account_number: '014180012345678905'
                  label: Nueva cuenta
              phone_bank_code:
                summary: Reasignar banco de cuenta celular DiMo
                x-translations:
                  en:
                    summary: Reassign bank for DiMo phone account
                value:
                  bank_code: '40021'
                  label: Cuenta DiMo HSBC
              jsonapi_envelope:
                summary: Envoltorio JSON:API
                x-translations:
                  en:
                    summary: JSON:API envelope
                value:
                  data:
                    attributes:
                      label: Proveedor XYZ actualizado
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X PUT 'https://api.veriko.mx/v1/beneficiaries/{id}' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json' \
              -d '{
                "data": {
                  "attributes": {
                    "label": "Proveedor XYZ actualizado"
                  }
                }
              }'
      responses:
        '200':
          description: Beneficiario actualizado.
          x-translations:
            en:
              description: Beneficiary updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdateBeneficiaryResponse'
              example:
                data:
                  type: beneficiary
                  id: '50'
                  attributes:
                    account_number: '5512345678'
                    account_type: phone
                    clabe: '5512345678'
                    bank_code: '40021'
                    bank_name: HSBC
                    label: Cuenta DiMo HSBC
                    status: active
                    created_at: '2026-04-01T10:15:00Z'
                meta:
                  version: 1.47.0
                  api_version: v1
                  request_id: c9d0e1f2a3b4c5d6e7f8a9b0
        '400':
          description: El cuerpo de la petición está vacío o no es JSON válido.
          x-translations:
            en:
              description: The request body is empty or is not valid JSON.
              examples:
                body_empty:
                  value:
                    errors:
                      - detail: The request body is empty.
                invalid_json:
                  value:
                    errors:
                      - detail: The body is not valid JSON.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                body_empty:
                  summary: Cuerpo vacío
                  x-translations:
                    en:
                      summary: Empty body
                  value:
                    errors:
                      - status: '400'
                        code: body_empty
                        detail: El cuerpo de la petición está vacío.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: a5b6c7d8e9f0
                invalid_json:
                  summary: JSON mal formado
                  x-translations:
                    en:
                      summary: Malformed JSON
                  value:
                    errors:
                      - status: '400'
                        code: invalid_json
                        detail: El cuerpo no es JSON válido.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: b6c7d8e9f0a1
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: El cuerpo no pasó la validación. Los códigos posibles son `no_valid_fields`, `invalid_account_length`, `clabe_prefix_not_recognized`, `bank_code_required_for_phone`, `beneficiary_already_registered`, `account_number_invalid_type` y `label_invalid_type`. Las dos últimas aparecen cuando `account_number` o `label` llegan con un tipo distinto de cadena de texto (por ejemplo, un número o un arreglo). Todo error de campo trae `source.pointer`.
          x-translations:
            en:
              description: The body failed validation. Possible codes are `no_valid_fields`, `invalid_account_length`, `clabe_prefix_not_recognized`, `bank_code_required_for_phone`, `beneficiary_already_registered`, `account_number_invalid_type`, and `label_invalid_type`. The last two fire when `account_number` or `label` arrive as a type other than a string (a number or an array, for instance). Every field-level error carries `source.pointer`.
              examples:
                no_fields:
                  value:
                    errors:
                      - detail: The body carries no editable field.
                label_invalid_type:
                  value:
                    errors:
                      - detail: The label field must be a string.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                no_fields:
                  summary: Cuerpo sin campos editables
                  x-translations:
                    en:
                      summary: Body has no editable fields
                  value:
                    errors:
                      - status: '422'
                        code: no_valid_fields
                        detail: El cuerpo no trae ningún campo editable.
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: d0e1f2a3b4c5
                label_invalid_type:
                  summary: label con tipo inválido
                  x-translations:
                    en:
                      summary: label with an invalid type
                  value:
                    errors:
                      - status: '422'
                        code: label_invalid_type
                        detail: El campo label debe ser una cadena de texto.
                        source:
                          pointer: /data/attributes/label
                    meta:
                      version: 1.59.1
                      api_version: v1
                      request_id: e1f2a3b4c5d6
      x-translations:
        en:
          summary: Update a beneficiary
          description: |-
            Updates an already registered beneficiary. The body accepts the JSON:API
            envelope or a flat object, and what is sent determines the scope of the
            change:

            - `label` only: it is stored, leaving the account untouched.
            - A new `account_number`, or the legacy alias: `account_type`,
              `bank_code` and `bank_name` are derived again. If the new number is a
              phone, the body must carry `bank_code`.
            - `bank_code` only: it applies solely to a phone-type beneficiary. On a
              CLABE or a card the bank is derived from the number, so the value sent
              is discarded without an error.

            A body with no editable field responds with HTTP status `422`, with
            `no_valid_fields` in the body.

            If the new `account_number` is already registered under the same user
            account, active or archived, it responds with HTTP status `422`, with
            `beneficiary_already_registered` in the body, and the beneficiary does
            not change.
      security:
        - ApiKeyAuth: []
    delete:
      x-related:
        - PUT /v1/beneficiaries/{id}
        - DELETE /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries
        - GET /v1/beneficiaries/export
        - GET /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}/preview
      tags:
        - Beneficiaries
      summary: Archivar un beneficiario
      description: |-
        Archiva la cuenta beneficiaria. El registro no se borra: sale de la lista activa y visible, las validaciones históricas que lo referencian siguen intactas, de modo que archivar un beneficiario no afecta su historial de validaciones.

        La operación es reversible. Un alta posterior con el mismo `account_number` reactiva la cuenta y responde con un estado HTTP `200` (con `meta.reactivated=true` en el cuerpo).

        Los beneficiarios archivados se consultan con `GET /v1/beneficiaries?with_archived=1`.
      operationId: deleteBeneficiary
      externalDocs:
        url: https://docs.veriko.mx/how-to/manage-beneficiaries
        description: Create, update, and export beneficiaries
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          example: 42
          description: ID numérico del beneficiario.
          x-translations:
            en:
              description: Numeric beneficiary ID.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X DELETE 'https://api.veriko.mx/v1/beneficiaries/{id}' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '204':
          description: Beneficiario archivado. Sin cuerpo de respuesta. Idempotente — un archive concurrente responde con un estado HTTP `204` sin emitir duplicados.
          x-translations:
            en:
              description: Beneficiary archived. No response body. Idempotent — a concurrent archive returns `204` without emitting a duplicate audit entry.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
      x-translations:
        en:
          summary: Archive a beneficiary
          description: |-
            Archives the beneficiary account. The record is not deleted: it
            leaves the active, visible list, and the historical validations
            referencing it stay intact, so archiving a beneficiary does not
            affect its validation history.

            The operation is reversible. A later registration with the same
            `account_number` reactivates the account and responds with HTTP
            status `200`, with `meta.reactivated=true` in the body.

            Archived beneficiaries are listed with `GET
            /v1/beneficiaries?with_archived=1`.
      security:
        - ApiKeyAuth: []
  /beneficiaries/lookup:
    get:
      x-related:
        - DELETE /v1/beneficiaries/{id}
        - DELETE /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries
        - GET /v1/beneficiaries/export
        - GET /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}/preview
      tags:
        - Beneficiaries
      summary: Buscar cuenta en lista blanca propia
      description: |-
        Busca un número de cuenta exacto dentro de la lista de beneficiarios del usuario autenticado. Cuando existe, la respuesta trae los datos del banco.

        La búsqueda está aislada por cuenta: un número que no esté en la lista propia responde con un estado HTTP `404`.
      operationId: lookupBeneficiaryAccount
      parameters:
        - name: account
          in: query
          required: true
          schema:
            type: string
            pattern: ^\d{10,19}$
          example: '012180004412345678'
          description: 'Número de cuenta completo: 10 dígitos (celular), 16 (tarjeta) o 18 (CLABE). Los separadores, espacios y guiones se eliminan antes de buscar.'
          x-translations:
            en:
              description: 'Full account number: 10 digits (phone), 16 (card) or 18 (CLABE). Separators are stripped before lookup.'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/beneficiaries/lookup' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Cuenta encontrada en la lista del usuario; devuelve metadatos del banco.
          x-translations:
            en:
              description: Account found in the user's list; returns bank metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LookupBeneficiaryAccountResponse'
              examples:
                clabe_found:
                  summary: CLABE encontrada en la lista del usuario
                  x-translations:
                    en:
                      summary: CLABE found in user list
                  value:
                    data:
                      type: beneficiary_lookup
                      attributes:
                        account_number: '012180004412345678'
                        account_type: clabe
                        bank_code: '40012'
                        bank_name: BBVA MEXICO
                        label: Proveedor ABC
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: a1b2c3d4e5f6
                card_found:
                  summary: Tarjeta encontrada en la lista
                  x-translations:
                    en:
                      summary: Card found in user list
                  value:
                    data:
                      type: beneficiary_lookup
                      attributes:
                        account_number: '4111111111111111'
                        account_type: card
                        bank_code: '40012'
                        bank_name: BBVA MEXICO
                        label: Tarjeta BBVA nómina
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: a2b3c4d5e6f7
                phone_found:
                  summary: Celular DiMo encontrado en la lista
                  x-translations:
                    en:
                      summary: DiMo phone found in user list
                  value:
                    data:
                      type: beneficiary_lookup
                      attributes:
                        account_number: '5512345678'
                        account_type: phone
                        bank_code: '40012'
                        bank_name: BBVA MEXICO
                        label: Cuenta DiMo Banamex
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: b2c3d4e5f6a7
        '400':
          description: Parámetro `account` ausente (`query_param_account_required`) o formato inválido (`invalid_account_format`).
          x-translations:
            en:
              description: '`account` parameter missing (`query_param_account_required`) or invalid format (`invalid_account_format`).'
              examples:
                missing:
                  value:
                    errors:
                      - detail: The `account` query parameter is required.
                bad_format:
                  value:
                    errors:
                      - detail: The account format is not recognized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missing:
                  summary: Parámetro account vacío
                  x-translations:
                    en:
                      summary: account parameter empty
                  value:
                    errors:
                      - status: '400'
                        code: query_param_account_required
                        detail: Falta el parámetro `account` en la consulta.
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: c3d4e5f6a7b8
                bad_format:
                  summary: Longitud fuera de rango / no numérico
                  x-translations:
                    en:
                      summary: Out-of-range length / non-numeric input
                  value:
                    errors:
                      - status: '400'
                        code: invalid_account_format
                        detail: No reconocemos el formato de esa cuenta.
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: d4e5f6a7b8c9
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Cuenta no encontrada en la lista de beneficiarios del usuario.
          x-translations:
            en:
              description: Account not found in the user's beneficiary list.
              example:
                errors:
                  - detail: No beneficiary found for this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - status: '404'
                    code: beneficiary_not_found_for_account
                    detail: No encontramos ningún beneficiario para esa cuenta.
                meta:
                  version: 1.47.0
                  api_version: v1
                  request_id: b2c3d4e5f6a7
      x-translations:
        en:
          summary: Look up account in own whitelist
          description: |-
            Looks up an exact account number within the beneficiary list of the
            authenticated account. When it exists, the response carries the bank
            details.

            The lookup is isolated per account: a number absent from one's own
            list responds with HTTP status `404`.
      security:
        - ApiKeyAuth: []
  /beneficiaries/imports/template:
    get:
      x-related:
        - DELETE /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}/preview
        - PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}
        - POST /v1/beneficiaries/imports
        - POST /v1/beneficiaries/imports/{id}/commit
      tags:
        - Beneficiaries
      summary: Descargar la plantilla de importación de beneficiarios
      description: |-
        Devuelve un **archivo plantilla para la importación de beneficiarios** en el formato solicitado, con encabezados en español o inglés y filas de ejemplo para CLABE, tarjeta y celular/DiMo. Sirve de punto de partida para {% concept slug="bulk-imports" %}la importación masiva{% /concept %}.

        El idioma de los encabezados se negocia con la cabecera `Accept-Language`; de forma predeterminada se entregan en español, acepta después encabezados en ambos idiomas de forma insensible a acentos.
      operationId: downloadBeneficiaryImportTemplate
      externalDocs:
        url: https://docs.veriko.mx/how-to/beneficiary-import
        description: Asistente de importación de beneficiarios por lotes
      x-translations:
        en:
          summary: Download bulk import template
          description: |-
            Returns a template file in the requested format, with headers in
            Spanish or English and example rows for CLABE, card and DiMo phone.
            Serves as a starting point for {% concept slug="bulk-imports" %}the
            bulk import{% /concept %}.

            Header language is negotiated with the `Accept-Language` header;
            Spanish is served by default. The import parser then accepts headers
            in both languages accent-insensitively.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: format
          in: query
          required: true
          schema:
            type: string
            enum:
              - csv
              - xlsx
              - xls
              - txt
              - json
          example: csv
          description: Formato de la plantilla que se descarga. `csv`, `xls`, `xlsx` y `txt` contienen las columnas esperadas listas para rellenar; `json` contiene la misma forma para generar el archivo desde un programa.
          x-translations:
            en:
              description: Format of the downloaded template. `csv`, `xls`, `xlsx`, and `txt` carry the expected columns ready to fill in; `json` carries the same shape for generating the file from a program.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/beneficiaries/imports/template' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Archivo plantilla generado. El `Content-Type` varía por formato (`text/csv`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`, `application/vnd.ms-excel`, `text/plain`, `application/json`).
          x-translations:
            en:
              description: Generated template file. `Content-Type` varies by format (`text/csv`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`, `application/vnd.ms-excel`, `text/plain`, `application/json`).
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="beneficiarios_plantilla.csv"
              description: Sugerencia de nombre de archivo para descarga.
              x-translations:
                en:
                  description: Suggested download filename.
            Content-Type:
              schema:
                type: string
                example: text/csv; charset=UTF-8
              description: Varía según `?format` (CSV / XLSX / XLS / TXT / JSON).
              x-translations:
                en:
                  description: Varies by `?format` (CSV / XLSX / XLS / TXT / JSON).
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          description: '`invalid_format`: El formato solicitado no está soportado.'
          x-translations:
            en:
              description: '`invalid_format`: The requested format is not supported.'
              example:
                errors:
                  - detail: 'Format must be one of: csv, xlsx, xls, txt, json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - status: '422'
                    code: invalid_format
                    detail: 'El formato debe ser uno de: csv, xlsx, xls, txt, json.'
                meta:
                  version: 1.47.0
                  api_version: v1
                  request_id: c5d6e7f8a9b0
  /beneficiaries/imports:
    post:
      tags:
        - Beneficiaries
      summary: Iniciar importación masiva de beneficiarios
      description: |-
        Sube un archivo (CSV, XLS, XLSX, TXT o PDF) de hasta 20 MB y abre con él un trabajo de importación.
        Las hojas XLSX/XLS admiten hasta 10 000 filas y 64 columnas. Las fórmulas XLSX se leen como texto, sin ejecutarlas. Los PDF admiten hasta 50 páginas y limitan a 8 MiB la descompresión de cada flujo.

        El modo de lectura decide cuánto trabajo hace el archivo y cuánto el servidor:

        - `template`: el archivo respeta los encabezados canónicos, que se obtienen de `/v1/beneficiaries/imports/template`. El banco se deriva del prefijo de la CLABE o del BIN de la tarjeta.
        - `free`: cualquier formato. Las cuentas se extraen del contenido, con apoyo de un LLM cuando el archivo no tiene estructura reconocible.

        **La importación no persiste nada por sí sola.** Queda a la espera de una confirmación explícita. La secuencia completa son cuatro pasos:

        1. Esta llamada (`POST /v1/beneficiaries/imports`) devuelve el identificador del trabajo.
        2. `GET /v1/beneficiaries/imports/{id}` informa del avance hasta `preview_ready` o `failed`.
        3. `GET /v1/beneficiaries/imports/{id}/preview` muestra las filas extraídas, y `PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}` corrige las que hagan falta.
        4. `POST /v1/beneficiaries/imports/{id}/commit` persiste el resultado.

        El sondeo del paso 2 y su cadencia están descritos en {% concept slug="async-validations" %}las operaciones asíncronas{% /concept %}.

        {% callout type="warning" %}
        Solo puede haber un trabajo abierto a la vez: una segunda subida mientras la anterior sigue viva responde con un estado HTTP `422` (con `import_already_in_flight` en el cuerpo).

        {% /callout %}
      operationId: createBeneficiaryImport
      externalDocs:
        url: https://docs.veriko.mx/how-to/beneficiary-import
        description: 'How-to: importación masiva de beneficiarios'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/CreateBeneficiaryImportRequest'
            encoding:
              file:
                contentType: text/csv, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, text/plain, application/pdf
              parse_mode:
                contentType: text/plain
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/beneficiaries/imports' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '202':
          description: Trabajo de importación creado y esperando procesamiento.
          x-translations:
            en:
              description: Import job created and enqueued for processing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateBeneficiaryImportResponse'
              example:
                data:
                  type: beneficiary_import
                  id: '42'
                  attributes:
                    status: pending
                meta:
                  version: 1.47.0
                  api_version: v1
                  request_id: e1f2a3b4c5d6
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          description: 'Falló la validación o la pre-condición. Códigos posibles: `file_required`, `file_too_large`, `unsupported_format` e `import_already_in_flight`.'
          x-translations:
            en:
              description: 'Validation or pre-condition failed. Possible codes: `file_required`, `file_too_large`, `unsupported_format`, and `import_already_in_flight`.'
              example:
                errors:
                  - detail: A `file` part is required in the multipart body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - status: '422'
                    code: file_required
                    detail: Falta la parte `file` en el cuerpo multiparte.
                meta:
                  version: 1.47.0
                  api_version: v1
                  request_id: f2a3b4c5d6e7
      x-related:
        - GET /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}/preview
        - POST /v1/beneficiaries/imports/{id}/commit
        - DELETE /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/template
      x-translations:
        en:
          summary: Start a bulk beneficiary import
          description: |-
            Uploads a file — CSV, XLS, XLSX, TXT, or PDF, up to 20 MB — and opens an
            import job with it. XLSX/XLS sheets allow up to 10,000 rows and 64 columns.
            XLSX formulas are read as text without evaluation. PDFs allow up to 50
            pages and cap decoded streams at 8 MiB. The reading mode decides how much work the file does
            and how much the server does:

            - `template`: the file follows the canonical headers, obtained from
              `/v1/beneficiaries/imports/template`. The bank is derived from the CLABE
              prefix or the card BIN.
            - `free`: any format. Accounts are extracted from the content, with a
              language model stepping in when the file has no recognisable structure.

            **The import persists nothing on its own.** It waits for an explicit
            confirmation. The full sequence is four steps:

            1. This call (`POST /v1/beneficiaries/imports`) returns the job identifier.
            2. `GET /v1/beneficiaries/imports/{id}` reports progress until
               `preview_ready` or `failed`.
            3. `GET /v1/beneficiaries/imports/{id}/preview` shows the extracted rows,
               and `PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}` corrects the
               ones that need it.
            4. `POST /v1/beneficiaries/imports/{id}/commit` persists the result.

            The polling in step 2 and its cadence are described in
            {% concept slug="async-validations" %}asynchronous operations{% /concept %}.

            {% callout type="warning" %}
            Only one job may be open at a time: a second upload while the previous one
            is still alive responds with HTTP status `422`, with
            `import_already_in_flight` in the body.
            {% /callout %}
      security:
        - ApiKeyAuth: []
  /beneficiaries/imports/{id}:
    get:
      x-related:
        - DELETE /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}/preview
        - PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}
        - POST /v1/beneficiaries/imports/{id}/commit
        - GET /v1/beneficiaries/imports/template
        - POST /v1/beneficiaries/imports
      tags:
        - Beneficiaries
      summary: Consultar el estado de una importación
      description: |-
        Devuelve el estado de un trabajo de importación y sus contadores por grupo: `valid`, `correctable`, `fatal`, `duplicate_account` (sumados en `duplicate_count`) y `committed`/`skipped` tras la confirmación.

        El endpoint admite sondeo cada \~2s hasta que `status` sea `preview_ready`, `completed`, `failed` o `cancelled`.
      operationId: getBeneficiaryImport
      externalDocs:
        url: https://docs.veriko.mx/how-to/beneficiary-import
        description: Asistente de importación de beneficiarios por lotes
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          example: 42
          description: ID numérico del trabajo de importación.
          x-translations:
            en:
              description: Numeric ID of the import job.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/beneficiaries/imports/{id}' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Estado actual de la importación con contadores por grupo.
          x-translations:
            en:
              description: Current job status with per-bucket counters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeneficiaryImportJobResponse'
              examples:
                preview_ready:
                  summary: Job listo para revisar (preview_ready)
                  x-translations:
                    en:
                      summary: Job ready for review (preview_ready)
                  value:
                    data:
                      type: beneficiary_import
                      id: '42'
                      attributes:
                        status: preview_ready
                        file_format: csv
                        parse_mode: template
                        total_rows: 150
                        valid_count: 120
                        correctable_count: 20
                        fatal_count: 5
                        duplicate_count: 5
                        committed_count: 0
                        skipped_count: 0
                        llm_invoked: false
                        error_code: null
                        error_summary: null
                        created_at: '2026-05-16T10:00:00Z'
                        parsed_at: '2026-05-16T10:00:08Z'
                        committed_at: null
                        completed_at: null
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: d6e7f8a9b0c1
                committed:
                  summary: Commit finalizado (completed)
                  x-translations:
                    en:
                      summary: Commit finished (completed)
                  value:
                    data:
                      type: beneficiary_import
                      id: '42'
                      attributes:
                        status: completed
                        file_format: csv
                        parse_mode: template
                        total_rows: 150
                        valid_count: 120
                        correctable_count: 20
                        fatal_count: 5
                        duplicate_count: 5
                        committed_count: 140
                        skipped_count: 10
                        llm_invoked: false
                        error_code: null
                        error_summary: null
                        created_at: '2026-05-16T10:00:00Z'
                        parsed_at: '2026-05-16T10:00:08Z'
                        committed_at: '2026-05-16T10:05:00Z'
                        completed_at: '2026-05-16T10:05:15Z'
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: e7f8a9b0c1d2
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
      x-translations:
        en:
          summary: Get import job status
          description: |-
            Returns the status of an import job and its per-bucket counters:
            `valid`, `correctable`, `fatal`, `duplicate_account` (summed in
            `duplicate_count`) and `committed`/`skipped` after the commit.

            The endpoint accepts polling every \~2s until `status` is
            `preview_ready`, `completed`, `failed` or `cancelled`.
      security:
        - ApiKeyAuth: []
    delete:
      x-related:
        - GET /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}/preview
        - PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}
        - POST /v1/beneficiaries/imports/{id}/commit
        - GET /v1/beneficiaries/imports/template
        - POST /v1/beneficiaries/imports
      tags:
        - Beneficiaries
      summary: Cancelar una importación en curso
      description: |-
        Cancela un trabajo de importación que aún no se ha confirmado (en estado `pending`, `parsing` o `preview_ready`).

        Para importaciones en estados terminales (`completed`, `failed`, `cancelled`) o ya en `committing` responde con un estado HTTP `404`.

        Las cuentas ya persistidas por una confirmación previo deben archivarse individualmente con `DELETE /v1/beneficiaries/{id}`.
      operationId: cancelBeneficiaryImport
      externalDocs:
        url: https://docs.veriko.mx/how-to/beneficiary-import
        description: Asistente de importación de beneficiarios por lotes
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          example: 42
          description: ID numérico del trabajo de importación.
          x-translations:
            en:
              description: Numeric ID of the import job.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X DELETE 'https://api.veriko.mx/v1/beneficiaries/imports/{id}' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '204':
          description: Trabajo de importación cancelado. Sin cuerpo.
          x-translations:
            en:
              description: Import job cancelled. No body.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Trabajo de importación no existe o está en estado terminal.
          x-translations:
            en:
              description: Job does not exist or is in a terminal state.
              example:
                errors:
                  - detail: Import job not found or already terminal.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - status: '404'
                    code: not_found
                    detail: El lote no existe o ya llegó a un estado final.
                meta:
                  version: 1.47.0
                  api_version: v1
                  request_id: f8a9b0c1d2e3
      x-translations:
        en:
          summary: Cancel an import job
          description: |-
            Cancels an import job that has not been confirmed yet (`pending`,
            `parsing` or `preview_ready`).

            For jobs in terminal states (`completed`, `failed`, `cancelled`) or
            already `committing` returns `404`.

            Accounts already persisted by a previous commit must be archived
            individually with `DELETE /v1/beneficiaries/{id}`.
      security:
        - ApiKeyAuth: []
  /beneficiaries/imports/{id}/preview:
    get:
      x-related:
        - DELETE /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}
        - PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}
        - POST /v1/beneficiaries/imports/{id}/commit
        - GET /v1/beneficiaries/imports/template
        - POST /v1/beneficiaries/imports
      tags:
        - Beneficiaries
      summary: Previsualizar las filas extraídas de la importación
      description: |-
        Devuelve las filas extraídas del archivo en un trabajo de importación (paginadas y filtrables por grupo).

        Disponible solo cuando la importación está en `preview_ready` o estados posteriores con `total_rows>0`.

        {% callout type="warning" %}
        En este endpoint los datos de CLABEs, tarjetas o celulares NO son enmascarados. Se presume que el usuario solicitante de la importación ya tiene en su poder dichos datos, por lo que no hay motivos para tratarlos bajo nuestra política de {% concept slug="pii-masking" %}enmascarado de datos{% /concept %}

        {% /callout %}
      operationId: getBeneficiaryImportPreview
      externalDocs:
        url: https://docs.veriko.mx/how-to/beneficiary-import
        description: Asistente de importación de beneficiarios por lotes
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          example: 42
          description: ID numérico del trabajo de importación.
          x-translations:
            en:
              description: Numeric ID of the import job.
        - name: buckets
          in: query
          required: false
          schema:
            type: array
            items:
              type: string
              enum:
                - valid
                - correctable
                - fatal
                - duplicate_account
                - duplicate_alias
            example:
              - valid
              - correctable
          example:
            - valid
            - correctable
          description: 'Qué filas incluir, según cómo quedaron al leer el archivo — `valid`: Se persiste tal cual; `correctable`: Tiene un problema que la plataforma sabe arreglar sola; `fatal`: No se puede aprovechar y se descarta. `duplicate_account` y `duplicate_alias` son filas que chocan con un beneficiario que ya existe (por número o por nombre); por número se descarta y por nombre se guarda igual. Sin este filtro se devuelven todas.'
          x-translations:
            en:
              description: |-
                Which rows to include, based on how they came out of the file.
                `valid` goes in as-is. `correctable` has a problem the platform can fix on its own — a bank inferred from the account number, say — and goes in too. `fatal` cannot be used and is dropped. `duplicate_account` and `duplicate_alias` clash with an existing beneficiary, by number or by name; the first is dropped and the second is kept anyway.
                Without this filter every row comes back.
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          example: 1
          description: Número de página (1-based).
          x-translations:
            en:
              description: Page number (1-based).
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          example: 25
          description: Filas por página (1–100).
          x-translations:
            en:
              description: Rows per page (1–100).
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/beneficiaries/imports/{id}/preview' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Filas paginadas de la vista previa con metadatos de paginación y instantánea de la importación.
          x-translations:
            en:
              description: Paginated preview rows with pagination metadata and job snapshot.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeneficiaryImportPreviewResponse'
              examples:
                page_1:
                  summary: Primera página, todos los buckets
                  x-translations:
                    en:
                      summary: First page, all buckets
                  value:
                    data:
                      - type: beneficiary_import_row
                        id: '101'
                        attributes:
                          row_index: 0
                          status: valid
                          parsed_account: '012180004412345678'
                          parsed_account_type: clabe
                          parsed_bank_code: '40012'
                          parsed_bank_name: BBVA MEXICO
                          parsed_label: Proveedor ABC
                          error_codes: []
                          corrections_applied: {}
                          user_overrides: {}
                          raw_preview:
                            Alias: Proveedor ABC
                            Cuenta: '012180004412345678'
                          created_beneficiary_id: null
                      - type: beneficiary_import_row
                        id: '102'
                        attributes:
                          row_index: 1
                          status: correctable
                          parsed_account: '5512345678'
                          parsed_account_type: phone
                          parsed_bank_code: null
                          parsed_bank_name: null
                          parsed_label: null
                          error_codes:
                            - bank_code_required_for_phone
                          corrections_applied:
                            alias_auto_assigned: Beneficiario 002
                          user_overrides: {}
                          raw_preview:
                            Alias: ''
                            Cuenta: '5512345678'
                          created_beneficiary_id: null
                    meta:
                      pagination:
                        page: 1
                        per_page: 25
                        total: 150
                        total_pages: 6
                      job:
                        type: beneficiary_import
                        id: '42'
                        attributes:
                          status: preview_ready
                          file_format: csv
                          parse_mode: template
                          total_rows: 150
                          valid_count: 120
                          correctable_count: 20
                          fatal_count: 5
                          duplicate_count: 5
                          committed_count: 0
                          skipped_count: 0
                          llm_invoked: false
                          error_code: null
                          error_summary: null
                          created_at: '2026-05-16T10:00:00Z'
                          parsed_at: '2026-05-16T10:00:08Z'
                          committed_at: null
                          completed_at: null
                      version: 1.47.0
                      api_version: v1
                      request_id: a9b0c1d2e3f4
                only_fatal:
                  summary: Filtrado solo a filas fatales
                  x-translations:
                    en:
                      summary: Filtered to fatal rows only
                  value:
                    data:
                      - type: beneficiary_import_row
                        id: '199'
                        attributes:
                          row_index: 98
                          status: fatal
                          parsed_account: '99999'
                          parsed_account_type: null
                          parsed_bank_code: null
                          parsed_bank_name: null
                          parsed_label: Cuenta corta
                          error_codes:
                            - invalid_account_length
                          corrections_applied: {}
                          user_overrides: {}
                          raw_preview:
                            Alias: Cuenta corta
                            Cuenta: '99999'
                          created_beneficiary_id: null
                    meta:
                      pagination:
                        page: 1
                        per_page: 25
                        total: 5
                        total_pages: 1
                      job:
                        type: beneficiary_import
                        id: '42'
                        attributes:
                          status: preview_ready
                          file_format: csv
                          parse_mode: template
                          total_rows: 150
                          valid_count: 120
                          correctable_count: 20
                          fatal_count: 5
                          duplicate_count: 5
                          committed_count: 0
                          skipped_count: 0
                          llm_invoked: false
                          error_code: null
                          error_summary: null
                          created_at: '2026-05-16T10:00:00Z'
                          parsed_at: '2026-05-16T10:00:08Z'
                          committed_at: null
                          completed_at: null
                      version: 1.47.0
                      api_version: v1
                      request_id: b0c1d2e3f4a5
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
      x-translations:
        en:
          summary: Preview extracted rows from import
          description: |-
            Returns the rows extracted from the file in an import job, paginated
            and filterable by bucket.

            Available only when the import is in `preview_ready` or later states
            with `total_rows>0`.

            {% callout type="warning" %}
            On this endpoint the CLABE, card and phone numbers are NOT masked.
            The user who requested the import already holds that data, so there
            is no reason to treat it under our {% concept slug="pii-masking"
            %}data masking{% /concept %} policy.
            {% /callout %}
      security:
        - ApiKeyAuth: []
  /beneficiaries/imports/{id}/rows/{row_id}:
    delete:
      tags:
        - Beneficiaries
      summary: Quitar una fila de la vista previa
      description: |-
        Elimina una fila de un trabajo de importación propio antes de confirmarlo.

        La fila deja de formar parte del trabajo de importación. Los contadores y
        duplicados restantes se recalculan; las cuentas ya guardadas no cambian.

        {% callout type="info" %}
        Solo admite trabajos de importación en estado `preview_ready`. En otros
        estados responde con un estado HTTP `422` (con `job_not_editable` en el cuerpo).

        {% /callout %}
      operationId: deleteBeneficiaryImportRow
      externalDocs:
        url: https://docs.veriko.mx/how-to/beneficiary-import
        description: Asistente de importación de beneficiarios
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          example: 42
          description: Identificador del trabajo de importación.
          x-translations:
            en:
              description: Import job identifier.
        - name: row_id
          in: path
          required: true
          schema:
            type: integer
          example: 102
          description: Identificador de la fila del trabajo de importación.
          x-translations:
            en:
              description: Import-job row identifier.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X DELETE 'https://api.veriko.mx/v1/beneficiaries/imports/{id}/rows/{row_id}' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '204':
          description: Fila retirada del trabajo de importación. Sin cuerpo.
          x-translations:
            en:
              description: Row removed from the import job. No body.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Trabajo de importación no editable (`job_not_editable`).
          x-translations:
            en:
              description: Import job is not editable (`job_not_editable`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-related:
        - GET /v1/beneficiaries/imports/{id}/preview
        - POST /v1/beneficiaries/imports/{id}/commit
      x-translations:
        en:
          summary: Remove a preview row
          description: |-
            Removes a row from an owned import job before confirmation.

            The row is no longer part of the import job. The remaining counters and
            duplicates are recalculated; previously saved accounts are unchanged.

            {% callout type="info" %}
            Only accepts import jobs in `preview_ready` status. Other statuses
            respond with HTTP status `422` (with `job_not_editable` in the body).

            {% /callout %}
      security:
        - ApiKeyAuth: []
    patch:
      x-related:
        - DELETE /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}/preview
        - POST /v1/beneficiaries/imports/{id}/commit
        - GET /v1/beneficiaries/imports/template
        - POST /v1/beneficiaries/imports
      tags:
        - Beneficiaries
      summary: Editar una fila de la vista previa
      description: |-
        Permite corregir una fila de la vista previa antes de confirmar la persistencia de un trabajo de importación. Acepta envoltorio JSON:API o un objeto plano. Todos los atributos son opcionales; solo se sobreescriben los presentes.

        Campos editables:

        - `parsed_account`
        - `parsed_label`
        - `parsed_account_type`
        - `parsed_bank_code`
        - `parsed_bank_name`

        Tras persistir la corrección el servidor re-procesa la fila (re-deriva banco a partir del prefijo CLABE / BIN nuevos) para que los contadores de la importación y la columna Banco de la vista previa queden sincronizados sin esperar a la confirmación.

        {% callout type="info" %}
        Solo válido cuando el trabajo de importación está en estado `preview_ready`: En otros estados responde con un estado HTTP `422` (con `job_not_editable` en el cuerpo).

        {% /callout %}
      operationId: patchBeneficiaryImportRow
      externalDocs:
        url: https://docs.veriko.mx/how-to/beneficiary-import
        description: Asistente de importación de beneficiarios por lotes
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          example: 42
          description: ID numérico del trabajo de importación.
          x-translations:
            en:
              description: Numeric ID of the import job.
        - name: row_id
          in: path
          required: true
          schema:
            type: integer
          example: 102
          description: ID numérico de la fila dentro de la importación.
          x-translations:
            en:
              description: Numeric row ID within the job.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PatchBeneficiaryImportRowRequest'
            examples:
              jsonapi:
                summary: Envoltorio JSON:API
                x-translations:
                  en:
                    summary: JSON:API envelope
                value:
                  data:
                    attributes:
                      parsed_label: Mamá
                      parsed_account_type: clabe
              fix_phone:
                summary: Asignar bank_code a fila celular correctable
                x-translations:
                  en:
                    summary: Assign bank_code to a correctable phone row
                value:
                  parsed_account_type: phone
                  parsed_bank_code: '40012'
                  parsed_bank_name: BBVA MEXICO
              fix_account:
                summary: Corregir typo de CLABE
                x-translations:
                  en:
                    summary: Fix CLABE typo
                value:
                  parsed_account: 0121-8000-4412-3456-78
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X PATCH 'https://api.veriko.mx/v1/beneficiaries/imports/{id}/rows/{row_id}' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json' \
              -d '{
                "parsed_account": "0121-8000-4412-3456-78"
              }'
      responses:
        '200':
          description: Fila actualizada con la corrección aplicado y re-procesada.
          x-translations:
            en:
              description: Row updated with the override applied and re-processed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PatchBeneficiaryImportRowResponse'
              example:
                data:
                  type: beneficiary_import_row
                  id: '102'
                  attributes:
                    row_index: 1
                    status: valid
                    parsed_account: '5512345678'
                    parsed_account_type: phone
                    parsed_bank_code: '40012'
                    parsed_bank_name: BBVA MEXICO
                    parsed_label: null
                    error_codes: []
                    corrections_applied:
                      alias_auto_assigned: Beneficiario 002
                    user_overrides:
                      parsed_account_type: phone
                      parsed_bank_code: '40012'
                      parsed_bank_name: BBVA MEXICO
                    raw_preview:
                      Alias: ''
                      Cuenta: '5512345678'
                    created_beneficiary_id: null
                meta:
                  version: 1.47.0
                  api_version: v1
                  request_id: c1d2e3f4a5b6
        '400':
          description: El cuerpo de la petición está vacío o no es JSON válido.
          x-translations:
            en:
              description: The request body is empty or is not valid JSON.
              examples:
                body_empty:
                  value:
                    errors:
                      - detail: The request body is empty.
                invalid_json:
                  value:
                    errors:
                      - detail: The body is not valid JSON.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                body_empty:
                  summary: Cuerpo vacío
                  x-translations:
                    en:
                      summary: Empty body
                  value:
                    errors:
                      - status: '400'
                        code: body_empty
                        detail: El cuerpo de la petición está vacío.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: a5b6c7d8e9f0
                invalid_json:
                  summary: JSON mal formado
                  x-translations:
                    en:
                      summary: Malformed JSON
                  value:
                    errors:
                      - status: '400'
                        code: invalid_json
                        detail: El cuerpo no es JSON válido.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: b6c7d8e9f0a1
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: 'Falló la validación o la importación no es editable. Códigos: `invalid_account` (el número no casa el formato), `invalid_account_type`, `invalid_bank_code`, `no_valid_fields`, `job_not_editable`.'
          x-translations:
            en:
              description: 'Validation failed or job is not editable. Codes: `invalid_account` (the number does not match the expected format), `invalid_account_type`, `invalid_bank_code`, `no_valid_fields`, `job_not_editable`.'
              examples:
                not_editable:
                  value:
                    errors:
                      - detail: Job is not in preview_ready state.
                bad_type:
                  value:
                    errors:
                      - detail: parsed_account_type must be clabe, card, or phone.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                not_editable:
                  summary: Job ya en `committing`
                  x-translations:
                    en:
                      summary: Job already in `committing`
                  value:
                    errors:
                      - status: '422'
                        code: job_not_editable
                        detail: El lote no está en estado `preview_ready`.
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: d2e3f4a5b6c7
                bad_type:
                  summary: parsed_account_type fuera del enum
                  x-translations:
                    en:
                      summary: parsed_account_type outside enum
                  value:
                    errors:
                      - status: '422'
                        code: invalid_account_type
                        detail: '`parsed_account_type` debe ser `clabe`, `card` o `phone`.'
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: e3f4a5b6c7d8
      x-translations:
        en:
          summary: Edit a preview row
          description: |-
            Allows a preview row to be corrected before the persistence of an
            import job is confirmed. Accepts a JSON:API envelope or a flat
            object. Every attribute is optional; only those present are
            overwritten.

            Editable fields:

            - `parsed_account`
            - `parsed_label`
            - `parsed_account_type`
            - `parsed_bank_code`
            - `parsed_bank_name`

            After persisting the correction the server re-processes the row (it
            re-derives the bank from the new CLABE prefix / BIN) so the import
            counters and the preview's Bank column stay in sync without waiting
            for the commit.

            {% callout type="info" %}
            Only valid while the import job is in `preview_ready` state — in
            other states it responds with HTTP status `422`, with
            `job_not_editable` in the body.
            {% /callout %}
      security:
        - ApiKeyAuth: []
  /beneficiaries/imports/{id}/commit:
    post:
      x-related:
        - DELETE /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}
        - GET /v1/beneficiaries/imports/{id}/preview
        - PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}
        - GET /v1/beneficiaries/imports/template
        - POST /v1/beneficiaries/imports
      tags:
        - Beneficiaries
      summary: Confirmar la importación masiva
      description: |-
        Confirma un trabajo de importación y dispara la persistencia de sus filas en `GET /v1/beneficiaries`.

        La operación es asíncrona: el endpoint responde con un estado HTTP `202` y la importación pasa a estado `committing` mientras el trabajador procesa las filas. `GET /v1/beneficiaries/imports/{id}` admite sondeo hasta que el estado sea `completed` o `failed`.

        Reglas de la confirmación:

        - Filas `valid` y `correctable`: Se persisten (con sufijo de alias si hubo colisión de alias). La excepción es un celular sin banco válido (`bank_code_required_for_phone` o `invalid_bank_code`): se corrige eligiendo el banco en la vista previa, y si al confirmar sigue sin banco no se guarda, queda como `fatal` con ese mismo código y cuenta en `skipped_count`.
        - Filas `fatal` y `duplicate_account`: Se omiten.
        - Cuentas previamente archivadas con el mismo número: Se reactivan en lugar de duplicarse.
      operationId: commitBeneficiaryImport
      externalDocs:
        url: https://docs.veriko.mx/how-to/beneficiary-import
        description: Asistente de importación de beneficiarios por lotes
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          example: 42
          description: ID numérico del trabajo de importación.
          x-translations:
            en:
              description: Numeric ID of the import job.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/beneficiaries/imports/{id}/commit' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '202':
          description: Commit encolado al trabajador. La importación pasa a `committing`.
          x-translations:
            en:
              description: Commit enqueued to the worker. The job transitions to `committing`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommitBeneficiaryImportResponse'
              example:
                data:
                  type: beneficiary_import
                  id: '42'
                  attributes:
                    status: committing
                meta:
                  version: 1.47.0
                  api_version: v1
                  request_id: f4a5b6c7d8e9
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Pre-condición de confirmación no cumplida. Los códigos posibles son `job_not_committable` (si el estado no es `preview_ready`), `plan_cap_exceeded` (cuando el tope esté activo) y el genérico `commit_failed`.
          x-translations:
            en:
              description: Commit pre-condition failed. The possible codes are `job_not_committable` (when the state is not `preview_ready`), `plan_cap_exceeded` (when the cap is active) and the generic `commit_failed`.
              example:
                errors:
                  - detail: Commit not allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - status: '422'
                    code: job_not_committable
                    detail: La confirmación no está permitida en este estado.
                meta:
                  version: 1.47.0
                  api_version: v1
                  request_id: a5b6c7d8e9f0
      x-translations:
        en:
          summary: Confirm the bulk import
          description: |-
            Confirms an import job and triggers the persistence of its rows into
            `GET /v1/beneficiaries`. The operation is asynchronous: the endpoint
            responds with HTTP status `202` and the import moves to `committing`
            while the worker processes the rows.
            `GET /v1/beneficiaries/imports/{id}` accepts polling until the state is
            `completed` or `failed`. Commit rules:

            - `valid` and `correctable` rows are persisted, with an alias suffix if
              there was an alias collision. The exception is a phone number without a
              valid bank (`bank_code_required_for_phone` or `invalid_bank_code`): it
              is fixed by choosing the bank in the preview, and if it still has no
              bank when you confirm it is not saved, becomes `fatal` with that same
              code and counts in `skipped_count`.
            - `fatal` and `duplicate_account` rows are skipped.
            - Previously archived accounts with the same number are reactivated
              instead of duplicated.
      security:
        - ApiKeyAuth: []
  /api/usage/export:
    get:
      x-related:
        - GET /v1/api/usage
        - GET /v1/usage/breakdown
        - GET /v1/usage/heatmap
        - GET /v1/usage/history
        - GET /v1/usage/limits
        - GET /v1/usage/summary
      tags:
        - Usage
      summary: Exportar el registro de actividad como CSV o XLSX
      description: |-
        Descarga el registro de actividad API del usuario autenticado.

        El formato se elige con `format` (`csv` de forma predeterminada, o `xlsx`), y
        el rango lo acotan `from` y `to`, las dos inclusive.

        {% callout type="note" %}
        La descarga trae como mucho 100 000 filas. `limit` sólo sirve para bajar ese
        tope, nunca para subirlo.
        {% /callout %}
      operationId: exportApiUsage
      externalDocs:
        url: https://docs.veriko.mx/how-to/monitor-usage
        description: Track API quota and billing-cycle consumption
      parameters:
        - name: format
          in: query
          description: 'Formato de archivo solicitado para descarga. Aceptados: `csv` (predeterminado) o `xlsx`.'
          x-translations:
            en:
              description: 'Requested file format for the download. Accepted: `csv` (default) or `xlsx`.'
          schema:
            type: string
            enum:
              - csv
              - xlsx
            default: csv
          example: csv
        - name: limit
          in: query
          description: 'Tope de filas de la descarga. Sólo sirve para BAJAR el tope predeterminado de 100 000: un valor mayor se ignora.'
          x-translations:
            en:
              description: 'Row cap for the download. It only serves to LOWER the default cap of 100,000: a larger value is ignored.'
          schema:
            type: integer
            minimum: 1
          example: 500
        - name: from
          in: query
          description: Fecha inicial (inclusiva) del rango a exportar, en formato ISO 8601 (YYYY-MM-DD).
          x-translations:
            en:
              description: Start date (inclusive) of the range to export, in ISO 8601 format (YYYY-MM-DD).
          schema:
            type: string
            format: date
            example: '2026-05-01'
          example: '2026-05-01'
        - name: to
          in: query
          description: Fecha final (inclusiva) del rango a exportar, en formato ISO 8601 (YYYY-MM-DD).
          x-translations:
            en:
              description: End date (inclusive) of the range to export, in ISO 8601 format (YYYY-MM-DD).
          schema:
            type: string
            format: date
            example: '2026-05-17'
          example: '2026-05-17'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/api/usage/export' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Archivo CSV (con BOM UTF-8) o XLSX según `format`.
          x-translations:
            en:
              description: CSV file (with UTF-8 BOM) or XLSX based on `format`.
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="actividad_api_2025-01-15.csv"
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
      x-translations:
        en:
          summary: Export API usage
          description: |-
            Downloads the authenticated user's API activity log.

            The format is chosen with `format` (`csv` by default, or `xlsx`), and the
            range is bounded by `from` and `to`, both inclusive.

            {% callout type="note" %}
            The download carries at most 100,000 rows. `limit` only serves to lower
            that cap, never to raise it.
            {% /callout %}
      security:
        - ApiKeyAuth: []
  /api/usage:
    get:
      x-related:
        - GET /v1/api/usage/export
        - GET /v1/usage/breakdown
        - GET /v1/usage/heatmap
        - GET /v1/usage/history
        - GET /v1/usage/limits
        - GET /v1/usage/summary
      tags:
        - Usage
      summary: Obtener métricas de uso de la API
      description: |-
        Devuelve las métricas de uso de la API del usuario autenticado:

        - Peticiones a la API hoy y en el mes.
        - Cuota del plan vigente.
        - Últimas peticiones a la API.
        - Validaciones consumidas por mes.
        - Estado actual del servicio Banxico CEP.
      operationId: getApiUsage
      externalDocs:
        url: https://docs.veriko.mx/how-to/monitor-usage
        description: Track API quota and billing-cycle consumption
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/api/usage' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Estadísticas de uso
          x-translations:
            en:
              description: Usage statistics
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ApiUsage'
              examples:
                active_user:
                  summary: Usuario activo — uso moderado del mes
                  x-translations:
                    en:
                      summary: Active user — moderate monthly usage
                  value:
                    data:
                      requests_today: 42
                      requests_month: 580
                      quota:
                        plan_slug: basic
                        limit: 1000
                        used: 420
                        remaining: 580
                        resets_at: '2025-04-01T00:00:00Z'
                    meta:
                      version: 1.51.0
                      request_id: f7a8b9c0d1e2
                new_user:
                  summary: Usuario nuevo — sin actividad
                  x-translations:
                    en:
                      summary: New user — no activity yet
                  value:
                    data:
                      requests_today: 0
                      requests_month: 0
                      quota:
                        plan_slug: free
                        limit: 50
                        used: 0
                        remaining: 50
                        resets_at: '2025-04-01T00:00:00Z'
                    meta:
                      version: 1.51.0
                      request_id: a8b9c0d1e2f3
        '401':
          $ref: '#/components/responses/Unauthorized'
      x-translations:
        en:
          summary: Get API usage metrics
          description: |-
            Returns the authenticated user's API usage metrics:

            - API requests today and this month.
            - Quota of the plan in force.
            - Most recent API requests.
            - Validations consumed per month.
            - Current Banxico CEP service status.
      security:
        - ApiKeyAuth: []
  /summary:
    get:
      x-related:
        - GET /v1/validations
        - GET /v1/usage/summary
        - GET /v1/insights/overview
        - GET /v1/beneficiaries
      tags:
        - Dashboard
      summary: Obtener el resumen del panel
      description: |-
        Devuelve los contadores agregados del usuario: el total histórico, el total
        del mes UTC en curso, las validaciones con veredicto `valid`, el contador
        mensual de los estados `pending` y `processing`, el monto promedio y la
        tasa de éxito. Incluye también la cantidad de beneficiarios activos.

        `relationships.recent_validations` contiene las validaciones no retiradas
        más recientes. La cantidad se controla con `?limit=`, entre `1` y `10`;
        el valor predeterminado es `5`.

        `comenzar_milestones_completed` indica el progreso del wizard `/comenzar`.
      operationId: getDashboardSummary
      parameters:
        - name: limit
          in: query
          description: Filtro — Número máximo de validaciones recientes a devolver. Entre `1` y `10`; el valor predeterminado es `5`.
          x-translations:
            en:
              description: Filter — Maximum number of recent validations to return. From `1` to `10`; the default is `5`.
          schema:
            type: integer
            minimum: 1
            maximum: 10
            default: 5
          example: 5
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/summary' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Resumen del panel del usuario autenticado.
          x-translations:
            en:
              description: Dashboard summary for the authenticated user.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/DashboardSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
      x-translations:
        en:
          summary: Get dashboard summary
          description: |-
            Returns the user's aggregate counters: the lifetime total, the current
            UTC-month total, validations with verdict `valid`, the monthly count of
            the `pending` and `processing` states, the average amount, and the
            success rate. It also includes the active beneficiary count.

            `relationships.recent_validations` contains the latest validations that
            have not been withdrawn. Its size is controlled by `?limit=`, from `1`
            to `10`; the default is `5`.

            `comenzar_milestones_completed` indicates progress through the
            `/comenzar` wizard.
      security:
        - ApiKeyAuth: []
  /users/me/retry-policy:
    get:
      x-related:
        - PUT /v1/users/me/retry-policy
        - GET /v1/users/me
      tags:
        - Users
      summary: Obtener la política de reintentos de una cuenta de usuario
      description: |-
        Devuelve la {% concept slug="retry-policy" %}política de
        reintentos{% /concept %} configurada en la cuenta del usuario autenticado.

        Esta política se aplicará a toda validación que no contenga su propia
        `retry_policy` en el cuerpo, tanto en `POST /v1/validate` como en
        `POST /v1/validate-ocr`.

        Una cuenta que nunca ha configurado la política de reintentos, recibe la
        forma desactivada: `enabled=false`, los contadores en `0` y `outcomes`
        vacío.

        `meta.caps` contiene los límites efectivos del plan activo, con los
        valores globales para los campos sin sobreescritura. `max_retries_cap=0`
        impide activar reintentos.
      operationId: getMyRetryPolicy
      externalDocs:
        url: https://docs.veriko.mx/how-to/configure-retry-policy
        description: Set account-level default política de reintentos
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/users/me/retry-policy' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Política de reintentos por defecto de la cuenta autenticada.
          x-translations:
            en:
              description: Default retry policy of the authenticated account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserRetryPolicyResponse'
              examples:
                enabled_policy:
                  summary: Política activa — 3 intentos cada 10 min
                  x-translations:
                    en:
                      summary: Active policy — 3 attempts every 10 min
                  value:
                    data:
                      type: retry_policy
                      attributes:
                        enabled: true
                        max_retries: 3
                        interval_seconds: 600
                        outcomes:
                          - not_found
                          - cep_unavailable
                    meta:
                      version: 1.51.0
                      request_id: b3c4d5e6f7a8
                disabled_policy:
                  summary: Política desactivada explícitamente
                  x-translations:
                    en:
                      summary: Policy explicitly disabled
                  value:
                    data:
                      type: retry_policy
                      attributes:
                        enabled: false
                        max_retries: 1
                        interval_seconds: 600
                        outcomes:
                          - not_found
                    meta:
                      version: 1.51.0
                      request_id: c4d5e6f7a8b9
        '401':
          $ref: '#/components/responses/Unauthorized'
      x-translations:
        en:
          summary: Get default retry policy
          description: |-
            Returns the authenticated user's default
            {% concept slug="retry-policy" %}retry policy{% /concept %}.

            It is the one applied to every validation that does not carry its own
            `retry_policy` in the body, at `POST /v1/validate` as well as at
            `POST /v1/validate-ocr`.

            An account that has never configured it receives the disabled shape:
            `enabled=false`, the counters at `0` and an empty `outcomes`.

            `meta.caps` contains the effective limits of the active plan, with
            global values for fields without overrides. `max_retries_cap=0`
            prevents enabling retries.
      security:
        - ApiKeyAuth: []
    put:
      x-related:
        - GET /v1/users/me/retry-policy
        - GET /v1/users/me
      tags:
        - Users
      summary: Establecer la política de reintentos en una cuenta de usuario
      description: |-
        Fija la {% concept slug="retry-policy" %}política de
        reintentos{% /concept %} que se aplicará a la cuenta del usuario
        autenticado.

        Desde que la política se guarda, se aplica a toda validación futura que no
        contenga la suya propia en el cuerpo.

        La comprobación es estricta: un valor por encima de un tope **se rechaza**,
        no se recorta en silencio, y cualquier desvío responde con un estado HTTP
        `422` (con `retry_policy_invalid` en el cuerpo).

        Con `enabled=false`, los demás campos se ignoran y la política queda
        guardada como desactivada.

        Acepta {% concept slug="idempotency" %}`Idempotency-Key`opcional{% /concept
        %}, que evita guardar dos veces lo mismo cuando la red obliga a reintentar.
      operationId: updateMyRetryPolicy
      externalDocs:
        url: https://docs.veriko.mx/how-to/configure-retry-policy
        description: Set account-level default política de reintentos
      parameters:
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateUserRetryPolicyRequest'
            examples:
              enable_retries:
                summary: Activar política de reintentos por defecto
                x-translations:
                  en:
                    summary: Enable default retry policy
                value:
                  retry_policy:
                    enabled: true
                    max_retries: 3
                    interval_seconds: 600
                    outcomes:
                      - not_found
                      - cep_unavailable
              disable_retries:
                summary: Desactivar reintentos automáticos
                x-translations:
                  en:
                    summary: Disable automatic retries
                value:
                  retry_policy:
                    enabled: false
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X PUT 'https://api.veriko.mx/v1/users/me/retry-policy' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json' \
              -d '{
                "retry_policy": {
                  "enabled": false
                }
              }'
      responses:
        '200':
          description: Política de reintentos actualizada (eco del body normalizado).
          x-translations:
            en:
              description: Retry policy updated (echo of the normalized body).
          headers:
            Idempotent-Replayed:
              schema:
                type: string
                enum:
                  - 'true'
                  - 'false'
              description: Solo presente cuando el cliente envió `Idempotency-Key`. `true` cuando la respuesta es replay del caché de idempotencia (TTL 24h por usuario+endpoint+key).
              x-translations:
                en:
                  description: Only present when the client sent `Idempotency-Key`. `true` when the response is a replay from the idempotency cache (24h TTL per user+endpoint+key).
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Cuerpo de la respuesta. Todo el contenido cuelga de aquí, según la envoltura JSON:API.
                        x-translations:
                          en:
                            description: Response body. Everything hangs off here, per the JSON:API envelope.
                        properties:
                          type:
                            type: string
                            example: retry_policy
                          attributes:
                            $ref: '#/components/schemas/RetryPolicy'
        '400':
          description: El cuerpo de la petición está vacío o no es JSON válido.
          x-translations:
            en:
              description: The request body is empty or is not valid JSON.
              examples:
                body_empty:
                  value:
                    errors:
                      - detail: The request body is empty.
                invalid_json:
                  value:
                    errors:
                      - detail: The body is not valid JSON.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                body_empty:
                  summary: Cuerpo vacío
                  x-translations:
                    en:
                      summary: Empty body
                  value:
                    errors:
                      - status: '400'
                        code: body_empty
                        detail: El cuerpo de la petición está vacío.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: b0c1d2e3f4a5
                invalid_json:
                  summary: JSON mal formado
                  x-translations:
                    en:
                      summary: Malformed JSON
                  value:
                    errors:
                      - status: '400'
                        code: invalid_json
                        detail: El cuerpo no es JSON válido.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: c1d2e3f4a5b6
        '401':
          $ref: '#/components/responses/Unauthorized'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: 'La política de reintentos es inválida (`retry_policy_invalid`). Causas: campo `enabled` falta o no es booleano; `max_retries` fuera de `[1, max_retries_cap]`; `interval_seconds` fuera de `[min_interval_seconds, max_interval_seconds]`; `outcomes` vacío cuando `enabled=true`; outcome fuera del allowlist (`eligible_outcomes` de la configuración global de reintentos).'
          x-translations:
            en:
              description: 'Retry policy is invalid (`retry_policy_invalid`). Causes: `enabled` missing or not boolean; `max_retries` outside `[1, max_retries_cap]`; `interval_seconds` outside `[min_interval_seconds, max_interval_seconds]`; `outcomes` empty when `enabled=true`; outcome outside the allowlist (`eligible_outcomes` from the global retry configuration).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Set default retry policy
          description: |-
            Sets the authenticated user's default
            {% concept slug="retry-policy" %}retry policy{% /concept %}. From the
            moment it is saved, it applies to every future validation that does not
            carry its own in the body.

            The check is strict: a value beyond a cap **is rejected**, not silently
            trimmed. Any deviation responds with an HTTP `422` status (carrying
            `retry_policy_invalid` in the body).

            With `enabled=false` the other fields are ignored and the policy is
            stored as disabled.

            It accepts an optional
            {% concept slug="idempotency" %}`Idempotency-Key`{% /concept %}, which
            prevents storing the same thing twice when the network forces a retry.
      security:
        - ApiKeyAuth: []
  /status/banxico:
    get:
      x-related:
        - GET /v1/status/banxico/timeseries
      tags:
        - Banxico Status
      summary: Consultar el estado del servicio de verificación de Banxico
      description: |-
        Devuelve el estado operativo actual del **servicio de verificación de
        Banxico**, derivado de chequeos periódicos de salud.

        El estado puede ser:

        - `operational`: Todos los chequeos pasan y la latencia es normal.
        - `degraded`: Problemas parciales — latencia elevada o errores
          intermitentes.
        - `down`: El servicio es por el momento inaccesible.
        - `unknown`: No hay datos de salud vigentes: aún no se ha ejecutado ningún
          chequeo, o el último es más viejo que tres veces `check_interval_seconds`
          porque el chequeo automático dejó de correr. En ese caso `last_verified_at`
          conserva la fecha de ese último chequeo.

        La respuesta se cachea 60 segundos en el cliente con `Cache-Control`, y ese
        plazo se configura en el despliegue. Cualquier usuario autenticado puede
        consultarlo, y no hace falta ningún permiso concreto.

        {% callout type="info" %}
        **Validaciones síncronas durante una interrupción:**\
        Cuando el estado es `degraded` o `down`, las validaciones síncronas pueden
        responder con un estado HTTP `503` (con `banxico_rate_limit_exhausted` en el
        cuerpo). Configura `PUT /v1/validations/{id}/retry-policy` para reintentar
        automáticamente cuando el servicio de verificación de Banxico vuelva a estar
        disponible.

        {% /callout %}
      operationId: banxicoPublicStatus
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/status/banxico' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Estado actual del servicio de verificación de Banxico.
          x-translations:
            en:
              description: Current Banxico validation service status.
          headers:
            Cache-Control:
              schema:
                type: string
                example: public, max-age=60
              description: TTL de caché para la vista pública (configurable; default 60 s).
              x-translations:
                en:
                  description: Public-view cache TTL (configurable; defaults to 60 s).
            Vary:
              schema:
                type: string
                example: Origin
              description: Indicador de variación por Origin (CORS).
              x-translations:
                en:
                  description: Origin-based variation marker (CORS).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BanxicoPublicStatusResponse'
              examples:
                operational:
                  summary: Servicio de verificación de Banxico operativo sin incidentes
                  x-translations:
                    en:
                      summary: Banxico validation service fully operational
                  value:
                    data:
                      type: banxico_status
                      id: current
                      attributes:
                        status: operational
                        status_label: Operativo
                        message: El servicio de verificación está operando normalmente.
                        last_verified_at: '2026-04-11T15:30:00Z'
                        estimated_recovery_at: null
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: a1b2c3d4e5f6
                degraded:
                  summary: Servicio de verificación de Banxico degradado — respuestas lentas
                  x-translations:
                    en:
                      summary: Banxico validation service degraded — slow responses
                  value:
                    data:
                      type: banxico_status
                      id: current
                      attributes:
                        status: degraded
                        status_label: Degradado
                        message: Las verificaciones están respondiendo más lento de lo habitual.
                        last_verified_at: '2026-04-11T16:00:00Z'
                        estimated_recovery_at: '2026-04-11T17:00:00Z'
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: b2c3d4e5f6a7
                down:
                  summary: Servicio de verificación de Banxico inaccesible
                  x-translations:
                    en:
                      summary: Banxico validation service unreachable
                  value:
                    data:
                      type: banxico_status
                      id: current
                      attributes:
                        status: down
                        status_label: Interrumpido
                        message: El servicio de verificación de Banxico no está disponible. Estamos monitoreando.
                        last_verified_at: '2026-04-11T18:00:00Z'
                        estimated_recovery_at: null
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: c3d4e5f6a7b8
        '401':
          $ref: '#/components/responses/Unauthorized'
      x-translations:
        en:
          summary: Get the Banxico service status (public view)
          description: |-
            Returns the current operational status of the **Banxico validation
            service**, derived from periodic health checks.

            The status can be:

            - `operational`: All checks pass and latency is normal.
            - `degraded`: Partial issues — high latency or intermittent errors.
            - `down`: The service is unreachable for now.
            - `unknown`: There is no current health data: no check has run yet,
              or the last one is more than three times `check_interval_seconds`
              old because the automated check stopped running. In that case
              `last_verified_at` keeps the date of that last check.

            The response is cached 60 seconds on the client with `Cache-Control`,
            and that window is set at deploy time. Any authenticated user can query
            it, and no specific permission is required.

            {% callout type="info" %}
            **Synchronous validations during an outage:**\
            When the status is `degraded` or `down`, synchronous validations may
            respond with an HTTP `503` status (carrying
            `banxico_rate_limit_exhausted` in the body). Configure
            `PUT /v1/validations/{id}/retry-policy` to retry automatically once the
            Banxico service is available again.

            {% /callout %}
      security:
        - ApiKeyAuth: []
  /status/banxico/timeseries:
    get:
      x-related:
        - GET /v1/status/banxico
      tags:
        - Banxico Status
      summary: Obtener la serie temporal de salud del servicio de verificación de Banxico
      description: |-
        Devuelve el estado operativo histórico del **servicio de verificación de
        Banxico**, agrupado en puntos de datos por intervalo de tiempo para la
        métrica y ventana solicitadas.

        El tamaño del intervalo depende de la ventana:

        - `1h`: Intervalo por minuto.
        - `8h`, `12h`, `24h` y `7d`: Intervalo por hora.

        Expone las métricas `probe_latency` y `verdict`.

        La respuesta se cachea 60 segundos en el cliente con `Cache-Control`, y ese
        plazo se configura en el despliegue. Cualquier usuario autenticado puede
        consultarlo, y no hace falta ningún permiso concreto.

        {% callout type="info" %}
        **Valores fuera de rango:**\
        Un `metric` o `window` fuera de los valores permitidos NO produce un estado
        HTTP `422`: la petición cae silenciosamente a los valores por defecto
        (`probe_latency`, `24h`).

        {% /callout %}
      operationId: banxicoPublicTimeseries
      parameters:
        - name: metric
          in: query
          required: false
          schema:
            type: string
            enum:
              - probe_latency
              - verdict
            default: probe_latency
          example: probe_latency
          description: 'Filtro — Serie que se devuelve. `probe_latency`: Cada punto es el promedio de latencia (en milisegundos); `verdict`: El veredicto Banxico del grupo (el más reciente), codificado como `0`, `1` o `2`.'
          x-translations:
            en:
              description: Filter — Series to return. With `probe_latency`, each point is the average latency in milliseconds; with `verdict`, the bucket's Banxico verdict (its most recent), encoded as `0`, `1`, or `2`.
        - name: window
          in: query
          required: false
          schema:
            type: string
            enum:
              - 1h
              - 8h
              - 12h
              - 24h
              - 7d
            default: 24h
          example: 24h
          description: 'Filtro — Ventana que cubre la serie: `1h`, `8h`, `12h`, `24h` y `7d`. `1h` agrupa por minuto; el resto por hora.'
          x-translations:
            en:
              description: 'Filter — Window the series covers: `1h`, `8h`, `12h`, `24h` and `7d`. `1h` buckets by minute; the rest bucket by hour.'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/status/banxico/timeseries' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Puntos de datos de la serie temporal.
          x-translations:
            en:
              description: Time-series data points.
          headers:
            Cache-Control:
              schema:
                type: string
                example: public, max-age=60
              description: TTL de caché para la vista pública (configurable; default 60 s).
              x-translations:
                en:
                  description: Public-view cache TTL (configurable; defaults to 60 s).
            Vary:
              schema:
                type: string
                example: Origin
              description: Variación por Origin (CORS).
              x-translations:
                en:
                  description: Origin-based variation (CORS).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BanxicoPublicTimeseriesResponse'
              examples:
                probe_latency_24h:
                  summary: Latencia en buckets de 1 hora sobre las últimas 24 h
                  x-translations:
                    en:
                      summary: Latency in 1-hour buckets over the last 24 h
                  value:
                    data:
                      type: banxico_public_timeseries
                      id: probe_latency_24h
                      attributes:
                        metric: probe_latency
                        window: 24h
                        unit: ms
                        bucket_size_minutes: 60
                        points:
                          - ts: '2026-04-11T14:00:00Z'
                            value: 1523
                          - ts: '2026-04-11T15:00:00Z'
                            value: 1487
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: f6e5d4c3b2a1
                verdict_7d:
                  summary: Distribución de veredictos sobre 7 días
                  x-translations:
                    en:
                      summary: Verdict distribution over 7 days
                  value:
                    data:
                      type: banxico_public_timeseries
                      id: verdict_7d
                      attributes:
                        metric: verdict
                        window: 7d
                        unit: verdict
                        bucket_size_minutes: 60
                        points:
                          - ts: '2026-04-04T00:00:00Z'
                            value: 2
                          - ts: '2026-04-04T01:00:00Z'
                            value: 1
                          - ts: '2026-04-04T02:00:00Z'
                            value: 2
                    meta:
                      version: 1.47.0
                      api_version: v1
                      request_id: a7b8c9d0e1f2
        '401':
          $ref: '#/components/responses/Unauthorized'
      x-translations:
        en:
          summary: Banxico health time series (public view)
          description: |-
            Returns the historical operational status of the **Banxico validation
            service**, bucketed by time interval for the requested metric and window.

            The bucket size depends on the window:

            - `1h`: per minute.
            - `8h`, `12h`, `24h`, and `7d`: per hour.

            It exposes the `probe_latency` and `verdict` metrics.

            The response is cached 60 seconds on the client with `Cache-Control`,
            and that window is set at deploy time. Any authenticated user can query
            it, and no specific permission is required.

            {% callout type="info" %}
            **Out-of-range values:**\
            A `metric` or `window` outside the allowed values does NOT cause an HTTP
            `422` status: the request silently falls back to the defaults
            (`probe_latency`, `24h`).

            {% /callout %}
      security:
        - ApiKeyAuth: []
  /webhooks:
    get:
      tags:
        - Webhooks
      summary: Listar endpoints webhook registrados
      description: |-
        Devuelve los endpoints webhook del usuario autenticado, con su estado actual y los eventos a los que están suscritos.

        El `secret` de firma no se expone aquí: sólo aparece una vez, en la respuesta de `POST /v1/webhooks` al dar de alta el endpoint.

        {% callout type="info" %}
        **Probar endpoint y conexión:**\
        Para verificar que un endpoint webhook sigue alcanzable, `POST /v1/webhooks/{id}/test` envía una entrega sintética al receptor.

        {% /callout %}
      operationId: listWebhooks
      externalDocs:
        url: https://docs.veriko.mx/how-to/set-up-webhooks
        description: Register, test, and monitor webhook deliveries
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/webhooks' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Lista de endpoints webhook del usuario con su estado, datos y eventos.
          x-translations:
            en:
              description: List of the user's webhook endpoints with their status and subscribed events.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/WebhookEndpoint'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-related:
        - POST /v1/webhooks
        - GET /v1/webhooks/deliveries
      x-translations:
        en:
          summary: List webhook endpoints
          description: |-
            Returns the authenticated user's registered webhook endpoints, each with
            its status, current data and the events it subscribes to.

            The signing `secret` is not exposed here: it appears only once, in the
            `POST /v1/webhooks` response when the endpoint is created.

            {% callout type="info" %}
            **Testing the endpoint and the connection:**\
            To verify that a webhook endpoint is still reachable,
            `POST /v1/webhooks/{id}/test` sends a synthetic delivery to the
            receiver.

            {% /callout %}
      security:
        - ApiKeyAuth: []
    post:
      tags:
        - Webhooks
      summary: Crear un endpoint webhook para recibir eventos
      description: |-
        Registra un endpoint HTTPS que recibirá los eventos suscritos. La URL debe resolver a una dirección pública: `localhost` y los rangos privados se rechazan.

        Cada endpoint puede suscribirse a diferentes eventos, entre ellos:

        - Eventos `validation.*`: Esperan cualquier estado terminal de una validación para notificar ese estado mediante el webhook (p. ej: `validation.completed`, `validation.error`, etc).
        - Evento `validation.returned`: avisa cuando una validación que ya había salido `valid` pasa después a `returned` porque Banxico reportó la devolución; no se emite al crear la validación, donde el evento es `validation.completed`. Para recibirlo hay que suscribirse a él además de a `validation.completed`.
        - Eventos `billing.*`: No producen entregas hasta que haya algún movimiento en la suscripción del usuario (p. ej: `billing.subscription_canceled`, `billing.payment_succeeded`, cuotas de uso, etc).

        Si se alcanza el tope de endpoints registrados, la creación responde con un estado HTTP `422` (con `webhook_limit_reached` en el cuerpo) y con el valor del límite en el campo `meta.limit`.

        {% callout type="info" %}
        **Probar endpoint y conexión:**\
        `POST /v1/webhooks/{id}/test` envía una entrega sintética al endpoint recién creado; conviene confirmar que el receptor responde con un estado HTTP `2xx`antes de dirigirle tráfico real.

        {% /callout %}

        {% callout type="warning" %}
        **Firma secreta:**\
        El `secret` de firma se devuelve **únicamente en esta respuesta** y no puede recuperarse después. Un secreto perdido se sustituye con `POST /v1/webhooks/{id}/regenerate-secret`, que devuelve el nuevo e invalida el anterior de inmediato — el endpoint no hay que volver a registrarlo.\
        Ese secreto verifica cada entrega: el HMAC-SHA256 del cuerpo recibido debe coincidir con la cabecera `X-Webhook-Signature: sha256=<hex>`.\
        Cada entrega lleva además `X-Webhook-Signature-Timestamped: t=<segundos>,v1=<hex>`, donde `v1` es el HMAC-SHA256 de `<t>.<cuerpo>`. Esa firma incluye la hora del intento, y la ventana de tolerancia recomendada es de 5 minutos contra `t`, no contra el `timestamp` del cuerpo: los reintentos llegan hasta unas 8,6 horas después del evento.\
        \
        La firma, y más detalles se describen en {% concept slug="webhooks-architecture" %}la arquitectura de webhooks{% /concept %}.

        {% /callout %}
      operationId: createWebhook
      externalDocs:
        url: https://docs.veriko.mx/how-to/set-up-webhooks
        description: Register, test, and monitor webhook deliveries
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookRequest'
            examples:
              validations_only:
                summary: Solo eventos de validación
                value:
                  url: https://webhook.example.com/validations
                  events:
                    - validation.completed
                    - validation.failed
              full_subscription:
                summary: Validaciones + eventos de facturación
                value:
                  url: https://webhook.example.com/platform
                  events:
                    - validation.completed
                    - validation.failed
                    - validation.error
                    - billing.payment_succeeded
                    - billing.payment_failed
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/webhooks' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json' \
              -d '{
                "url": "https://webhook.example.com/platform",
                "events": [
                  "validation.completed",
                  "validation.failed",
                  "validation.error",
                  "billing.payment_succeeded",
                  "billing.payment_failed"
                ]
              }'
      responses:
        '201':
          description: 'Endpoint webhook creado. El campo `secret` aparece **solo en esta respuesta** y no puede volver a consultarse: es el que verifica la firma `X-Webhook-Signature: sha256=<hex>` que acompaña a cada entrega.'
          x-translations:
            en:
              description: 'Endpoint created. The `secret` field appears **in this response only** and cannot be read again: it is what verifies the `X-Webhook-Signature: sha256=<hex>` signature carried by every delivery.'
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/WebhookEndpoint'
        '400':
          description: El cuerpo de la petición está vacío o no es JSON válido, o la URL apunta a una dirección no pública.
          x-translations:
            en:
              description: The request body is empty or is not valid JSON, or the URL points to a non-public address.
              examples:
                body_empty:
                  value:
                    errors:
                      - detail: The request body is empty.
                invalid_json:
                  value:
                    errors:
                      - detail: The body is not valid JSON.
                url_dns_failed:
                  value:
                    errors:
                      - detail: The host name could not be resolved.
                url_ssrf_blocked:
                  value:
                    errors:
                      - detail: The given URL resolves to a non-public address.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                body_empty:
                  summary: Cuerpo vacío
                  x-translations:
                    en:
                      summary: Empty body
                  value:
                    errors:
                      - status: '400'
                        code: body_empty
                        detail: El cuerpo de la petición está vacío.
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: e6f7a2b3c4d5
                invalid_json:
                  summary: JSON mal formado
                  x-translations:
                    en:
                      summary: Malformed JSON
                  value:
                    errors:
                      - status: '400'
                        code: invalid_json
                        detail: El cuerpo no es JSON válido.
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: f7a2b3c4d5e6
                url_dns_failed:
                  summary: El dominio no resuelve
                  x-translations:
                    en:
                      summary: Host does not resolve
                  value:
                    errors:
                      - status: '400'
                        code: url_dns_failed
                        detail: No se pudo resolver el nombre de la dirección.
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: d4e5f6a1b2c4
                url_ssrf_blocked:
                  summary: La URL apunta a una red no pública
                  x-translations:
                    en:
                      summary: URL points to a non-public network
                  value:
                    errors:
                      - status: '400'
                        code: url_ssrf_blocked
                        detail: La URL indicada resuelve a una dirección no pública.
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: d4e5f6a1b2c5
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: La URL, la lista de eventos o `description` no superan la validación, o la cuenta de usuario alcanzó su tope de endpoints. `webhook_url_invalid_type` y `webhook_description_invalid_type` aparecen cuando el campo llega con un tipo distinto de cadena de texto; `webhook_description_too_long` cuando `description` excede 255 caracteres. Todo error de campo trae `source.pointer`.
          x-translations:
            en:
              description: The URL, the event list, or `description` failed validation, or the account has reached its endpoint cap. `webhook_url_invalid_type` and `webhook_description_invalid_type` fire when the field arrives as a type other than a string; `webhook_description_too_long` when `description` exceeds 255 characters. Every field-level error carries `source.pointer`.
              examples:
                missing_field:
                  value:
                    errors:
                      - detail: The url field is required.
                url_required:
                  value:
                    errors:
                      - detail: The url field is required.
                url_too_long:
                  value:
                    errors:
                      - detail: The URL cannot exceed 2,048 characters.
                url_invalid_format:
                  value:
                    errors:
                      - detail: The URL is not a valid URL.
                url_not_https:
                  value:
                    errors:
                      - detail: The URL must use HTTPS.
                events_required:
                  value:
                    errors:
                      - detail: The endpoint must subscribe to at least one event.
                events_too_many:
                  value:
                    errors:
                      - detail: An endpoint accepts at most 10 events.
                event_invalid:
                  value:
                    errors:
                      - detail: The given event does not exist. The rejected event is in meta.event.
                limit_reached:
                  value:
                    errors:
                      - detail: The maximum number of allowed endpoints has been reached. The applied cap is in meta.limit.
                description_too_long:
                  value:
                    errors:
                      - detail: The description field must not exceed 255 characters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missing_field:
                  summary: Falta un campo obligatorio
                  x-translations:
                    en:
                      summary: Missing a required field
                  value:
                    errors:
                      - status: '422'
                        code: validation_error
                        detail: El campo url es obligatorio.
                        source:
                          pointer: /data/attributes/url
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: b4c5d6e7f8a9
                url_required:
                  summary: Falta la URL
                  x-translations:
                    en:
                      summary: Missing URL
                  value:
                    errors:
                      - status: '422'
                        code: webhook_url_required
                        detail: El campo url es obligatorio.
                        source:
                          pointer: /data/attributes/url
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: a1b2c3d4e5f6
                url_too_long:
                  summary: URL de más de 2 048 caracteres
                  x-translations:
                    en:
                      summary: URL longer than 2,048 characters
                  value:
                    errors:
                      - status: '422'
                        code: webhook_url_too_long
                        detail: La URL no puede exceder 2 048 caracteres.
                        source:
                          pointer: /data/attributes/url
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: b2c3d4e5f6a1
                url_invalid_format:
                  summary: URL mal formada
                  x-translations:
                    en:
                      summary: Malformed URL
                  value:
                    errors:
                      - status: '422'
                        code: webhook_url_invalid_format
                        detail: La URL no tiene un formato válido.
                        source:
                          pointer: /data/attributes/url
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: c3d4e5f6a1b2
                url_not_https:
                  summary: URL sin HTTPS
                  x-translations:
                    en:
                      summary: Non-HTTPS URL
                  value:
                    errors:
                      - status: '422'
                        code: webhook_url_not_https
                        detail: La URL debe usar HTTPS.
                        source:
                          pointer: /data/attributes/url
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: d4e5f6a1b2c3
                events_required:
                  summary: Lista de eventos vacía
                  x-translations:
                    en:
                      summary: Empty event list
                  value:
                    errors:
                      - status: '422'
                        code: webhook_events_required
                        detail: El endpoint debe suscribirse a al menos un evento.
                        source:
                          pointer: /data/attributes/events
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: a2b3c4d5e6f7
                events_too_many:
                  summary: Más de 10 eventos
                  x-translations:
                    en:
                      summary: More than 10 events
                  value:
                    errors:
                      - status: '422'
                        code: webhook_events_too_many
                        detail: Un endpoint admite como máximo 10 eventos.
                        source:
                          pointer: /data/attributes/events
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: b3c4d5e6f7a2
                event_invalid:
                  summary: Evento desconocido
                  x-translations:
                    en:
                      summary: Unknown event
                  value:
                    errors:
                      - status: '422'
                        code: webhook_event_invalid
                        detail: El evento indicado no existe. El evento rechazado viene en meta.event.
                        source:
                          pointer: /data/attributes/events
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: c4d5e6f7a2b3
                limit_reached:
                  summary: Tope de endpoints alcanzado
                  x-translations:
                    en:
                      summary: Endpoint cap reached
                  value:
                    errors:
                      - status: '422'
                        code: webhook_limit_reached
                        detail: Se alcanzó el número máximo de endpoints permitidos. El tope aplicado viene en meta.limit.
                    meta:
                      version: 1.57.0
                      api_version: v1
                      request_id: d5e6f7a2b3c4
                description_too_long:
                  summary: description mayor a 255 caracteres
                  x-translations:
                    en:
                      summary: description longer than 255 characters
                  value:
                    errors:
                      - status: '422'
                        code: webhook_description_too_long
                        detail: El campo description no debe exceder 255 caracteres.
                        source:
                          pointer: /data/attributes/description
                    meta:
                      version: 1.59.1
                      api_version: v1
                      request_id: e6f7a2b3c4d5
        '429':
          $ref: '#/components/responses/RateLimited'
      x-related:
        - GET /v1/webhooks
        - POST /v1/webhooks/{id}/test
        - POST /v1/webhooks/{id}/regenerate-secret
      x-translations:
        en:
          summary: Create a webhook endpoint
          description: |-
            Registers an HTTPS endpoint that will receive the subscribed events as
            webhooks. The URL must resolve to a public address: `localhost` and
            private ranges are rejected.

            Each endpoint can subscribe to different events, among them:

            - `validation.*` events: they await any terminal state of a validation to report that state through the webhook (e.g. `validation.completed`, `validation.error`, and so on).
            - `validation.returned` event: it fires when a validation that had already come out `valid` later moves to `returned` because Banxico reported the return; it is not emitted when the validation is created, where the event is `validation.completed`. To receive it, subscribe to it in addition to `validation.completed`.
            - `billing.*` events: they produce no delivery until something moves in the user's subscription (e.g. `billing.subscription_canceled`, `billing.payment_succeeded`, usage quotas, and so on).

            Once the cap of registered endpoints is reached, creation responds with
            HTTP status `422` (with `webhook_limit_reached` in the body) and the
            limit value in the `meta.limit` field.

            {% callout type="info" %}
            **Testing the endpoint and the connection:**\
            `POST /v1/webhooks/{id}/test` sends a synthetic delivery to the newly
            created endpoint; it is worth confirming that the receiver answers with
            an HTTP `2xx` status before directing real traffic to it.

            {% /callout %}

            {% callout type="warning" %}
            **Signing secret:**\
            The signing `secret` is returned **only in this response** and cannot be
            retrieved afterwards. A lost secret is replaced with
            `POST /v1/webhooks/{id}/regenerate-secret`, which returns the new one and
            invalidates the previous one immediately — the endpoint does not have to
            be registered again.\
            That secret verifies every delivery: the HMAC-SHA256 of the received body
            must match the `X-Webhook-Signature: sha256=<hex>` header.\
            Every delivery also carries `X-Webhook-Signature-Timestamped: t=<seconds>,v1=<hex>`, where `v1` is the HMAC-SHA256 of `<t>.<body>`. That signature includes the attempt time, and the recommended tolerance window is 5 minutes against `t`, not against the `timestamp` of the body: retries arrive up to about 8.6 hours after the event.\
            \
            Signing, and further detail, are described in {% concept slug="webhooks-architecture" %}the webhooks architecture{% /concept %}.

            {% /callout %}
      security:
        - ApiKeyAuth: []
  /webhooks/{id}:
    put:
      x-related:
        - DELETE /v1/webhooks/{id}
        - GET /v1/webhooks/{id}/deliveries
        - GET /v1/webhooks/{id}/deliveries/export
        - POST /v1/webhooks/{id}/regenerate-secret
        - POST /v1/webhooks/{id}/test
        - GET /v1/webhooks
      tags:
        - Webhooks
      summary: Actualizar un endpoint webhook
      description: |-
        Actualiza la URL, los eventos suscritos o el estado de un endpoint webhook ya registrado.

        Solo cambia lo que viene en el cuerpo: Un campo ausente se queda como estaba, y un cuerpo sin ningún campo editable responde con un estado HTTP `422` (con `no_valid_fields` en el cuerpo).

        Poner el estado en `disabled` detiene las entregas sin perder el historial; es lo que conviene mientras se arregla un receptor caído, en vez de borrar el endpoint y volver a crearlo.

        {% callout type="warning" %}
        **Firma secreta:**\
        **El secreto de firma no cambia aquí.** Cambiar la dirección deja el mismo secreto en el endpoint nuevo, de modo que un receptor que ya validaba firmas sigue validándolas. Para rotarlo está `POST /v1/webhooks/{id}/regenerate-secret`.\
        \
        La firma, y más detalles se describen en {% concept slug="webhooks-architecture" %}la arquitectura de webhooks{% /concept %}.

        {% /callout %}
      operationId: updateWebhook
      externalDocs:
        url: https://docs.veriko.mx/how-to/set-up-webhooks
        description: Register, test, and monitor webhook deliveries
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          description: Identificador único del endpoint webhook (UUID v4).
          x-translations:
            en:
              description: Unique identifier of the webhook endpoint (UUID v4).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWebhookRequest'
            example:
              url: https://webhook.example.com/platform
              events:
                - validation.completed
              status: active
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X PUT 'https://api.veriko.mx/v1/webhooks/{id}' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json' \
              -d '{
                "url": "https://webhook.example.com/platform",
                "events": [
                  "validation.completed"
                ],
                "status": "active"
              }'
      responses:
        '200':
          description: Endpoint webhook actualizado.
          x-translations:
            en:
              description: Webhook endpoint updated.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/WebhookEndpoint'
        '400':
          description: El cuerpo de la petición está vacío o no es JSON válido, o la URL nueva no resuelve a una dirección pública.
          x-translations:
            en:
              description: The request body is empty or is not valid JSON, or the new URL does not resolve to a public address.
              examples:
                body_empty:
                  value:
                    errors:
                      - detail: The request body is empty.
                invalid_json:
                  value:
                    errors:
                      - detail: The body is not valid JSON.
                url_dns_failed:
                  value:
                    errors:
                      - detail: The host name could not be resolved.
                url_ssrf_blocked:
                  value:
                    errors:
                      - detail: The given URL resolves to a non-public address.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                body_empty:
                  summary: Cuerpo vacío
                  x-translations:
                    en:
                      summary: Empty body
                  value:
                    errors:
                      - status: '400'
                        code: body_empty
                        detail: El cuerpo de la petición está vacío.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: c4d5e6f7a8b9
                invalid_json:
                  summary: JSON mal formado
                  x-translations:
                    en:
                      summary: Malformed JSON
                  value:
                    errors:
                      - status: '400'
                        code: invalid_json
                        detail: El cuerpo no es JSON válido.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: d5e6f7a8b9c0
                url_dns_failed:
                  summary: El dominio no resuelve
                  x-translations:
                    en:
                      summary: Host does not resolve
                  value:
                    errors:
                      - status: '400'
                        code: url_dns_failed
                        detail: No se pudo resolver el nombre de la dirección.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: d4e5f6a1b2c4
                url_ssrf_blocked:
                  summary: La URL apunta a una red no pública
                  x-translations:
                    en:
                      summary: URL points to a non-public network
                  value:
                    errors:
                      - status: '400'
                        code: url_ssrf_blocked
                        detail: La URL indicada resuelve a una dirección no pública.
                    meta:
                      version: 1.58.0
                      api_version: v1
                      request_id: d4e5f6a1b2c5
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '`not_found`: El endpoint no existe o no pertenece al usuario.'
          x-translations:
            en:
              description: '`not_found`: Endpoint does not exist or does not belong to the user.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: 'Datos inválidos. Códigos posibles: `webhook_url_invalid_type`, `webhook_url_empty`, `webhook_url_too_long`, `webhook_url_invalid_format`, `webhook_url_not_https`, `webhook_events_required`, `webhook_events_too_many`, `webhook_event_invalid`, `webhook_description_invalid_type`, `webhook_description_too_long`, `webhook_status_invalid`, `no_valid_fields`. Una `description` de más de 255 caracteres responde `webhook_description_too_long`. Todo error de campo trae `source.pointer`.'
          x-translations:
            en:
              description: 'Invalid data. Possible codes: `webhook_url_invalid_type`, `webhook_url_empty`, `webhook_url_too_long`, `webhook_url_invalid_format`, `webhook_url_not_https`, `webhook_events_required`, `webhook_events_too_many`, `webhook_event_invalid`, `webhook_description_invalid_type`, `webhook_description_too_long`, `webhook_status_invalid`, `no_valid_fields`. A `description` longer than 255 characters responds `webhook_description_too_long`. Every field-level error carries `source.pointer`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: Update a webhook endpoint
          description: |-
            Updates the receiving address, the subscribed events, or the status of an
            already registered endpoint. Only what arrives in the body changes: an
            absent field stays as it was, and a body with no editable field responds
            with an HTTP `422` status (with `no_valid_fields` in the body).

            **The signing secret does not change here.** Changing the address leaves
            the same secret on the new endpoint, so a receiver that already validated
            signatures keeps validating them. To rotate it there is
            `POST /v1/webhooks/{id}/regenerate-secret`.

            Setting the status to `disabled` stops deliveries without losing the
            history; that is the move while a downed receiver is being fixed, rather
            than deleting the endpoint and creating it again.
      security:
        - ApiKeyAuth: []
    delete:
      x-related:
        - GET /v1/webhooks/{id}/deliveries
        - GET /v1/webhooks/{id}/deliveries/export
        - POST /v1/webhooks/{id}/regenerate-secret
        - POST /v1/webhooks/{id}/test
        - PUT /v1/webhooks/{id}
        - GET /v1/webhooks
      tags:
        - Webhooks
      summary: Eliminar un endpoint webhook
      description: |-
        Elimina el endpoint webhook y **todo su historial de entregas**. No hay archivado: el registro desaparece y no se puede recuperar.

        Para dejar de recibir entregas sin perder el historial, la vía es asignarle el estado `disabled` con `PUT /v1/webhooks/{id}`.
      operationId: deleteWebhook
      externalDocs:
        url: https://docs.veriko.mx/how-to/set-up-webhooks
        description: Register, test, and monitor webhook deliveries
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          description: Identificador único del endpoint webhook (UUID v4).
          x-translations:
            en:
              description: Unique identifier of the webhook endpoint (UUID v4).
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X DELETE 'https://api.veriko.mx/v1/webhooks/{id}' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '204':
          description: Sin contenido — endpoint eliminado.
          x-translations:
            en:
              description: No content — endpoint deleted.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '`not_found`: El endpoint no existe o no pertenece al usuario.'
          x-translations:
            en:
              description: '`not_found`: Endpoint does not exist or does not belong to the user.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: Delete a webhook endpoint
          description: |-
            Deletes the endpoint and **its entire delivery history**. Unlike almost
            everything else in this API, there is no archiving here: the record is
            gone and cannot be recovered.

            To stop receiving deliveries without losing the history, the route is
            setting the status to `disabled` with `PUT /v1/webhooks/{id}`.
      security:
        - ApiKeyAuth: []
  /webhooks/{id}/test:
    post:
      x-related:
        - DELETE /v1/webhooks/{id}
        - GET /v1/webhooks/{id}/deliveries
        - GET /v1/webhooks/{id}/deliveries/export
        - POST /v1/webhooks/{id}/regenerate-secret
        - PUT /v1/webhooks/{id}
        - GET /v1/webhooks
      tags:
        - Webhooks
      summary: Enviar evento de prueba al endpoint webhook
      description: |-
        Envía una entrega sintética al endpoint webhook para comprobar que el receptor está en pie.

        Se recomienda ejecutar al registrar o actualizar un endpoint webhook: confirma que responde con un estado HTTP `2xx` y que valida la firma del cuerpo.

        Antes de enviar, se vuelve a comprobar la URL. Un nombre que no resuelve, o una URL que no es pública, dejaría el resultado con `delivered=false` y el detalle en `error`.

        {% callout type="info" %}
        **Un evento de prueba fallido no cuenta contra el endpoint.** El contador de fallos consecutivos que deshabilita el endpoint automáticamente no aplica, así que probar cuantas veces haga falta no acerca el endpoint a la desactivación automática.

        {% /callout %}
      operationId: sendWebhookTest
      externalDocs:
        url: https://docs.veriko.mx/how-to/set-up-webhooks
        description: Register, test, and monitor webhook deliveries
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          description: Identificador único del endpoint webhook (UUID v4).
          x-translations:
            en:
              description: Unique identifier of the webhook endpoint (UUID v4).
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/webhooks/{id}/test' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '200':
          description: Resultado del intento de entrega de prueba. `delivered=false` cuando la URL falla la validación URL o cuando el receptor respondió con un código fuera de `2xx`. (`error` contiene el detalle si aplica).
          x-translations:
            en:
              description: Result of the test delivery attempt. `delivered=false` when the URL fails SSRF re-validation (`error` carries the detail) or when the receiver responded with a non-`2xx` code.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          type:
                            type: string
                            enum:
                              - webhook_test_result
                          attributes:
                            $ref: '#/components/schemas/SendWebhookTestAttributes'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '`not_found`: El endpoint no existe o no pertenece al usuario.'
          x-translations:
            en:
              description: '`not_found`: Endpoint does not exist or does not belong to the user.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: Send a test event to the webhook
          description: |-
            Sends a synthetic delivery to the endpoint to check the receiver is
            standing. It is the natural next step after registering or updating an
            endpoint: it confirms the receiver answers with an HTTP `2xx` status and
            validates the body signature.

            **A failed test does not count against the endpoint.** The consecutive
            failure counter that eventually disables it is not touched here, so
            testing as often as needed does not bring the endpoint closer to
            automatic disabling.

            Before anything is sent, the address is re-checked. A name that does not
            resolve, or an address pointing at the internal network, leaves the
            result with `delivered=false` and the detail in `error`.
      security:
        - ApiKeyAuth: []
  /webhooks/{id}/deliveries:
    get:
      x-related:
        - GET /v1/webhooks/{id}/deliveries/export
        - DELETE /v1/webhooks/{id}
        - POST /v1/webhooks/{id}/regenerate-secret
        - POST /v1/webhooks/{id}/test
        - PUT /v1/webhooks/{id}
        - GET /v1/webhooks
      tags:
        - Webhooks
      summary: Listar entregas de un endpoint webhook
      description: |-
        Devuelve los intentos de entrega de un endpoint webhook específico, de la entrega más reciente a la más antigua, con paginación.

        Cada intento lleva: el código de estado HTTP con el que respondió el endpoint webhook, el tiempo de respuesta y el cuerpo de la respuesta truncado.
      operationId: listWebhookDeliveries
      externalDocs:
        url: https://docs.veriko.mx/how-to/set-up-webhooks
        description: Register, test, and monitor webhook deliveries
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          description: Identificador único del endpoint webhook (UUID v4).
          x-translations:
            en:
              description: Unique identifier of the webhook endpoint (UUID v4).
        - name: page
          in: query
          description: |-

            Filtro — Página que se pide, empezando en 1.
          x-translations:
            en:
              description: Page requested, starting at 1.
          schema:
            type: integer
            minimum: 1
            default: 1
          example: 1
        - name: per_page
          in: query
          description: Filtro — Cuántas entregas trae cada página.
          x-translations:
            en:
              description: How many deliveries each page carries.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          example: 50
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/webhooks/{id}/deliveries' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Intentos de entrega paginados de este endpoint webhook, con el código de estado HTTP, el tiempo de respuesta y el cuerpo truncado.
          x-translations:
            en:
              description: Paginated delivery attempts for this endpoint, with the HTTP status code, response time, and truncated body.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/WebhookDelivery'
                      meta:
                        type: object
                        properties:
                          pagination:
                            type: object
                            properties:
                              page:
                                type: integer
                                example: 1
                              per_page:
                                type: integer
                                example: 50
                              total:
                                type: integer
                                example: 137
                              total_pages:
                                type: integer
                                example: 3
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '`not_found`: El endpoint no existe o no pertenece al usuario.'
          x-translations:
            en:
              description: '`not_found`: Endpoint does not exist or does not belong to the user.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: List deliveries for an endpoint
          description: |-
            Returns the delivery attempts for a specific endpoint, from most recent
            to oldest, paginated.

            Each attempt carries the HTTP status code the receiver answered with, the
            response time, and the truncated response body.
      security:
        - ApiKeyAuth: []
  /webhooks/{id}/deliveries/export:
    get:
      x-related:
        - GET /v1/webhooks/{id}/deliveries
        - DELETE /v1/webhooks/{id}
        - POST /v1/webhooks/{id}/regenerate-secret
        - POST /v1/webhooks/{id}/test
        - PUT /v1/webhooks/{id}
        - GET /v1/webhooks
      tags:
        - Webhooks
      summary: Exportar las entregas de un endpoint webhook como CSV o XLSX
      description: Descarga hasta 100 000 registros del historial de entregas de un endpoint específico, en formato `CSV` o `XLSX`.
      operationId: exportWebhookDeliveries
      externalDocs:
        url: https://docs.veriko.mx/how-to/set-up-webhooks
        description: Register, test, and monitor webhook deliveries
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          description: Identificador único del endpoint webhook (UUID v4).
          x-translations:
            en:
              description: Unique identifier of the webhook endpoint (UUID v4).
        - name: format
          in: query
          required: false
          schema:
            type: string
            default: csv
            enum:
              - csv
              - xlsx
          example: csv
          description: 'Formato del archivo a descargar. Valores aceptados: `csv` y `xlsx`. Cualquier otro valor se sirve como `csv`.'
          x-translations:
            en:
              description: Output format. `csv` by default; `xlsx` produces a spreadsheet. Any other value is served as `csv`.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100000
          example: 500
          description: 'Filtro — Recorta el número de filas del archivo. Solo baja el tope: un valor mayor que 100 000, o menor que 1, deja el tope por defecto.'
          x-translations:
            en:
              description: 'Trims the number of rows in the file. It only lowers the cap: a value above 100,000, or below 1, leaves the default cap in place.'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/webhooks/{id}/deliveries/export' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Archivo CSV o XLSX con el historial de entregas del endpoint (hasta 100 000 filas).
          x-translations:
            en:
              description: CSV or XLSX file with the delivery history for this endpoint (up to 100,000 rows).
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: Export endpoint deliveries as CSV or XLSX
          description: |-
            Downloads up to 100,000 records of a specific webhook endpoint's delivery history.\
            Download formats: `csv` or `xlsx`.
      security:
        - ApiKeyAuth: []
  /webhooks/{id}/regenerate-secret:
    post:
      x-related:
        - DELETE /v1/webhooks/{id}
        - GET /v1/webhooks/{id}/deliveries
        - GET /v1/webhooks/{id}/deliveries/export
        - POST /v1/webhooks/{id}/test
        - PUT /v1/webhooks/{id}
        - GET /v1/webhooks
      tags:
        - Webhooks
      summary: Regenerar la firma secreta de un endpoint webhook
      description: |-
        Rota el secreto con el que se firman las entregas de un endpoint webhook. El secreto nuevo viaja **una sola vez**, en esta respuesta, y no hay forma de volver a consultarlo.

        {% callout type="danger" %}
        **El secreto anterior queda invalidado en el acto**: Un receptor que valide firmas dejará de validar las entregas siguientes hasta que se le configure el secreto nuevo.

        {% /callout %}
      operationId: regenerateWebhookSecret
      externalDocs:
        url: https://docs.veriko.mx/how-to/set-up-webhooks
        description: Register, test, and monitor webhook deliveries
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          description: Identificador único del endpoint webhook (UUID v4).
          x-translations:
            en:
              description: Unique identifier of the webhook endpoint (UUID v4).
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X POST 'https://api.veriko.mx/v1/webhooks/{id}/regenerate-secret' \
              -H 'Authorization: Bearer veriko_••••' \
              -H 'Content-Type: application/json'
      responses:
        '200':
          description: Nuevo secret generado. Solo se devuelve en esta respuesta.
          x-translations:
            en:
              description: New signing secret generated. Returned only in this response.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/WebhookEndpoint'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '`not_found`: El endpoint no existe o no pertenece al usuario.'
          x-translations:
            en:
              description: '`not_found`: Endpoint does not exist or does not belong to the user.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: Regenerate webhook signing secret
          description: |-
            Rotates the secret used to sign this endpoint's delivery bodies. The new
            secret travels **exactly once**, in this response, and there is no way to
            read it again.

            The previous one is invalidated at once: there is no overlap window. A
            receiver validating signatures will stop validating the following
            deliveries until the new secret is configured on it, so the rotation is
            worth coordinating with that deployment.
      security:
        - ApiKeyAuth: []
  /webhooks/deliveries:
    get:
      x-related:
        - GET /v1/webhooks/deliveries/export
        - DELETE /v1/webhooks/{id}
        - GET /v1/webhooks
        - GET /v1/webhooks/{id}/deliveries
        - GET /v1/webhooks/{id}/deliveries/export
        - POST /v1/webhooks
      tags:
        - Webhooks
      summary: Listar entregas de todos los endpoints webhook
      description: |-
        Devuelve una vista consolidada de las entregas hechas a los endpoints webhook del usuario autenticado.

        Se puede filtrar por endpoint, estado y tipo de evento.
      operationId: listAllDeliveries
      externalDocs:
        url: https://docs.veriko.mx/how-to/set-up-webhooks
        description: Register, test, and monitor webhook deliveries
      parameters:
        - name: page
          in: query
          description: Filtro — Página que se pide, empezando en 1.
          x-translations:
            en:
              description: Page requested, starting at 1.
          schema:
            type: integer
            minimum: 1
            default: 1
          example: 1
        - name: per_page
          in: query
          description: Filtro — Cuántas entregas trae cada página.
          x-translations:
            en:
              description: How many deliveries each page carries.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          example: 50
        - name: endpoint_id
          in: query
          description: Filtro — Identificador único del endpoint webhook (UUID v4).
          x-translations:
            en:
              description: Filter — unique identifier of the webhook endpoint (UUID v4).
          schema:
            type: string
            format: uuid
            example: 7c9e6679-7425-40de-944b-e07fc1f90ae7
          example: 7c9e6679-7425-40de-944b-e07fc1f90ae7
        - name: status
          in: query
          schema:
            type: string
            enum:
              - success
              - failed
              - retrying
              - pending
          example: failed
          description: Filtro — Estado de la entrega. `success` llegó. `failed` es el resultado del último intento; Messenger puede programar otro si el fallo es recuperable. `retrying` se pospuso por el límite de salida y Messenger la reintentará. `pending` todavía no se ha intentado.
          x-translations:
            en:
              description: Delivery state. `success` arrived. `failed` is the latest attempt's outcome; Messenger can schedule another one for a recoverable failure. `retrying` was deferred by the outbound limit and Messenger will retry it. `pending` has not been attempted yet.
        - name: event_type
          in: query
          schema:
            type: string
            enum:
              - validation.completed
              - validation.failed
              - validation.error
              - validation.retry.scheduled
              - validation.retry.resolved
              - validation.retry.exhausted
              - validation.returned
              - billing.payment_succeeded
              - billing.payment_failed
              - billing.trial_will_end
              - billing.subscription_canceled
              - billing.invoice_upcoming
              - test
          example: validation.completed
          description: |-
            Filtro — Tipo de evento que originó la entrega. **De validaciones**: `validation.completed` cuando termina bien, `validation.failed` cuando termina mal y `validation.error` cuando hay un error del servicio. **De los reintentos automáticos:** `validation.retry.scheduled` al programar un reintento automático, `validation.retry.resolved` cuando un reintento automático encuentra el CEP, y `validation.retry.exhausted` cuando los reintentos se agotan sin encontrar el CEP. **De la revisión posterior de un `valid`:** `validation.returned` cuando una validación que había salido `valid` pasa a `returned` porque Banxico reportó la devolución.\
            **De la suscripción:** `billing.payment_succeeded` y `billing.payment_failed` por cada cobro, `billing.invoice_upcoming` antes de la siguiente factura, `billing.trial_will_end` antes de que acabe la prueba gratuita, y `billing.subscription_canceled` al cancelar la suscripción. Las entregas sintéticas (envío de prueba) se marcan como `test`.
          x-translations:
            en:
              description: |-
                Type of event that produced the delivery.
                From a validation's lifecycle: `validation.completed` when it ends well, `validation.failed` when it ends badly, and `validation.error` when it fails on the service side. From its automatic retries: `validation.retry.scheduled` when one is scheduled, `validation.retry.resolved` when a retry ends up finding the receipt, and `validation.retry.exhausted` when they run out without success. From the follow-up check of a `valid`: `validation.returned` when a validation that had come out `valid` moves to `returned` because Banxico reported the return.
                From the subscription: `billing.payment_succeeded` and `billing.payment_failed` for each charge, `billing.invoice_upcoming` before the next invoice, `billing.trial_will_end` before the trial runs out, and `billing.subscription_canceled` when it is cancelled.
                `test` marks the synthetic deliveries from the test send.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/webhooks/deliveries' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Lista consolidada de entregas de todos los endpoints del usuario.
          x-translations:
            en:
              description: Consolidated list of deliveries across all the user's endpoints.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/WebhookDelivery'
                      meta:
                        type: object
                        properties:
                          pagination:
                            type: object
                            properties:
                              page:
                                type: integer
                                example: 1
                              per_page:
                                type: integer
                                example: 50
                              total:
                                type: integer
                                example: 137
                              total_pages:
                                type: integer
                                example: 3
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: List deliveries across all endpoints
          description: |-
            Consolidated view of the authenticated user's webhook deliveries, across
            all their endpoints at once. It can be filtered by endpoint, status, and
            event type.
      security:
        - ApiKeyAuth: []
  /webhooks/deliveries/export:
    get:
      x-related:
        - GET /v1/webhooks/deliveries
        - DELETE /v1/webhooks/{id}
        - GET /v1/webhooks
        - GET /v1/webhooks/{id}/deliveries
        - GET /v1/webhooks/{id}/deliveries/export
        - POST /v1/webhooks
      tags:
        - Webhooks
      summary: Exportar todas las entregas webhook como CSV o XLSX
      description: |-
        Descarga el historial de entregas de **todos** los endpoints de la cuenta de usuario en
        un archivo, con los mismos filtros que el listado: endpoint, estado y tipo de
        evento.

        {% concept slug="exports-and-formats" %}El formato se elige{% /concept %} con `?format` —`csv` por defecto, `xlsx` como
        alternativa— y el tope es de 100 000 filas, que `?limit` puede bajar.

        Conviene exportar antes de borrar un endpoint: `DELETE /v1/webhooks/{id}` se
        lleva su historial por delante y no hay forma de recuperarlo.
      operationId: exportAllDeliveries
      externalDocs:
        url: https://docs.veriko.mx/how-to/set-up-webhooks
        description: Register, test, and monitor webhook deliveries
      parameters:
        - name: endpoint_id
          in: query
          description: |-

            Filtro — Identificador único del endpoint webhook (UUID v4).
          x-translations:
            en:
              description: Filter — unique identifier of the webhook endpoint (UUID v4).
          schema:
            type: string
            format: uuid
            example: 7c9e6679-7425-40de-944b-e07fc1f90ae7
          example: 7c9e6679-7425-40de-944b-e07fc1f90ae7
        - name: status
          in: query
          schema:
            type: string
            enum:
              - success
              - failed
              - retrying
              - pending
          example: failed
          description: |-
            \
            Filtro — Estado de la entrega. `success` llegó. `failed` es el resultado del último intento; Messenger puede programar otro si el fallo es recuperable. `retrying` se pospuso por el límite de salida y Messenger la reintentará. `pending` todavía no se ha intentado.
          x-translations:
            en:
              description: Delivery state. `success` arrived. `failed` is the latest attempt's outcome; Messenger can schedule another one for a recoverable failure. `retrying` was deferred by the outbound limit and Messenger will retry it. `pending` has not been attempted yet.
        - name: event_type
          in: query
          schema:
            type: string
            enum:
              - validation.completed
              - validation.failed
              - validation.error
              - validation.retry.scheduled
              - validation.retry.resolved
              - validation.retry.exhausted
              - validation.returned
              - billing.payment_succeeded
              - billing.payment_failed
              - billing.trial_will_end
              - billing.subscription_canceled
              - billing.invoice_upcoming
              - test
          example: validation.completed
          description: |-

            Filtro — Tipo de evento que originó la entrega. **De validaciones**: `validation.completed` cuando termina bien, `validation.failed` cuando termina mal y `validation.error` cuando hay un error del servicio. **De los reintentos automáticos:** `validation.retry.scheduled` al programar un reintento automático, `validation.retry.resolved` cuando un reintento automático encuentra el CEP, y `validation.retry.exhausted` cuando los reintentos se agotan sin encontrar el CEP. **De la revisión posterior de un `valid`:** `validation.returned` cuando una validación que había salido `valid` pasa a `returned` porque Banxico reportó la devolución.\
            **De la suscripción:** `billing.payment_succeeded` y `billing.payment_failed` por cada cobro, `billing.invoice_upcoming` antes de la siguiente factura, `billing.trial_will_end` antes de que acabe la prueba gratuita, y `billing.subscription_canceled` al cancelar la suscripción. Las entregas sintéticas (envío de prueba) se marcan como `test`.
          x-translations:
            en:
              description: |-
                Type of event that produced the delivery.
                From a validation's lifecycle: `validation.completed` when it ends well, `validation.failed` when it ends badly, and `validation.error` when it fails on the service side. From its automatic retries: `validation.retry.scheduled` when one is scheduled, `validation.retry.resolved` when a retry ends up finding the receipt, and `validation.retry.exhausted` when they run out without success. From the follow-up check of a `valid`: `validation.returned` when a validation that had come out `valid` moves to `returned` because Banxico reported the return.
                From the subscription: `billing.payment_succeeded` and `billing.payment_failed` for each charge, `billing.invoice_upcoming` before the next invoice, `billing.trial_will_end` before the trial runs out, and `billing.subscription_canceled` when it is cancelled.
                `test` marks the synthetic deliveries from the test send.
        - name: format
          in: query
          required: false
          schema:
            type: string
            default: csv
            enum:
              - csv
              - xlsx
          example: csv
          description: |-

            Formato del archivo a descargar. Valores aceptados: `csv` y `xlsx`. Cualquier otro valor se sirve como `csv`.
          x-translations:
            en:
              description: Output format. `csv` by default; `xlsx` produces a spreadsheet. Any other value is served as `csv`.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100000
          example: 500
          description: 'Filtro — Recorta el número de filas del archivo. Solo baja el tope: un valor mayor que 100 000, o menor que 1, deja el tope por defecto.'
          x-translations:
            en:
              description: 'Trims the number of rows in the file. It only lowers the cap: a value above 100,000, or below 1, leaves the default cap in place.'
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/webhooks/deliveries/export' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Archivo CSV o XLSX con el historial consolidado de entregas de todos los endpoints webhook (hasta 100 000 filas).
          x-translations:
            en:
              description: CSV or XLSX file with the consolidated delivery history across all endpoints (up to 100,000 rows).
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-translations:
        en:
          summary: Export all deliveries as CSV or XLSX
          description: |-
            Downloads the delivery history of **every** endpoint on the account as a
            file, under the same filters as the list: endpoint, status, and event
            type.

            {% concept slug="exports-and-formats" %}The format is
            chosen{% /concept %} with `?format`: `csv` by default, `xlsx` as the
            alternative — and the cap is 100,000 rows, which `?limit` can lower.

            Exporting before deleting an endpoint is worth doing:
            `DELETE /v1/webhooks/{id}` takes its history with it and there is no way
            to recover it.
      security:
        - ApiKeyAuth: []
  /plans/public:
    get:
      x-related:
        - GET /v1/plans/public/comparison
      tags:
        - Plans
      summary: Listar el catálogo público de planes
      description: |-
        Devuelve el catálogo de planes para las superficies sin autenticación
        (la landing `/pricing` y los widgets embebibles).

        La operación es pública, tiene límite de tasa por IP y se almacena en caché
        durante 5 minutos. Sólo incluye planes activos y públicos (`is_active = 1`
        e `is_public = 1`); cada uno lleva sus `prices[]` activos en el modo de cobro
        vigente y sus `features[]` bilingües activas.

        No todos los planes se venden en todos los intervalos: un plan sin precio
        anual no trae filas con `billing_interval: year`. Ofrece sólo las
        combinaciones que aparecen en `prices[]`.

        {% callout type="info" %}
        La respuesta no expone `stripe_product_id`, `stripe_price_id`, `user_count`
        ni marcas de tiempo internas. El servidor resuelve el `stripe_price_id` al
        crear la Checkout Session.

        {% /callout %}
      operationId: listPublicPlans
      x-translations:
        en:
          summary: Public plan catalog (landing)
          description: |-
            Returns the plan catalog for unauthenticated surfaces (the `/pricing`
            landing and embeddable widgets).

            The operation is public, IP rate-limited, and cached for 5 minutes. It
            only includes active and public plans (`is_active = 1` and
            `is_public = 1`); each carries its active `prices[]` in the current
            billing mode and active bilingual `features[]`.

            Not every plan is sold in every interval: a plan with no annual price
            carries no `billing_interval: year` rows. Offer only the combinations
            that appear in `prices[]`.

            {% callout type="info" %}
            The response does not expose `stripe_product_id`, `stripe_price_id`,
            `user_count`, or internal timestamps. The server resolves the
            `stripe_price_id` when creating the Checkout Session.

            {% /callout %}
      security: []
      x-codeSamples:
        - lang: curl
          label: Shell
          source: curl -X GET 'https://api.veriko.mx/v1/plans/public'
      responses:
        '200':
          description: Planes activos y públicos con precios y características activas embebidas.
          x-translations:
            en:
              description: Active and public plans with active prices and features embedded.
          headers:
            Cache-Control:
              schema:
                type: string
              description: 'Directiva de caché pública: `public, max-age=300`.'
              x-translations:
                en:
                  description: 'Public cache directive: `public, max-age=300`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListPublicPlansResponse'
              examples:
                catalog:
                  summary: Catálogo con plan gratuito + plan Pro
                  x-translations:
                    en:
                      summary: Catalog with free + Pro plan
                  value:
                    data:
                      - type: plan
                        id: free
                        attributes:
                          slug: free
                          name: Gratis
                          billing_model: free
                          monthly_validation_limit: 50
                          included_validations: 50
                          trial_days: 0
                          is_default: true
                          sort_order: 0
                          prices: []
                          features:
                            - text_es: 50 validaciones al mes
                              text_en: 50 validations per month
                              icon: check
                              sort_order: 10
                              is_active: true
                      - type: plan
                        id: pro
                        attributes:
                          slug: pro
                          name: Pro
                          billing_model: hybrid
                          monthly_validation_limit: 10000
                          included_validations: 500
                          trial_days: 14
                          is_default: false
                          sort_order: 2
                          prices:
                            - currency: MXN
                              billing_interval: month
                              kind: base
                              unit_amount: 49900
                              tax_behavior: inclusive
                            - currency: MXN
                              billing_interval: month
                              kind: metered_overage
                              unit_amount: 100
                              tax_behavior: inclusive
                          features:
                            - text_es: 500 validaciones incluidas + overage opcional
                              text_en: 500 included validations + optional overage
                              icon: check
                              sort_order: 10
                              is_active: true
        '429':
          $ref: '#/components/responses/RateLimited'
  /plans/public/comparison:
    get:
      x-related:
        - GET /v1/plans/public
      tags:
        - Plans
      summary: Obtener la tabla comparativa de planes
      description: |-
        Devuelve la tabla «Compara los planes» de `/pricing` como datos
        estructurados.

        La operación es pública, tiene límite de tasa por IP y se almacena en caché
        durante 5 minutos.

        Las columnas (`plans`) son los mismos planes activos y públicos del
        catálogo, en el mismo orden, por lo que la comparativa no diverge de las
        tarjetas. Las filas (`rows`) son dimensiones bilingües; cada celda es
        booleana (incluido / no incluido) o de texto bilingüe.
      operationId: getPublicPlanComparison
      x-translations:
        en:
          summary: Get the plan comparison table
          description: |-
            Returns the `/pricing` “Compare plans” table as structured data.

            The operation is public, IP rate-limited, and cached for 5 minutes.

            The columns (`plans`) are the same active and public plans as the
            catalog, in the same order, so the comparison does not diverge from the
            cards. Rows (`rows`) are bilingual dimensions; each cell is either
            boolean (included / not included) or bilingual text.
      security: []
      x-codeSamples:
        - lang: curl
          label: Shell
          source: curl -X GET 'https://api.veriko.mx/v1/plans/public/comparison'
      responses:
        '200':
          description: Matriz comparativa de los planes visibles.
          x-translations:
            en:
              description: Comparison matrix for the visible plans.
          headers:
            Cache-Control:
              schema:
                type: string
              description: 'Directiva de caché pública: `public, max-age=300`.'
              x-translations:
                en:
                  description: 'Public cache directive: `public, max-age=300`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanComparisonResponse'
              examples:
                comparison:
                  summary: Dos planes públicos, celdas de texto y booleanas
                  x-translations:
                    en:
                      summary: Two public plans, text and boolean cells
                  value:
                    data:
                      type: plan_comparison
                      attributes:
                        plans:
                          - slug: free
                            name: Free
                            is_default: true
                          - slug: basic
                            name: Basic
                            is_default: false
                        rows:
                          - key: validations_included
                            label_es: Validaciones incluidas por mes
                            label_en: Validations included per month
                            cells:
                              free:
                                type: text
                                value: null
                                text_es: 50 al mes
                                text_en: 50 per month
                              basic:
                                type: text
                                value: null
                                text_es: 500 + uso extra
                                text_en: 500 + overage
                          - key: manual_ocr
                            label_es: Validación manual y con foto (OCR)
                            label_en: Manual and photo (OCR) validation
                            cells:
                              free:
                                type: bool
                                value: true
                                text_es: null
                                text_en: null
                              basic:
                                type: bool
                                value: true
                                text_es: null
                                text_en: null
  /usage/summary:
    get:
      x-related:
        - GET /v1/usage/breakdown
        - GET /v1/usage/heatmap
        - GET /v1/usage/history
        - GET /v1/usage/limits
        - GET /v1/api/usage
        - GET /v1/api/usage/export
      tags:
        - Usage
      summary: Consultar la cuota de validaciones para el plan del usuario
      description: |-
        Devuelve la cuota de validaciones del plan asignado al usuario autenticado:

        - El límite del plan.
        - Las validaciones consumidas.
        - Las validaciones restantes.
        - El porcentaje del límite consumido.

        El campo `tone` señala el nivel de aviso según ese porcentaje: `ok` por
        debajo del 70 %, `warn` entre el 70 % y el 89 %, y `danger` a partir del
        90 % o con la cuota agotada.

        `limit` es el del plan vigente, que devuelve
        [`GET /v1/billing/subscription`](/es/v1/billing/billing-get-subscription), y
        la marca de reinicio (`resets_at`) es el fin del ciclo de cuota: coincide con
        el `current_period_end` de esa suscripción salvo en un plan anual, donde la
        cuota se reinicia cada mes. `next_reset_at` es un alias de `resets_at`: las
        dos marcan el fin del ciclo de cuota en curso.

        Los límites de tasa, que no son cuota, los devuelve
        [`GET /v1/usage/limits`](/es/v1/usage/get-usage-limits).
      operationId: getUsageSummary
      externalDocs:
        url: https://docs.veriko.mx/how-to/monitor-usage
        description: Track API quota and billing-cycle consumption
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/usage/summary' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Instantánea de la cuota del mes en curso, con el porcentaje consumido y su nivel de aviso.
          x-translations:
            en:
              description: Snapshot of the current month's quota, with the percentage consumed and the alert tone.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          type:
                            type: string
                            enum:
                              - usage_summary
                          attributes:
                            $ref: '#/components/schemas/UsageSummary'
              examples:
                normal_usage:
                  summary: Uso normal — tono ok (42 % consumido)
                  x-translations:
                    en:
                      summary: Normal usage — ok tone (42% consumed)
                  value:
                    data:
                      type: usage_summary
                      attributes:
                        plan_slug: basic
                        plan_name: Basic
                        limit: 1000
                        used: 420
                        remaining: 580
                        used_percent: 42
                        tone: ok
                        resets_at: '2025-03-01T00:00:00Z'
                        next_reset_at: '2025-03-01T00:00:00Z'
                    meta:
                      version: 1.51.0
                      request_id: f8a9b0c1d2e3
                near_limit:
                  summary: Cerca del límite — tono danger (92 % consumido)
                  x-translations:
                    en:
                      summary: Near limit — danger tone (92% consumed)
                  value:
                    data:
                      type: usage_summary
                      attributes:
                        plan_slug: basic
                        plan_name: Basic
                        limit: 1000
                        used: 920
                        remaining: 80
                        used_percent: 92
                        tone: danger
                        resets_at: '2025-03-01T00:00:00Z'
                        next_reset_at: '2025-03-01T00:00:00Z'
                    meta:
                      version: 1.51.0
                      request_id: a9b0c1d2e3f4
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-translations:
        en:
          summary: Current monthly quota summary
          description: |-
            Returns the validation quota of the plan assigned to the authenticated
            user:

            - The plan limit.
            - The validations consumed.
            - The validations remaining.
            - The percentage of the limit consumed.

            The `tone` field signals the alert level by that percentage: `ok` below
            70%, `warn` between 70% and 89%, and `danger` from 90% or with the quota
            exhausted.

            `limit` is the active plan's, returned by
            [`GET /v1/billing/subscription`](/en/v1/billing/billing-get-subscription),
            and the reset timestamp (`resets_at`) is the end of the quota cycle: it
            matches that subscription's `current_period_end` except on an annual
            plan, where quota resets every month. `next_reset_at` is an alias of
            `resets_at`: both mark the end of the current quota cycle.

            Rate limits — which are not quota — are returned by [`GET
            /v1/usage/limits`](/en/v1/usage/get-usage-limits).
      security:
        - ApiKeyAuth: []
  /usage/history:
    get:
      x-related:
        - GET /v1/usage/breakdown
        - GET /v1/usage/heatmap
        - GET /v1/usage/limits
        - GET /v1/usage/summary
        - GET /v1/api/usage
        - GET /v1/api/usage/export
      tags:
        - Usage
      summary: Consultar índices de consumo mes a mes
      description: |-
        Devuelve el consumo mensual de validaciones del usuario autenticado en los
        últimos `months` meses, de más reciente a más antiguo.

        {% callout type="warning" %}
        El límite que acompaña a cada fila es el de hoy, no el que regía aquel mes:
        el historial de límites no se conserva.
        {% /callout %}
      operationId: getUsageHistory
      externalDocs:
        url: https://docs.veriko.mx/how-to/monitor-usage
        description: Track API quota and billing-cycle consumption
      parameters:
        - name: months
          in: query
          description: Número de meses a incluir en el historial (hacia atrás respecto al momento de la consulta).
          x-translations:
            en:
              description: Number of months to include in the history (counting back from the moment of the query).
          schema:
            type: integer
            minimum: 1
            maximum: 24
            default: 6
          example: 6
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/usage/history' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Historial de consumo mensual ordenado de más reciente a más antiguo.
          x-translations:
            en:
              description: Monthly usage history ordered from most to least recent.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - $ref: '#/components/schemas/GetUsageHistoryAttributes'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-translations:
        en:
          summary: Monthly usage history
          description: |-
            Returns the authenticated user's monthly validation usage over the last
            `months` months, most recent first.

            {% callout type="warning" %}
            The limit shown on each row is today's, not the one that applied that
            month: limit history is not retained.
            {% /callout %}
      security:
        - ApiKeyAuth: []
  /usage/breakdown:
    get:
      x-related:
        - GET /v1/usage/heatmap
        - GET /v1/usage/history
        - GET /v1/usage/limits
        - GET /v1/usage/summary
        - GET /v1/api/usage
        - GET /v1/api/usage/export
      tags:
        - Usage
      summary: Desglosar consumo por operación
      description: |-
        Desglosa el consumo del periodo indicado por tipo de operación contabilizada (`validation`, `validation_ocr`, etc.).

        El parámetro `period` acepta `current` para el mes en curso o una cadena `YYYY-MM` para un mes específico.
      operationId: getUsageBreakdown
      externalDocs:
        url: https://docs.veriko.mx/how-to/monitor-usage
        description: Track API quota and billing-cycle consumption
      parameters:
        - name: period
          in: query
          description: Período a desglosar. Acepta `current` para el mes en curso o una cadena `YYYY-MM` para un mes específico.
          x-translations:
            en:
              description: Period to break down. Accepts `current` for the running month or a `YYYY-MM` string for a specific month.
          schema:
            type: string
            example: current
          example: current
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/usage/breakdown' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Conteos de consumo por tipo de operación para el período solicitado.
          x-translations:
            en:
              description: Consumption counts by operation type for the requested period.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - $ref: '#/components/schemas/GetUsageBreakdownAttributes'
        '400':
          description: El formato del parámetro `period` no es válido.
          x-translations:
            en:
              description: The `period` parameter format is invalid.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-translations:
        en:
          summary: Usage breakdown by operation
          description: |-
            Breaks down the indicated period's usage by billable operation type
            (`validation`, `validation_ocr`, etc.).

            The `period` parameter accepts `current` for the running month or
            `YYYY-MM` for a specific month.
      security:
        - ApiKeyAuth: []
  /usage/limits:
    get:
      x-related:
        - GET /v1/usage/breakdown
        - GET /v1/usage/heatmap
        - GET /v1/usage/history
        - GET /v1/usage/summary
        - GET /v1/api/usage
        - GET /v1/api/usage/export
      tags:
        - Usage
      summary: Consultar los límites de tasa (rate-limits)
      description: |-
        Devuelve los límites de tasa aplicables al usuario autenticado, por contexto.

        Los contextos no llevan los mismos límites. `api` limita las peticiones
        autenticadas por clave y por minuto, con el valor del plan de la cuenta
        (`key_per_minute`), y suma un techo por IP (`ip_ceiling_per_minute`); las
        peticiones sin clave se limitan por IP (`ip_per_minute`). `webhooks` fija las
        entregas por minuto que la cuenta puede enviar hacia un mismo host destino,
        según su plan (`host_per_minute`), y el tope de todos los clientes juntos
        sobre ese host (`host_global_per_minute`). `login` limita por IP y por correo
        electrónico, cada uno por minuto y por hora.

        No devuelve contadores en vivo, sólo la configuración vigente. Estos límites
        son ajenos a la cuota mensual de validaciones, que devuelve
        [`GET /v1/usage/summary`](/es/v1/usage/get-usage-summary). El consumo de la
        ventana en curso lo informan, en cada respuesta autenticada de la API, las
        cabeceras `X-RateLimit-*`.

        {% callout type="note" %}
        Al superarse uno de estos límites, la operación afectada responde con estado
        HTTP `429`, la cabecera `Retry-After` y las cabeceras `X-RateLimit-Limit`,
        `X-RateLimit-Remaining` y `X-RateLimit-Reset`.
        {% /callout %}
      operationId: getUsageLimits
      externalDocs:
        url: https://docs.veriko.mx/how-to/monitor-usage
        description: Track API quota and billing-cycle consumption
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/usage/limits' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Configuración estática de límites de tasa por contexto.
          x-translations:
            en:
              description: Static rate limit configuration by context.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - $ref: '#/components/schemas/GetUsageLimitsAttributes'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-translations:
        en:
          summary: User-visible rate limits
          description: |-
            Returns the rate limits that apply to the authenticated user, by
            context.

            The contexts do not carry the same limits. `api` limits authenticated
            requests per key and per minute, with the value of the account plan
            (`key_per_minute`), and adds a per-IP ceiling (`ip_ceiling_per_minute`);
            requests without a key are limited per IP (`ip_per_minute`). `webhooks`
            sets the deliveries per minute the account may send to a single
            destination host, per its plan (`host_per_minute`), and the cap for all
            customers together on that host (`host_global_per_minute`). `login`
            limits per IP and per email, each one per minute and per hour.

            It does not return live counters, only the current configuration. These
            limits are unrelated to the monthly validation quota, which is returned
            by [`GET /v1/usage/summary`](/en/v1/usage/get-usage-summary). The
            consumption of the current window is reported, on every authenticated
            API response, by the `X-RateLimit-*` headers.

            {% callout type="note" %}
            When one of these limits is exceeded, the affected operation responds
            with HTTP status `429`, the `Retry-After` header and the
            `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`
            headers.
            {% /callout %}
      security:
        - ApiKeyAuth: []
  /usage/heatmap:
    get:
      x-related:
        - GET /v1/usage/breakdown
        - GET /v1/usage/history
        - GET /v1/usage/limits
        - GET /v1/usage/summary
        - GET /v1/api/usage
        - GET /v1/api/usage/export
      tags:
        - Usage
      summary: Obtener el mapa de calor de validaciones por día y hora
      description: |-
        Devuelve las validaciones del usuario agrupadas por día de la semana y hora
        del día, para los últimos `days` días (máximo 90).

        Sólo se devuelven las combinaciones con al menos una validación: el cliente
        rellena la rejilla de 7×24 poniendo a cero las celdas que falten.
      operationId: getUsageHeatmap
      externalDocs:
        url: https://docs.veriko.mx/how-to/monitor-usage
        description: Track API quota and billing-cycle consumption
      parameters:
        - name: days
          in: query
          description: Número de días a incluir en el mapa de calor.
          x-translations:
            en:
              description: Number of days to include in the heatmap.
          schema:
            type: integer
            minimum: 1
            maximum: 90
            default: 30
          example: 30
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/usage/heatmap' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Datos del mapa de calor agrupados por día y hora, incluyendo la cantidad de registros en cada intervalo.
          x-translations:
            en:
              description: Heatmap data grouped by day and hour, with the number of records in each interval.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetUsageHeatmapResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-translations:
        en:
          summary: Validation heatmap by day and hour
          description: |-
            Returns the user's validations grouped by day of week and hour of day,
            over the last `days` days (max 90).

            Only combinations with at least one validation are returned: the client
            fills in the 7×24 grid with the missing cells at zero.
      security:
        - ApiKeyAuth: []
  /insights/overview:
    get:
      x-related:
        - GET /v1/insights/top-banks
        - GET /v1/insights/top-beneficiaries
        - GET /v1/insights/trends
      tags:
        - Insights
      summary: Consultar las métricas de validaciones de la cuenta de usuario
      description: |-
        Devuelve las métricas de validaciones para el usuario autenticado:

        - Total de validaciones
        - Validaciones de hoy y de ayer
        - Tasa de éxito
        - Latencia promedio de las últimas 24 horas

        Incluye la distribución de veredictos de Banxico de los últimos 7 días.
      operationId: getUserInsightsOverview
      externalDocs:
        url: https://docs.veriko.mx/how-to/analyze-insights
        description: Analyze validation trends and counterparty patterns
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/insights/overview' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: KPIs propios del usuario y distribución de veredictos de Banxico de los últimos 7 días.
          x-translations:
            en:
              description: User-scoped KPIs and Banxico verdict distribution for the last 7 days.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          type:
                            type: string
                            example: insights_overview
                          attributes:
                            $ref: '#/components/schemas/InsightsOverviewAttributes'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-translations:
        en:
          summary: View the account's validation metrics
          description: |-
            Returns the validation metrics for the authenticated user:

            - Total validations
            - Today's and yesterday's validations
            - Success rate
            - Average latency over the last 24 hours

            Includes the Banxico verdict distribution for the last 7 days.
      security:
        - ApiKeyAuth: []
  /insights/trends:
    get:
      x-related:
        - GET /v1/insights/overview
        - GET /v1/insights/top-banks
        - GET /v1/insights/top-beneficiaries
      tags:
        - Insights
      summary: Obtener las series temporales de validaciones de la cuenta de usuario
      description: |-
        Devuelve una serie temporal que representa las validaciones del usuario autenticado, según el rango y la métrica seleccionados.

        El tamaño del intervalo varía con el rango:

        - `24h`: por hora.
        - `7d` y `30d`: por día.
        - `90d`: por semana (con inicio en lunes).

        La métrica `volume` desglosa entre validaciones manuales (`direct`) y por OCR (`ocr`).

        La métrica `latency` devuelve los percentiles p50, p95 y p99.
      operationId: getUserInsightsTrends
      externalDocs:
        url: https://docs.veriko.mx/how-to/analyze-insights
        description: Analyze validation trends and counterparty patterns
      parameters:
        - name: range
          in: query
          schema:
            type: string
            enum:
              - 24h
              - 7d
              - 30d
              - 90d
            default: 7d
          example: 7d
          description: |-
            Filtro — Ventana temporal que cubre la serie: `24h` (últimas 24 horas), `7d` (últimos 7 días), `30d` (últimos 30 días) o `90d` (últimos 90 días).

            El tamaño del intervalo se ajusta a la ventana, de modo que dos consultas con ventanas distintas no son comparables punto por punto.
          x-translations:
            en:
              description: |-
                Filter — Time window covered by the series: `24h` (last 24 hours), `7d` (last 7 days), `30d` (last 30 days), or `90d` (last 90 days).

                The interval size scales with the window, so two queries over different windows are not comparable point by point.
        - name: metric
          in: query
          schema:
            type: string
            enum:
              - volume
              - latency
            default: volume
          example: volume
          description: Filtro — Serie que se devuelve. Con `volume`, cada punto contiene la cantidad de validaciones; con `latency`, los percentiles del tiempo de procesamiento de las validaciones que tienen esa medición.
          x-translations:
            en:
              description: Filter — Series to return. With `volume`, each point contains the validation count; with `latency`, the processing-time percentiles for validations that have that measurement.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/insights/trends' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Intervalos de la serie temporal para el rango y la métrica solicitados.
          x-translations:
            en:
              description: Time-series intervals for the requested range and metric.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          type:
                            type: string
                            example: insights_trends
                          attributes:
                            $ref: '#/components/schemas/InsightsTrendsAttributes'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-translations:
        en:
          summary: Get the account's validation time series
          description: |-
            Returns a time series representing the authenticated user's validations, according to the selected range and metric.

            The interval size varies with the range:

            - `24h`: hourly.
            - `7d` and `30d`: daily.
            - `90d`: weekly, starting on Monday.

            The `volume` metric breaks down into manual (`direct`) and OCR (`ocr`) validations.

            The `latency` metric returns the p50, p95, and p99 percentiles.
      security:
        - ApiKeyAuth: []
  /insights/top-banks:
    get:
      x-related:
        - GET /v1/insights/overview
        - GET /v1/insights/top-beneficiaries
        - GET /v1/insights/trends
      tags:
        - Insights
      summary: Listar los bancos de la cuenta de usuario por volumen de validaciones o tasa de error
      description: Lista los bancos usados en las validaciones del usuario autenticado durante los últimos 30 días, ordenados por volumen total o por tasa de error.
      operationId: getUserInsightsTopBanks
      externalDocs:
        url: https://docs.veriko.mx/how-to/analyze-insights
        description: Analyze validation trends and counterparty patterns
      parameters:
        - name: metric
          in: query
          schema:
            type: string
            enum:
              - volume
              - errors
            default: volume
          example: volume
          description: Filtro — Criterio de ordenación. `volume` pone arriba los bancos con más validaciones; `errors`, los de mayor tasa de error.
          x-translations:
            en:
              description: Filter — Sorting criterion. `volume` puts the banks with the most validations on top; `errors`, those with the highest error rate.
        - name: limit
          in: query
          description: Filtro — Número máximo de bancos a devolver.
          x-translations:
            en:
              description: Filter — Maximum number of banks to return.
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
          example: 10
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/insights/top-banks' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Bancos del usuario ordenados según la métrica solicitada (últimos 30 días).
          x-translations:
            en:
              description: User's banks ordered by the requested metric over the last 30 days.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          type:
                            type: string
                            example: insights_top_banks
                          attributes:
                            $ref: '#/components/schemas/InsightsTopBanksAttributes'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-translations:
        en:
          summary: List the account's banks by validation volume or error rate
          description: Lists the banks used in the authenticated user's validations over the last 30 days, ranked by total volume or by error rate.
      security:
        - ApiKeyAuth: []
  /insights/top-beneficiaries:
    get:
      x-related:
        - GET /v1/insights/overview
        - GET /v1/insights/top-banks
        - GET /v1/insights/trends
      tags:
        - Insights
      summary: Listar los beneficiarios más frecuentes en las validaciones de la cuenta de usuario
      description: |-
        Lista los beneficiarios detectados con mayor frecuencia en las validaciones del usuario autenticado durante los últimos 90 días.

        Cada entrada incluye la etiqueta del beneficiario, el banco receptor y los últimos 4 dígitos de la cuenta.
      operationId: getUserInsightsTopBeneficiaries
      externalDocs:
        url: https://docs.veriko.mx/how-to/analyze-insights
        description: Analyze validation trends and counterparty patterns
      parameters:
        - name: limit
          in: query
          description: Filtro — Número máximo de beneficiarios a devolver.
          x-translations:
            en:
              description: Filter — Maximum number of beneficiaries to return.
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
          example: 10
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/insights/top-beneficiaries' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Beneficiarios más frecuentes en las validaciones de la cuenta de usuario (últimos 90 días).
          x-translations:
            en:
              description: Most frequent beneficiaries in the account's validations over the last 90 days.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          type:
                            type: string
                            enum:
                              - insights_top_beneficiaries
                          attributes:
                            $ref: '#/components/schemas/InsightsTopBeneficiariesAttributes'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-translations:
        en:
          summary: List the most frequent beneficiaries in the account's validations
          description: |-
            Lists the beneficiaries detected most frequently in the authenticated user's validations over the last 90 days.

            Each entry includes the beneficiary label, the receiving bank, and the last 4 digits of the account.
      security:
        - ApiKeyAuth: []
  /finance/summary:
    get:
      tags:
        - Finance
      summary: Consultar el resumen financiero del mes
      description: |-
        Devuelve {% concept slug="finance" %}los KPIs del mes{% /concept %} para el panel /finanzas:

        - Conteos por veredicto
        - Agregados monetarios (volumen total / verificado / no verificado, ticket promedio, mediana, mínimo, máximo, share verificado)
        - Tasa de éxito
        - Tiempo de procesamiento promedio
        - Desglose diario
        - Contrapartes y bancos emisores/receptores
        - Distribución de veredicto y comparativa con el mes anterior (deltas porcentuales, `null` cuando el mes previo tuvo base cero)

        "Verificado" significa que Banxico devolvió `valid` y el CEP fue encontrado. Cualquier otro veredicto cuenta como no verificado.
      operationId: getFinanceSummary
      externalDocs:
        url: https://docs.veriko.mx/how-to/generate-finance-report
        description: Export financial statements and CEP archives
      parameters:
        - name: month
          in: query
          required: true
          description: Mes a consultar en formato `YYYY-MM`.
          x-translations:
            en:
              description: Month to query in `YYYY-MM` format.
          schema:
            type: string
            pattern: ^\d{4}-\d{2}$
            example: 2026-04
          example: 2026-04
        - name: user_id
          in: query
          description: Solo para administradores — UUID del usuario a consultar.
          x-translations:
            en:
              description: Administrators only — UUID of the user to query.
          schema:
            type: string
            format: uuid
            example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/finance/summary' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: KPIs mensuales del usuario con comparativa al mes anterior.
          x-translations:
            en:
              description: Monthly KPI blob for the user, including comparison against the previous month.
          headers:
            Cache-Control:
              description: Directiva de caché privada.
              x-translations:
                en:
                  description: Private cache directive with stale-while-revalidate.
              schema:
                type: string
                example: private, max-age=60, stale-while-revalidate=60
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceSummaryResponse'
        '400':
          $ref: '#/components/responses/InvalidMonth'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      x-related:
        - GET /v1/finance/statement
        - GET /v1/finance/ceps
        - GET /v1/finance/by-bank
        - GET /v1/finance/counterparties
      x-translations:
        en:
          summary: Monthly financial summary (KPIs)
          description: |-
            Returns {% concept slug="finance" %}the month's KPIs{% /concept %} for the /finanzas panel:

            - Counts by verdict
            - Monetary aggregates (total / verified / unverified volume, average ticket, median, minimum, maximum, verified share)
            - Success rate
            - Average processing time
            - Daily breakdown
            - Counterparties and sending/receiving banks
            - Verdict distribution and comparison with the previous month (percentage deltas, `null` when the previous month had a zero base)

            "Verified" means Banxico returned `valid` and the CEP was found. Any other verdict counts as unverified.
      security:
        - ApiKeyAuth: []
  /finance/statement:
    get:
      tags:
        - Finance
      summary: Obtener el estado de cuenta mensual (PDF, XLSX, CSV o HTML)
      description: |-
        Genera el estado de cuenta financiero del mes {% concept slug="exports-and-formats" %}en cuatro formatos{% /concept %}:

        - `format=pdf` (predeterminado): PDF estilo extracto bancario con resumen de portafolio, distribución de veredicto, top contrapartes y bancos, detalle diario y hasta 1,000 transacciones en el anexo.
        - `format=xlsx`: libro con 6 hojas (Resumen, Detalle diario, Top contrapartes, Top bancos, Veredicto, Verificaciones). La hoja Verificaciones lleva el historial completo del mes sin tope.
        - `format=csv`: CSV particionado en secciones `[HEADER]`, `[KPIS]`, `[TRANSACTIONS]` y `[TOTALS]`. El orden de las secciones es estable, que es lo que permite parsear por marcador.
        - `format=html`: mismo contenido del PDF, servido inline para impresión desde el navegador.

        Todas las variantes devuelven la cabecera `X-Finance-Folio` con el folio determinístico del periodo.
      operationId: getFinanceStatement
      externalDocs:
        url: https://docs.veriko.mx/how-to/generate-finance-report
        description: Export financial statements and CEP archives
      parameters:
        - name: month
          in: query
          required: true
          description: Mes del estado de cuenta en formato `YYYY-MM`.
          x-translations:
            en:
              description: Statement month in `YYYY-MM` format.
          schema:
            type: string
            pattern: ^\d{4}-\d{2}$
            example: 2026-04
          example: 2026-04
        - name: format
          in: query
          schema:
            type: string
            enum:
              - pdf
              - xlsx
              - csv
              - html
            default: pdf
          example: pdf
          description: 'Formato de archivo solicitado para descarga. Aceptados: `pdf`, `xlsx`, `csv` o `html`.'
          x-translations:
            en:
              description: 'Requested file format for the download. Accepted: `pdf`, `xlsx`, `csv` or `html`.'
        - name: user_id
          in: query
          description: Solo para administradores — UUID del usuario a consultar.
          x-translations:
            en:
              description: Administrators only — UUID of the user to query.
          schema:
            type: string
            format: uuid
            example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/finance/statement' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Stream del estado de cuenta en el formato solicitado.
          x-translations:
            en:
              description: Statement stream in the requested format.
          headers:
            Content-Disposition:
              description: Nombre de archivo sugerido para la descarga.
              x-translations:
                en:
                  description: Suggested filename for the download.
              schema:
                type: string
                example: attachment; filename="platform_estado_cuenta_2026-04.pdf"
            X-Finance-Folio:
              description: Folio determinístico del periodo. El mismo par usuario+mes produce siempre el mismo folio.
              x-translations:
                en:
                  description: Deterministic folio for the period. The same user+month pair always yields the same folio.
              schema:
                type: string
                example: A3F12B9C0D4E
            X-Frame-Options:
              schema:
                type: string
                enum:
                  - DENY
              description: —
              x-translations:
                en:
                  description: —
            Cache-Control:
              schema:
                type: string
                example: no-cache, no-store, must-revalidate
              description: Deshabilita el almacenamiento en caché por parte de intermediarios durante la descarga.
              x-translations:
                en:
                  description: Disables intermediary caching of the download (deterministic folio, but sensitive content).
          content:
            application/pdf:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
            text/csv:
              schema:
                type: string
                format: binary
            text/html:
              schema:
                type: string
        '400':
          $ref: '#/components/responses/InvalidMonth'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          description: '`pdf_render_failed`, `xlsx_render_failed` o `csv_stream_failed`: Reintentar con otro formato.'
          x-translations:
            en:
              description: '`pdf_render_failed`, `xlsx_render_failed` or `csv_stream_failed`: retry with another format.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-related:
        - GET /v1/finance/summary
        - GET /v1/finance/ceps
        - GET /v1/finance/monthly
      x-translations:
        en:
          summary: Monthly statement (PDF / XLSX / CSV / HTML)
          description: |-
            Generates the month's financial statement {% concept slug="exports-and-formats" %}in four formats{% /concept %}:

            - `format=pdf` (default): bank-statement-style PDF with portfolio summary, verdict distribution, top counterparties and banks, daily breakdown and up to 1,000 transactions in the appendix.
            - `format=xlsx`: workbook with 6 sheets (Resumen, Detalle diario, Top contrapartes, Top bancos, Veredicto, Verificaciones). The Verificaciones sheet carries the month's full history with no cap.
            - `format=csv`: CSV partitioned into `[HEADER]`, `[KPIS]`, `[TRANSACTIONS]` and `[TOTALS]` sections. The section order is stable, which is what makes parsing by marker possible.
            - `format=html`: same content as the PDF, served inline for printing from the browser.

            Every variant returns the `X-Finance-Folio` header with the period's deterministic folio.
      security:
        - ApiKeyAuth: []
  /finance/monthly:
    get:
      x-related:
        - GET /v1/finance/accounting
        - GET /v1/finance/by-bank
        - GET /v1/finance/ceps
        - GET /v1/finance/counterparties
        - GET /v1/finance/statement
        - GET /v1/finance/summary
      tags:
        - Finance
      summary: Obtener el informe mensual de verificaciones (CSV, XLSX o PDF)
      description: |-
        Devuelve el listado (en formato compatible con el SAT) de las verificaciones del mes en 15 columnas:

        - Fecha.
        - Folio plataforma.
        - Tipo.
        - Clave de rastreo.
        - Fecha operación.
        - Monto.
        - Banco emisor.
        - CLABE emisora.
        - Banco receptor.
        - CLABE receptora.
        - Beneficiario.
        - Veredicto Banxico.
        - CEP disponible.
        - Folio CEP.
        - Tiempo (ms).

        El informe puede ser solicitado en alguno de los siguientes formatos: `format=csv` (predeterminado), `format=xlsx`, `format=pdf` (máximo 1000 filas).\
        Además se puede usar `format=preview` para obtener las primeras 20 filas como JSON.

        Para más información sobre exportaciones consulta {% concept slug="exports-and-formats" %}exportaciones y formatos{% /concept %}.
      operationId: getFinanceMonthly
      externalDocs:
        url: https://docs.veriko.mx/how-to/generate-finance-report
        description: Export financial statements and CEP archives
      parameters:
        - name: month
          in: query
          required: true
          description: Mes a reportar en formato `YYYY-MM`.
          x-translations:
            en:
              description: Month to report in `YYYY-MM` format.
          schema:
            type: string
            pattern: ^\d{4}-\d{2}$
            example: 2026-04
          example: 2026-04
        - name: format
          in: query
          schema:
            type: string
            enum:
              - csv
              - xlsx
              - pdf
              - preview
            default: csv
          example: csv
          description: 'Formato de archivo solicitado para descarga. Aceptados: `csv`, `xlsx`, `pdf` o `preview`.'
          x-translations:
            en:
              description: 'Requested file format for the download. Accepted: `csv`, `xlsx`, `pdf` or `preview`.'
        - name: user_id
          in: query
          description: Solo para administradores — UUID del usuario a consultar.
          x-translations:
            en:
              description: Administrators only — UUID of the user to query.
          schema:
            type: string
            format: uuid
            example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100000
          example: 500
          description: Recorta el número de filas cuando la salida es `csv` o `xlsx`. No afecta al PDF, que lleva su propio tope de 1000 filas.
          x-translations:
            en:
              description: Trims the number of rows when the output is `csv` or `xlsx`. It does not affect the PDF, which carries its own 1000-row cap.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/finance/monthly' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Stream CSV/XLSX/PDF, o envelope JSON en modo vista previa.
          x-translations:
            en:
              description: CSV/XLSX/PDF stream, or JSON envelope in preview mode.
          headers:
            Content-Disposition:
              description: Nombre de archivo sugerido (no aplica al modo vista previa).
              x-translations:
                en:
                  description: Suggested filename (does not apply in preview mode).
              schema:
                type: string
                example: attachment; filename="platform_verificaciones_2026-04.csv"
            Cache-Control:
              description: Deshabilita el almacenamiento en caché por parte de intermediarios durante la descarga (no aplica al modo vista previa).
              x-translations:
                en:
                  description: Disables intermediary caching of the download (does not apply to preview mode).
              schema:
                type: string
                example: no-cache, no-store, must-revalidate
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
            application/pdf:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                $ref: '#/components/schemas/FinancePreviewResponse'
        '400':
          $ref: '#/components/responses/InvalidMonth'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          description: '`pdf_render_failed`: Reintentar con otro formato.'
          x-translations:
            en:
              description: '`pdf_render_failed`: PDF renderer crashed (OOM/font). Try CSV or XLSX.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Monthly validations report (CSV / XLSX / PDF / preview)
          description: |-
            Returns the listing (in SAT-compatible format) of the month's verifications in 15 columns:

            - Fecha.
            - Folio plataforma.
            - Tipo.
            - Clave de rastreo.
            - Fecha operación.
            - Monto.
            - Banco emisor.
            - CLABE emisora.
            - Banco receptor.
            - CLABE receptora.
            - Beneficiario.
            - Veredicto Banxico.
            - CEP disponible.
            - Folio CEP.
            - Tiempo (ms).

            The report can be requested in any of these formats: `format=csv` (default), `format=xlsx`, `format=pdf` (maximum 1000 rows).\
            `format=preview` is also available, to get the first 20 rows as JSON.

            For more about exports see {% concept slug="exports-and-formats" %}exports and formats{% /concept %}.
      security:
        - ApiKeyAuth: []
  /finance/counterparties:
    get:
      x-related:
        - GET /v1/finance/accounting
        - GET /v1/finance/by-bank
        - GET /v1/finance/ceps
        - GET /v1/finance/monthly
        - GET /v1/finance/statement
        - GET /v1/finance/summary
      tags:
        - Finance
      summary: Obtener el resumen por contraparte (CSV, XLSX o PDF)
      description: |-
        Agrupa las verificaciones del mes por cuenta **beneficiaria y banco**, incluye 7 columnas:

        - Cuenta beneficiaria.
        - Banco.
        - Verificaciones.
        - Válidas.
        - Monto total.
        - Primera.
        - Última.

        El resumen puede ser solicitado en alguno de los siguientes formatos: `format=csv` (predeterminado), `format=xlsx`, `format=pdf` (máximo 1000 filas).\
        Además se puede usar `format=preview` para obtener las primeras 20 filas como JSON.

        Para más información sobre exportaciones consulta {% concept slug="exports-and-formats" %}exportaciones y formatos{% /concept %}.
      operationId: getFinanceCounterparties
      externalDocs:
        url: https://docs.veriko.mx/how-to/generate-finance-report
        description: Export financial statements and CEP archives
      parameters:
        - name: month
          in: query
          required: true
          description: Mes a reportar en formato `YYYY-MM`.
          x-translations:
            en:
              description: Month to report in `YYYY-MM` format.
          schema:
            type: string
            pattern: ^\d{4}-\d{2}$
            example: 2026-04
          example: 2026-04
        - name: format
          in: query
          schema:
            type: string
            enum:
              - csv
              - xlsx
              - pdf
              - preview
            default: csv
          example: csv
          description: 'Formato de archivo solicitado para descarga. Aceptados: `csv`, `xlsx`, `pdf` o `preview`.'
          x-translations:
            en:
              description: 'Requested file format for the download. Accepted: `csv`, `xlsx`, `pdf` or `preview`.'
        - name: user_id
          in: query
          description: Solo para administradores — UUID del usuario a consultar.
          x-translations:
            en:
              description: Administrators only — UUID of the user to query.
          schema:
            type: string
            format: uuid
            example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100000
          example: 500
          description: Recorta el número de filas cuando la salida es `csv` o `xlsx`. No afecta al PDF, que lleva su propio tope de 1000 filas.
          x-translations:
            en:
              description: Trims the number of rows when the output is `csv` or `xlsx`. It does not affect the PDF, which carries its own 1000-row cap.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/finance/counterparties' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Stream CSV/XLSX/PDF, o envelope JSON en modo vista previa.
          x-translations:
            en:
              description: CSV/XLSX/PDF stream, or JSON envelope in preview mode.
          headers:
            Content-Disposition:
              description: Nombre de archivo sugerido (no aplica al modo vista previa).
              x-translations:
                en:
                  description: Suggested filename (does not apply in preview mode).
              schema:
                type: string
                example: attachment; filename="platform_contrapartes_2026-04.csv"
            Cache-Control:
              description: Deshabilita el almacenamiento en caché por parte de intermediarios durante la descarga (no aplica al modo vista previa).
              x-translations:
                en:
                  description: Disables intermediary caching of the download (does not apply to preview mode).
              schema:
                type: string
                example: no-cache, no-store, must-revalidate
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
            application/pdf:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                $ref: '#/components/schemas/FinancePreviewResponse'
        '400':
          $ref: '#/components/responses/InvalidMonth'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          description: '`pdf_render_failed`: Reintentar con otro formato.'
          x-translations:
            en:
              description: '`pdf_render_failed`: PDF renderer crashed. Try CSV or XLSX.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Counterparty summary (CSV / XLSX / PDF / preview)
          description: |-
            Groups the month's verifications by **beneficiary account and bank**, and includes 7 columns:

            - Cuenta beneficiaria.
            - Banco.
            - Verificaciones.
            - Válidas.
            - Monto total.
            - Primera.
            - Última.

            The summary can be requested in any of these formats: `format=csv` (default), `format=xlsx`, `format=pdf` (maximum 1000 rows).\
            `format=preview` is also available, to get the first 20 rows as JSON.

            For more about exports see {% concept slug="exports-and-formats" %}exports and formats{% /concept %}.
      security:
        - ApiKeyAuth: []
  /finance/by-bank:
    get:
      x-related:
        - GET /v1/finance/accounting
        - GET /v1/finance/ceps
        - GET /v1/finance/counterparties
        - GET /v1/finance/monthly
        - GET /v1/finance/statement
        - GET /v1/finance/summary
      tags:
        - Finance
      summary: Obtener el resumen por banco receptor (CSV, XLSX o PDF)
      description: |-
        Agrupa las verificaciones del mes por banco receptor (`banxico_code`), con totales, conteo de válidas, tasa de éxito y monto total. Contiene 6 columnas:

        - Clave Banxico.
        - Banco.
        - Verificaciones.
        - Válidas.
        - Tasa éxito (%).
        - Monto total.

        El resumen puede ser solicitado en alguno de los siguientes formatos: `format=csv` (predeterminado), `format=xlsx`, `format=pdf` (máximo 1000 filas).\
        Además se puede usar `format=preview` para obtener las primeras 20 filas como JSON.

        {% callout type="info" %}
        En formato PDF, la fila de Totales reporta la tasa de éxito agregada ponderada por filas (`sum(válidas) * 100 / sum(verificaciones)`), NO el promedio simple de las tasas por fila.

        {% /callout %}

        Para más información sobre exportaciones consulta {% concept slug="exports-and-formats" %}exportaciones y formatos{% /concept %}.
      operationId: getFinanceByBank
      externalDocs:
        url: https://docs.veriko.mx/how-to/generate-finance-report
        description: Export financial statements and CEP archives
      parameters:
        - name: month
          in: query
          required: true
          description: Mes a reportar en formato `YYYY-MM`.
          x-translations:
            en:
              description: Month to report in `YYYY-MM` format.
          schema:
            type: string
            pattern: ^\d{4}-\d{2}$
            example: 2026-04
          example: 2026-04
        - name: format
          in: query
          schema:
            type: string
            enum:
              - csv
              - xlsx
              - pdf
              - preview
            default: csv
          example: csv
          description: 'Formato de archivo solicitado para descarga. Aceptados: `csv`, `xlsx`, `pdf` o `preview`.'
          x-translations:
            en:
              description: 'Requested file format for the download. Accepted: `csv`, `xlsx`, `pdf` or `preview`.'
        - name: user_id
          in: query
          description: Solo para administradores — UUID del usuario a consultar.
          x-translations:
            en:
              description: Administrators only — UUID of the user to query.
          schema:
            type: string
            format: uuid
            example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100000
          example: 500
          description: Recorta el número de filas cuando la salida es `csv` o `xlsx`. No afecta al PDF, que lleva su propio tope de 1000 filas.
          x-translations:
            en:
              description: Trims the number of rows when the output is `csv` or `xlsx`. It does not affect the PDF, which carries its own 1000-row cap.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/finance/by-bank' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Stream CSV/XLSX/PDF, o envelope JSON en modo vista previa.
          x-translations:
            en:
              description: CSV/XLSX/PDF stream, or JSON envelope in preview mode.
          headers:
            Content-Disposition:
              description: Nombre de archivo sugerido (no aplica al modo vista previa).
              x-translations:
                en:
                  description: Suggested filename (does not apply in preview mode).
              schema:
                type: string
                example: attachment; filename="platform_bancos_2026-04.csv"
            Cache-Control:
              description: Deshabilita el almacenamiento en caché por parte de intermediarios durante la descarga (no aplica al modo vista previa).
              x-translations:
                en:
                  description: Disables intermediary caching of the download (does not apply to preview mode).
              schema:
                type: string
                example: no-cache, no-store, must-revalidate
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
            application/pdf:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                $ref: '#/components/schemas/FinancePreviewResponse'
        '400':
          $ref: '#/components/responses/InvalidMonth'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          description: '`pdf_render_failed`: Reintentar con otro formato.'
          x-translations:
            en:
              description: '`pdf_render_failed`: PDF renderer crashed. Try CSV or XLSX.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Bank summary (CSV / XLSX / PDF / preview)
          description: |-
            Groups the month's verifications by receiving bank (`banxico_code`), with totals, valid count, success rate and total amount. It contains 6 columns:

            - Clave Banxico.
            - Banco.
            - Verificaciones.
            - Válidas.
            - Tasa éxito (%).
            - Monto total.

            The summary can be requested in any of these formats: `format=csv` (default), `format=xlsx`, `format=pdf` (maximum 1000 rows).\
            `format=preview` is also available, to get the first 20 rows as JSON.

            {% callout type="info" %}
            In PDF format, the Totales row reports the row-weighted aggregate success rate (`sum(válidas) * 100 / sum(verificaciones)`), NOT the simple average of the per-row rates.

            {% /callout %}

            For more about exports see {% concept slug="exports-and-formats" %}exports and formats{% /concept %}.
      security:
        - ApiKeyAuth: []
  /finance/accounting:
    get:
      x-related:
        - GET /v1/finance/by-bank
        - GET /v1/finance/ceps
        - GET /v1/finance/counterparties
        - GET /v1/finance/monthly
        - GET /v1/finance/statement
        - GET /v1/finance/summary
      tags:
        - Finance
      summary: Exportar la contabilidad (CSV, XLSX o PDF)
      description: |-
        Exporta las mismas columnas que el **reporte mensual de verificaciones** (15 columnas, formato compatible con el SAT), fechas en formato `DD/MM/YYYY` y separador decimal configurable.

        Diseñado para importar directamente en software contable (Contpaq, Aspel, Excel contable).

        Puede ser solicitado en alguno de los siguientes formatos: `format=csv` (predeterminado), `format=xlsx`, `format=pdf` (máximo 1000 filas).\
        Además se puede usar `format=preview` para obtener las primeras 20 filas como JSON.

        Para más información sobre exportaciones consulta {% concept slug="exports-and-formats" %}exportaciones y formatos{% /concept %}.
      operationId: getFinanceAccounting
      externalDocs:
        url: https://docs.veriko.mx/how-to/generate-finance-report
        description: Export financial statements and CEP archives
      parameters:
        - name: month
          in: query
          required: true
          description: Mes a exportar en formato `YYYY-MM`.
          x-translations:
            en:
              description: Month to export in `YYYY-MM` format.
          schema:
            type: string
            pattern: ^\d{4}-\d{2}$
            example: 2026-04
          example: 2026-04
        - name: decimal
          in: query
          description: Separador decimal solicitado. `comma` → `1.234,56` (default); `dot` → `1,234.56`. Cualquier otro valor se trata como `comma`.
          x-translations:
            en:
              description: Decimal separator. `comma` → `1.234,56` (default); `dot` → `1,234.56`. Any other value is treated as `comma`.
          schema:
            type: string
            enum:
              - comma
              - dot
            default: comma
          example: comma
        - name: format
          in: query
          schema:
            type: string
            enum:
              - csv
              - xlsx
              - pdf
              - preview
            default: csv
          example: csv
          description: 'Formato de archivo solicitado para descarga. Aceptados: `csv`, `xlsx`, `pdf` o `preview`.'
          x-translations:
            en:
              description: 'Requested file format for the download. Accepted: `csv`, `xlsx`, `pdf` or `preview`.'
        - name: user_id
          in: query
          description: Solo para administradores — UUID del usuario a consultar.
          x-translations:
            en:
              description: Administrators only — UUID of the user to query.
          schema:
            type: string
            format: uuid
            example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100000
          example: 500
          description: Recorta el número de filas cuando la salida es `csv` o `xlsx`. No afecta al PDF, que lleva su propio tope de 1000 filas.
          x-translations:
            en:
              description: Trims the number of rows when the output is `csv` or `xlsx`. It does not affect the PDF, which carries its own 1000-row cap.
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/finance/accounting' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Stream CSV/XLSX/PDF, o envelope JSON en modo vista previa.
          x-translations:
            en:
              description: CSV/XLSX/PDF stream, or JSON envelope in preview mode.
          headers:
            Content-Disposition:
              description: Nombre de archivo sugerido (no aplica al modo vista previa).
              x-translations:
                en:
                  description: Suggested filename (does not apply in preview mode).
              schema:
                type: string
                example: attachment; filename="platform_contable_2026-04.csv"
            Cache-Control:
              description: Deshabilita el almacenamiento en caché por parte de intermediarios durante la descarga (no aplica al modo vista previa).
              x-translations:
                en:
                  description: Disables intermediary caching of the download (does not apply to preview mode).
              schema:
                type: string
                example: no-cache, no-store, must-revalidate
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
            application/pdf:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                $ref: '#/components/schemas/FinancePreviewResponse'
        '400':
          $ref: '#/components/responses/InvalidMonth'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          description: '`pdf_render_failed`: Reintentar con otro formato.'
          x-translations:
            en:
              description: '`pdf_render_failed`: PDF renderer crashed. Try CSV or XLSX.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Accounting export (CSV / XLSX / PDF · DD/MM/YYYY)
          description: |-
            Exports the same columns as the **monthly verifications report** (15 columns, SAT-compatible format), with dates in `DD/MM/YYYY` and a configurable decimal separator.

            Designed for direct import into accounting software (Contpaq, Aspel, accounting Excel).

            It can be requested in any of these formats: `format=csv` (default), `format=xlsx`, `format=pdf` (maximum 1000 rows).\
            `format=preview` is also available, to get the first 20 rows as JSON.

            For more about exports see {% concept slug="exports-and-formats" %}exports and formats{% /concept %}.
      security:
        - ApiKeyAuth: []
  /finance/ceps:
    get:
      x-related:
        - GET /v1/finance/accounting
        - GET /v1/finance/by-bank
        - GET /v1/finance/counterparties
        - GET /v1/finance/monthly
        - GET /v1/finance/statement
        - GET /v1/finance/summary
      tags:
        - Finance
      summary: Descargar los comprobantes en un archivo comprimido
      description: |-
        Entrega en un solo ZIP todos los archivos CEP disponibles de un rango de fechas.

        Cada validación con comprobante aporta su archivo XML y PDF (si está disponible), todo bajo el directorio `ceps/`. El archivo trae dos manifiestos con el mismo contenido en distinto formato — `manifest.csv` y `manifest.xlsx`: Que asocian cada comprobante con su validación, fecha y clave de rastreo. Sirven para conciliar sin abrir los comprobantes uno por uno.

        Un rango sin comprobantes responde con un estado HTTP `404 no_ceps`.

        Fechas mal formadas o un `to` anterior al `from`, responden con un estado HTTP `400 invalid_range`.

        {% callout type="info" %}
        Para un solo comprobante está `GET /v1/validations/{id}/cep`, y para el estado de cuenta del mes sin los archivos, `GET /v1/finance/statement`.

        {% /callout %}
      operationId: getFinanceCeps
      externalDocs:
        url: https://docs.veriko.mx/how-to/generate-finance-report
        description: Export financial statements and CEP archives
      parameters:
        - name: from
          in: query
          required: true
          description: Fecha de inicio (inclusive, `YYYY-MM-DD`).
          x-translations:
            en:
              description: Start date (inclusive, `YYYY-MM-DD`).
          schema:
            type: string
            format: date
            pattern: ^\d{4}-\d{2}-\d{2}$
            example: '2026-04-01'
          example: '2026-04-01'
        - name: to
          in: query
          required: true
          description: Fecha de fin (inclusive, `YYYY-MM-DD`). Debe ser ≥ `from`.
          x-translations:
            en:
              description: End date (inclusive, `YYYY-MM-DD`). Must be ≥ `from`.
          schema:
            type: string
            format: date
            pattern: ^\d{4}-\d{2}-\d{2}$
            example: '2026-04-30'
          example: '2026-04-30'
        - name: user_id
          in: query
          description: Solo para administradores — UUID del usuario a consultar.
          x-translations:
            en:
              description: Administrators only — UUID of the user to query.
          schema:
            type: string
            format: uuid
            example: f47ac10b-58cc-4372-a567-0e02b2c3d479
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/finance/ceps' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: ZIP con los XML/PDF de CEP y los manifiestos CSV + XLSX.
          x-translations:
            en:
              description: ZIP archive containing CEP XML/PDF files and CSV + XLSX manifests.
          headers:
            Content-Type:
              schema:
                type: string
                enum:
                  - application/zip
            Content-Disposition:
              description: Nombre de archivo sugerido para la descarga.
              x-translations:
                en:
                  description: Suggested filename for the download.
              schema:
                type: string
                example: attachment; filename="platform_ceps_2026-04-01_a_2026-04-30.zip"
            Content-Length:
              description: Tamaño exacto del ZIP en bytes.
              x-translations:
                en:
                  description: Exact size of the ZIP archive in bytes.
              schema:
                type: integer
            X-Finance-Items:
              description: Número de CEPs incluidos en el ZIP.
              x-translations:
                en:
                  description: Number of CEPs included in the ZIP archive.
              schema:
                type: integer
                example: 42
            Cache-Control:
              schema:
                type: string
                example: no-cache, no-store, must-revalidate
              description: Deshabilita el almacenamiento en caché por parte de intermediarios durante la descarga.
              x-translations:
                en:
                  description: Disables intermediary caching of the download.
          content:
            application/zip:
              schema:
                type: string
                format: binary
        '400':
          description: '`invalid_range`: Fechas mal formadas o `to < from`.'
          x-translations:
            en:
              description: '`invalid_range`: Malformed dates or `to < from`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '`no_ceps`: El rango no contiene ningún CEP disponible.'
          x-translations:
            en:
              description: '`no_ceps`: No CEPs available in the specified range.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-translations:
        en:
          summary: Bulk CEP download (ZIP)
          description: |-
            Delivers every available CEP file in a date range as a single ZIP.

            Each validation with a receipt contributes its XML and PDF file (when
            available), all under the `ceps/` directory. The archive carries two
            manifests with the same content in different formats — `manifest.csv`
            and `manifest.xlsx`: Associating each receipt with its validation, date
            and tracking key. They serve to reconcile without opening the receipts
            one by one.

            A range with no receipts responds with an HTTP status `404 no_ceps`.

            Malformed dates, or a `to` earlier than the `from`, respond with an HTTP
            status `400 invalid_range`.

            {% callout type="info" %}
            For a single receipt there is `GET /v1/validations/{id}/cep`, and for
            the month's statement without the files, `GET /v1/finance/statement`.

            {% /callout %}
      security:
        - ApiKeyAuth: []
  /billing/subscription:
    get:
      x-related:
        - GET /v1/usage/summary
        - GET /v1/summary
      tags:
        - Billing
      summary: Consultar suscripción activa del usuario
      description: |-
        Devuelve la suscripción activa del usuario autenticado. La respuesta siempre
        contiene un recurso: si no existe una suscripción activa, el servidor
        asigna el plan predeterminado.

        `source` distingue pagos mediante Stripe (`stripe`), concesiones
        administrativas (`admin_granted`) y planes asignados por el sistema
        (`system_default`). El resumen del ciclo (`used`, `limit`, `remaining` y
        `resets_at`) vive en `meta.quota`.

        El consumo del ciclo también se consulta con
        [`GET /v1/usage/summary`](/es/v1/usage/get-usage-summary). Su `resets_at` es
        el fin del ciclo de cuota: coincide con `current_period_end` salvo en un plan
        anual, donde la cuota se reinicia cada mes y `resets_at` marca el fin del
        mes en curso. Los planes disponibles se consultan con
        [`GET /v1/plans`](/es/v1/plans/list-plans).
      operationId: billingGetSubscription
      x-translations:
        en:
          summary: Get user's active subscription
          description: |-
            Returns the authenticated user's active subscription. The response
            always contains a resource: if no active subscription exists, the
            server assigns the default plan.

            `source` distinguishes Stripe payments (`stripe`), admin grants
            (`admin_granted`), and plans assigned by the system (`system_default`).
            The cycle summary (`used`, `limit`, `remaining`, and `resets_at`) lives
            in `meta.quota`.

            Current-cycle usage is also available from
            [`GET /v1/usage/summary`](/en/v1/usage/get-usage-summary). Its
            `resets_at` is the end of the quota cycle: it matches
            `current_period_end` except on an annual plan, where quota resets every
            month and `resets_at` marks the end of the current month. Available
            plans are returned by [`GET /v1/plans`](/en/v1/plans/list-plans).
      x-codeSamples:
        - lang: curl
          label: Shell
          source: |-
            curl -X GET 'https://api.veriko.mx/v1/billing/subscription' \
              -H 'Authorization: Bearer veriko_••••'
      responses:
        '200':
          description: Suscripción activa del usuario con plan, ciclo y cuota.
          x-translations:
            en:
              description: User's active subscription with plan, cycle, and quota.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingSubscriptionResponse'
              examples:
                stripe_subscription:
                  summary: Suscripción Stripe activa — plan Pro
                  x-translations:
                    en:
                      summary: Active Stripe subscription — Pro plan
                  value:
                    data:
                      type: billing_subscription
                      id: '4218'
                      attributes:
                        plan_slug: pro
                        plan_name: Pro
                        billing_model: hybrid
                        source: stripe
                        status: active
                        current_period_start: '2026-04-01T00:00:00Z'
                        current_period_end: '2026-05-01T00:00:00Z'
                        currency: MXN
                        billing_interval: month
                        overage_enabled: false
                        included_validations: 500
                        monthly_validation_limit: 10000
                        effective_limit: 500
                        is_stripe: true
                        stripe_subscription_id: sub_1OaBcDeFgHiJk2
                        grant_reason: null
                        cancel_at_period_end: false
                        cancel_at: null
                        canceled_at: null
                        trial_start: null
                        trial_end: null
                    meta:
                      effective_plan_slug: pro
                      has_subscription: true
                      quota:
                        plan: pro
                        used: 420
                        limit: 500
                        remaining: 80
                        resets_at: '2026-05-01T00:00:00Z'
                system_default:
                  summary: Plan gratuito por defecto — sin Stripe
                  x-translations:
                    en:
                      summary: Default free plan — no Stripe
                  value:
                    data:
                      type: billing_subscription
                      id: '1'
                      attributes:
                        plan_slug: free
                        plan_name: Gratis
                        billing_model: free
                        source: system_default
                        status: active
                        current_period_start: '2026-04-01T00:00:00Z'
                        current_period_end: '2026-05-01T00:00:00Z'
                        currency: MXN
                        billing_interval: month
                        overage_enabled: false
                        included_validations: 25
                        monthly_validation_limit: 25
                        effective_limit: 25
                        is_stripe: false
                        stripe_subscription_id: null
                        grant_reason: null
                        cancel_at_period_end: false
                        cancel_at: null
                        canceled_at: null
                        trial_start: null
                        trial_end: null
                    meta:
                      effective_plan_slug: free
                      has_subscription: true
                      quota:
                        plan: free
                        used: 5
                        limit: 25
                        remaining: 20
                        resets_at: '2026-05-01T00:00:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - ApiKeyAuth: []
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: 'API key para acceso programático (M2M). Envía la cabecera `Authorization: Bearer veriko_<64 hex>`. Las API keys se generan en el dashboard del usuario.'
      x-translations:
        en:
          description: 'API key for programmatic (M2M) access. Send `Authorization: Bearer veriko_<64 hex>`. API keys are minted from the user dashboard.'
  parameters:
    IdempotencyKeyHeader:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        pattern: ^[A-Za-z0-9_\-]{1,255}$
        example: 11111111-2222-3333-4444-555555555555
      description: 'Llave opcional que garantiza que la petición se procese **exactamente una vez** dentro de un TTL de 24 horas. Los reintentos con la misma llave y el mismo cuerpo devuelven la respuesta cacheada con la cabecera `Idempotent-Replayed: true` (sin re-disparar webhooks) y sin crear una nueva validación.'
      x-translations:
        en:
          description: |
            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 + `_` + `-`.
    AsyncQueryParam:
      name: async
      in: query
      required: false
      schema:
        type: string
        enum:
          - '0'
          - '1'
          - 'true'
          - 'false'
          - 'yes'
        default: '0'
      description: 'Cuando es `1`/`true`/`yes`: Procesa la validación en segundo plano y responde un estado HTTP 202 inmediato con `validation_id`. Sin el flag o con `0`, la respuesta es síncrona y devuelve el resultado final.'
      x-translations:
        en:
          description: |
            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.
    ValidationId:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Identificador (UUID) de la validación
      x-translations:
        en:
          description: Validation UUID.
    IfNoneMatchHeader:
      name: If-None-Match
      in: header
      required: false
      schema:
        type: string
        example: W/"3-processing"
      description: '**ETag** recibido en peticiones anteriores. Si los datos no han cambiado y el `etag_version` sigue igual, el server responde `304 Not Modified` (sin cuerpo) — evita recibir los datos de nuevo cuando el estado del recurso NO ha cambiado.'
      x-translations:
        en:
          description: '**ETag** received in previous requests. If the data has not changed and `etag_version` is still the same, the server responds `304 Not Modified` (no body) — avoids receiving the data again when the resource state has NOT changed.'
  schemas:
    ErrorObject:
      type: object
      description: 'Un error individual: el código estable por el que ramifica el cliente, el mensaje legible —ese sí traducido—, el estado HTTP y, cuando el fallo es de un campo concreto, el puntero que lo señala.'
      x-translations:
        en:
          description: 'A single error: the stable code the client branches on, the human-readable message — that one translated —, the HTTP status, and, when the failure is about one field, the pointer naming it.'
      properties:
        status:
          type: string
          pattern: ^[1-5]\d\d$
          description: Código de estado HTTP como string.
          example: '422'
          x-translations:
            en:
              description: HTTP status code as a string.
        code:
          type: string
          description: |
            Código de error estable legible por máquina (contrato). No se traduce. Los clientes deben ramificar por este campo.
          example: validation_error
          x-translations:
            en:
              description: |
                Stable, machine-readable error code (contract). Never translated. Clients must branch on this field.
        detail:
          type: string
          description: |
            Mensaje de error legible por humanos. Traducido según `Accept-Language` (`es` / `en`).
          example: El campo fecha es obligatorio.
          x-translations:
            en:
              description: |
                Human-readable error message. Translated per `Accept-Language` (`es` / `en`).
        source:
          type: object
          description: Origen estructurado del error (JSON Pointer al campo que falló).
          x-translations:
            en:
              description: Structured source of the error (JSON Pointer to the failing field).
          properties:
            pointer:
              type: string
              description: |
                JSON Pointer (RFC 6901) al campo que causó el error (ej. `/data/attributes/fecha`).
              example: /data/attributes/fecha
              x-translations:
                en:
                  description: |
                    JSON Pointer (RFC 6901) to the field that caused the error (e.g. `/data/attributes/fecha`).
      required:
        - status
        - code
        - detail
    DatetimeMeta:
      type: object
      description: |
        Descriptor compañero presente en el bloque `meta` de cada respuesta (y en el
        `meta` del cuerpo de los webhooks salientes). Permite a los clientes afirmar
        el contrato de zona horaria sin releer el spec.
      required:
        - timezone
        - format
      properties:
        timezone:
          type: string
          enum:
            - UTC
          description: 'Siempre `UTC`: La zona canónica para cada campo datetime del cuerpo.'
          x-translations:
            en:
              description: 'Always `UTC`: The canonical timezone for every datetime field in the body.'
          example: UTC
        format:
          type: string
          enum:
            - ISO 8601
          description: 'Siempre `ISO 8601`: Sufijo `Z` explícito en cada datetime.'
          x-translations:
            en:
              description: 'Always `ISO 8601`: Explicit `Z` suffix on every datetime.'
          example: ISO 8601
      example:
        timezone: UTC
        format: ISO 8601
      x-translations:
        en:
          description: |
            Companion descriptor present in the `meta` block of every response (and in
            the `meta` of outgoing webhook bodies). Lets clients assert the timezone
            contract without re-reading the spec.
    ErrorResponse:
      type: object
      description: |
        Wrapper estándar de respuesta de error. El campo `code` dentro de cada error es el contrato estable legible por máquina — los clientes deben ramificar por `code`, no por `detail`.
      x-translations:
        en:
          description: |
            Standard error response wrapper. The `code` field within each error is the stable, machine-readable contract — clients must branch on `code`, not `detail`.
      required:
        - errors
        - meta
      properties:
        errors:
          type: array
          minItems: 1
          description: |
            Lista de uno o más errores ocurridos durante la petición. Cada elemento incluye `status`, `code`, `detail` y, opcionalmente, `source.pointer` apuntando al campo que falló.
          x-translations:
            en:
              description: |
                List of one or more errors that occurred during the request. Each entry includes `status`, `code`, `detail` and, optionally, `source.pointer` referencing the field that failed.
          items:
            $ref: '#/components/schemas/ErrorObject'
        meta:
          type: object
          description: |
            Metadatos de la respuesta, incluyendo versión de la API, prefijo de ruta, identificador único de la petición y marca temporal del servidor en UTC.
          x-translations:
            en:
              description: |
                Response metadata including API version, route prefix, unique request identifier, and the server timestamp in UTC.
          properties:
            version:
              type: string
              pattern: ^\d+\.\d+\.\d+$
              description: Versión de la API que procesó la petición.
              example: 1.47.0
              x-translations:
                en:
                  description: API version that processed the request.
            api_version:
              type: string
              description: Versión del prefijo de ruta de la API (ej. `v1`).
              example: v1
              x-translations:
                en:
                  description: API route prefix version (e.g. `v1`).
            request_id:
              type: string
              description: Identificador único de la petición (hex).
              example: a1b2c3d4e5f6
              x-translations:
                en:
                  description: Unique request identifier (hex).
            datetime:
              $ref: '#/components/schemas/DatetimeMeta'
            validation_id:
              type: string
              format: uuid
              description: |
                Identificador de la validación que se creó antes de fallar. Sólo aparece en errores de `POST /v1/validate` y `POST /v1/validate-ocr` cuando la validación ya quedó registrada con estado `failed` o `error`; consúltala con `GET /v1/validations/{id}` para ver el motivo y lo que se leyó.
              example: 74fa884a-2bdb-4d9d-b36f-3a2def754984
              x-translations:
                en:
                  description: |
                    Identifier of the validation that was created before failing. Only present on `POST /v1/validate` and `POST /v1/validate-ocr` errors when the validation was already recorded with status `failed` or `error`; fetch it with `GET /v1/validations/{id}` to see the reason and what was read.
    Error:
      type: object
      description: |
        Respuesta de error estilo JSON:API. El campo `code` es el contrato estable legible por máquina y nunca se traduce — los clientes deben ramificar por `code`, no por `detail`. El campo `detail` es legible por humanos y se traduce según la cabecera `Accept-Language` de la petición.
      x-translations:
        en:
          description: |
            JSON:API-style error response. The `code` field is the stable, machine-readable contract and is never translated — clients must branch on `code`, not `detail`. The `detail` field is human-readable and translated per the `Accept-Language` request header.
      properties:
        errors:
          type: array
          minItems: 1
          description: |
            Lista de uno o más errores ocurridos durante la petición. Cada elemento incluye `status`, `code`, `detail` y, opcionalmente, `source.pointer` apuntando al campo que falló.
          x-translations:
            en:
              description: |
                List of one or more errors that occurred during the request. Each entry includes `status`, `code`, `detail` and, optionally, `source.pointer` referencing the field that failed.
          items:
            $ref: '#/components/schemas/ErrorObject'
        meta:
          type: object
          description: |
            Metadatos de la respuesta de error, incluyendo la versión de la API que la atendió y el identificador único de la petición para correlación en logs.
          x-translations:
            en:
              description: |
                Error response metadata, including the API version that served it and the unique request identifier for log correlation.
          properties:
            version:
              type: string
              description: Versión de la API que procesó la petición.
              example: 1.47.0
              x-translations:
                en:
                  description: API version that processed the request.
            request_id:
              type: string
              description: Identificador único de la petición (UUID hex).
              example: a1b2c3d4e5f6
              x-translations:
                en:
                  description: Unique request identifier (hex UUID).
      required:
        - errors
    SuccessEnvelope:
      type: object
      description: |
        Wrapper estándar de respuesta exitosa. El payload de la respuesta está en `data`; los metadatos de la petición en `meta`; los enlaces de paginación o relacionados en `links` cuando aplica.
      x-translations:
        en:
          description: |
            Standard successful response wrapper. The response payload is in `data`; request metadata is in `meta`; pagination or related links are in `links` when applicable.
      properties:
        data:
          description: |
            Payload principal de la respuesta. La forma varía según el endpoint (objeto, array, o envelope JSON:API con `type`, `id`, `attributes`).
          x-translations:
            en:
              description: |
                Main response payload. Shape varies by endpoint (object, array, or JSON:API envelope with `type`, `id`, `attributes`).
        meta:
          type: object
          description: |
            Metadatos de la respuesta, incluyendo versión de la API, prefijo de ruta, identificador único de la petición y marca temporal del servidor en UTC.
          x-translations:
            en:
              description: |
                Response metadata including API version, route prefix, unique request identifier, and the server timestamp in UTC.
          properties:
            version:
              type: string
              pattern: ^\d+\.\d+\.\d+$
              description: Versión de la API que procesó la petición.
              example: 1.47.0
              x-translations:
                en:
                  description: API version that processed the request.
            api_version:
              type: string
              description: Versión del prefijo de ruta de la API (ej. `v1`).
              example: v1
              x-translations:
                en:
                  description: API route prefix version (e.g. `v1`).
            request_id:
              type: string
              description: Identificador único de la petición (hex).
              example: a1b2c3d4e5f6
              x-translations:
                en:
                  description: Unique request identifier (hex).
            datetime:
              $ref: '#/components/schemas/DatetimeMeta'
        links:
          type: object
          description: |
            Enlaces de paginación o relacionados, presentes solo cuando el endpoint devuelve una colección paginada.
          x-translations:
            en:
              description: |
                Pagination or related links, present only when the endpoint returns a paginated collection.
    JsonApiResourceBase:
      type: object
      description: |
        Identidad de un recurso dentro de la envoltura JSON:API: el tipo al que pertenece y su identificador. Todo recurso que viaja en `data` la lleva, junto con sus `attributes`.
      x-translations:
        en:
          description: |
            Identity of a resource within the JSON:API envelope: the type it belongs to and its identifier. Every resource travelling in `data` carries it, alongside its `attributes`.
      properties:
        type:
          type: string
          description: |
            Tipo del recurso. Es un valor fijo por recurso y no varía entre respuestas; permite distinguir recursos cuando una colección mezcla varios.
          x-translations:
            en:
              description: |
                Resource type. It is a fixed value per resource and does not vary between responses; it tells resources apart when a collection mixes several.
          example: validation
        id:
          type: string
          description: |
            Identificador del recurso, único dentro de su `type`. Viaja siempre como cadena, incluso cuando su origen es una columna autonumérica.
          x-translations:
            en:
              description: |
                Resource identifier, unique within its `type`. It always travels as a string, even when it originates from an auto-increment column.
    TimestampUTC:
      type: string
      format: date-time
      description: |
        Timestamp ISO 8601 en UTC con sufijo `Z` explícito; por ejemplo,
        `"2026-05-01T05:14:38Z"`.

        Los campos `*_at`, `*_end`, `*_start` y `*_date` adoptan esta forma.
        `meta.datetime` describe el mismo contrato en la respuesta.
      example: '2026-05-01T05:14:38Z'
      x-translations:
        en:
          description: |
            ISO 8601 timestamp in UTC with an explicit `Z` suffix; for example,
            `"2026-05-01T05:14:38Z"`. The `*_at`, `*_end`, `*_start`, and `*_date`
            fields follow this format. `meta.datetime` describes the same contract in
            the response.
    UserMeBundle:
      type: object
      description: 'Conjunto consolidado del usuario autenticado que devuelve `GET /v1/users/me`: perfil, metadatos de la clave de API, suscripción activa, estado de la autenticación de 2 factores (2FA), permisos del rol y resumen de notificaciones en una sola llamada.'
      x-translations:
        en:
          description: 'Consolidated set for the authenticated user returned by `GET /v1/users/me`: profile, API key metadata, active subscription, two-factor authentication (2FA) state, role permissions and notification summary in a single call.'
      properties:
        id:
          type: string
          format: uuid
          description: Identificador único del usuario (UUID v4).
          x-translations:
            en:
              description: Unique user identifier (UUID v4).
          example: b1e4a2c3-1234-4abc-8def-000000000001
        email:
          type: string
          format: email
          description: Correo electrónico asociado a la cuenta de usuario (con el que se inicia sesión y se reciben notificaciones).
          x-translations:
            en:
              description: 'Email address tied to the user account: the one it signs in with and where its notifications arrive.'
          example: carlos@example.com
        name:
          type: string
          maxLength: 100
          description: Nombre con el que se presenta a la cuenta de usuario en la interfaz (máximo 100 caracteres).
          x-translations:
            en:
              description: Name the user is presented with in the interface. Up to 100 characters.
          example: Carlos Lopez
        role:
          type: string
          enum:
            - owner
            - admin
            - user
            - viewer
          description: 'Rol de la cuenta de usuario en la organización. Decide qué permisos tienen sus sesiones — `owner`: Es la titular; `admin`: La administra; `user`: La usa; `viewer`: Solo lee.'
          x-translations:
            en:
              description: 'The user account''s role in the organization. It decides what permissions its sessions carry — `owner`: It owns the account; `admin`: It administers it; `user`: It uses it; `viewer`: Read-only.'
          example: user
        status:
          type: string
          enum:
            - active
            - suspended
            - pending_verification
          description: 'Estado de la cuenta de usuario — `active`: Permite operar con normalidad; `suspended`: Cuenta suspendida; `pending_verification`: Falta verificación de correo electrónico.'
          x-translations:
            en:
              description: 'Account status. Only `active` allows API operations. `suspended`: Account suspended; `pending_verification`: The email is still unverified.'
          example: active
        timezone:
          type: string
          description: Zona horaria IANA con la que se presentan las fechas al usuario.
          x-translations:
            en:
              description: IANA timezone the dates are presented to the user in.
          example: America/Mexico_City
        language:
          type:
            - string
            - 'null'
          enum:
            - es
            - en
            - null
          description: 'Idioma preferido de la cuenta de usuario para mostrar la interfaz y notificaciones — `es`: Castellano; `en`: Inglés; `null`: La cuenta de usuario aún no ha elegido idioma preferido.'
          x-translations:
            en:
              description: 'Language the user account prefers for the interface and the notifications — `es`: Spanish; `en`: English; `null`: The user account has not chosen a preferred language yet.'
          example: es
        email_verified_at:
          type:
            - string
            - 'null'
          format: date-time
          description: Fecha y hora de la verificación del correo electrónico de la cuenta de usuario (en ISO 8601 UTC). `null` cuando sigue pendiente.
          x-translations:
            en:
              description: Date and time when the email was verified, in ISO 8601 UTC. `null` when it is still pending.
          example: '2026-04-30T10:15:00Z'
        created_at:
          type: string
          format: date-time
          description: Fecha y hora del alta de la cuenta de usuario, en ISO 8601 UTC.
          x-translations:
            en:
              description: Date and time when the account was created, in ISO 8601 UTC.
          example: '2026-01-15T08:00:00Z'
        api_key:
          type:
            - object
            - 'null'
          description: 'Datos de la clave de API de la cuenta de usuario. Nunca contiene la clave completa: para eso está `POST /v1/users/me/api-key/reveal`, que exige la contraseña.'
          x-translations:
            en:
              description: 'The user account''s API key data. It never carries the full key: for that there is `POST /v1/users/me/api-key/reveal`, which requires the password.'
          properties:
            prefix:
              type: string
              description: Primeros 8 caracteres de la clave de API.
              x-translations:
                en:
                  description: First 8 characters of the API key, which identify it without revealing it.
              example: veriko_a1b2
            last_4:
              type:
                - string
                - 'null'
              description: Últimos 4 caracteres de la clave de API, con los que se pinta enmascarada (`veriko_a1b2••••••f9c2`). `null` en las cuentas anteriores a ese campo, hasta que se rellena.
              x-translations:
                en:
                  description: Last 4 characters of the API key, used to render it masked (`veriko_a1b2••••••f9c2`). `null` for accounts predating that field, until it is backfilled.
              example: f9c2
        subscription:
          type:
            - object
            - 'null'
          description: Suscripción activa de la cuenta de usuario. Cuando no hay ninguna, contiene los valores del plan predeterminado del sistema, y solo es `null` cuando no se pudo consultar — el caso que `_warnings` reporta.
          x-translations:
            en:
              description: The user account's active subscription. When there is none, it carries the values of the system default plan, and it is `null` only when it could not be read — the case `_warnings` reports.
          properties:
            plan_slug:
              type: string
              description: Identificador único y legible del plan.
              x-translations:
                en:
                  description: Unique, human-readable plan identifier.
              example: pro
            plan_name:
              type:
                - string
                - 'null'
              description: Nombre visible del plan activo, tal como lo muestra el catálogo de planes. Es `null` cuando no se pudo leer el catálogo.
              x-translations:
                en:
                  description: Display name of the active plan, as the plan catalog shows it. It is `null` when the catalog could not be read.
              example: Pro
            status:
              type: string
              enum:
                - active
                - past_due
                - canceled
                - paused
                - trialing
              description: 'Estado actual de la suscripción según el ciclo de facturación. `active`: Al corriente; `trialing`: En periodo de prueba; `past_due`: Con cobros pendientes; `canceled`: Cancelada; `paused`: En pausa.'
              x-translations:
                en:
                  description: 'Current subscription status per the billing cycle. `active`: Up to date; `trialing`: In trial; `past_due`: A charge is outstanding; `canceled`: Cancelled; `paused`: Paused.'
              example: active
            current_period_end:
              type:
                - string
                - 'null'
              format: date-time
              description: Fecha y hora del fin del periodo de facturación en curso, en ISO 8601 UTC.
              x-translations:
                en:
                  description: Date and time when the current billing period ends, in ISO 8601 UTC.
              example: '2026-06-15T00:00:00Z'
            cancel_at:
              type:
                - string
                - 'null'
              format: date-time
              description: Fecha y hora de la cancelación programada, en ISO 8601 UTC. `null` cuando no hay ninguna pendiente.
              x-translations:
                en:
                  description: Date and time of the scheduled cancellation, in ISO 8601 UTC. `null` when none is pending.
              example: null
            billing_interval:
              type:
                - string
                - 'null'
              enum:
                - month
                - year
                - once
              description: 'Cadencia de facturación — `once`: Ciclo único, sin renovación automática; `month`: Cada mes; `year`: Cada año.'
              x-translations:
                en:
                  description: 'Billing cadence — `once`: One cycle with no automatic renewal; `month`: Monthly; `year`: Yearly.'
              example: month
        two_factor:
          type: object
          description: Estado de la autenticación de 2 factores (2FA).
          x-translations:
            en:
              description: Two-factor authentication (2FA) state.
          properties:
            enabled:
              type: boolean
              description: '`true` cuando la autenticación de 2 factores (2FA) está activa en la cuenta.'
              x-translations:
                en:
                  description: '`true` when two-factor authentication (2FA) is active on the user account.'
              example: false
            method:
              type:
                - string
                - 'null'
              enum:
                - sms
                - email
                - null
              description: 'Canal por el que se entrega el código de autenticación (OTP) — `sms`: Por mensaje de texto; `email`: Por correo electrónico; `null`: La autenticación de 2 factores (2FA) está desactivada.'
              x-translations:
                en:
                  description: 'Channel the authentication code (OTP) is delivered through — `sms`: By text message; `email`: By email; `null`: 2FA is disabled.'
              example: sms
            last_used_at:
              type:
                - string
                - 'null'
              format: date-time
              description: 'Fecha y hora del último uso de la autenticación de 2 factores (2FA), en ISO 8601 UTC. Campo previsto para más adelante: hoy siempre `null`.'
              x-translations:
                en:
                  description: 'Date and time of the last 2FA use, in ISO 8601 UTC. A field planned for later: today it is always `null`.'
              example: null
        permissions:
          type: array
          description: Lista plana de permisos del rol asignado a la cuenta de usuario (en formato `resource:action`).
          x-translations:
            en:
              description: Flat list of the user's role permissions in `resource:action` format.
          items:
            type: string
            example: validations:read
        notifications:
          type: object
          description: Resumen de la cola de notificaciones del usuario.
          x-translations:
            en:
              description: Summary of the user's notification queue.
          properties:
            unread_count:
              type: integer
              description: Cantidad de notificaciones del usuario sin leer ni archivar.
              x-translations:
                en:
                  description: Number of notifications that are neither read nor archived.
              example: 3
            push_enabled:
              type: boolean
              description: '`true` cuando la cuenta tiene al menos una suscripción de notificaciones push activa.'
              x-translations:
                en:
                  description: '`true` when the account has at least one active push subscription.'
              example: false
            telegram_linked:
              type: boolean
              description: '`true` cuando la cuenta de usuario tiene un chat de Telegram vinculado.'
              x-translations:
                en:
                  description: '`true` when the account has a linked Telegram chat.'
              example: false
        legal_accepted:
          type: boolean
          description: '`true` cuando no queda pendiente la aceptación de ningún documento: ni los Términos de Servicio ni el Aviso de Privacidad. `false` cuando falta la aceptación de cualquiera de los dos, o cuando el equipo exigió aceptar una versión que la cuenta todavía no ha aceptado. Gatea el modal de consentimiento post-login, que lista lo pendiente en `GET /users/me/legal/pending`.'
          x-translations:
            en:
              description: '`true` when no document is left pending: neither the Terms of Service nor the Privacy Notice. `false` when the acceptance of either is missing, or when the team required acceptance of a version that the account has not accepted yet. Gates the post-login consent modal, which lists what is pending in `GET /users/me/legal/pending`.'
          example: true
        _warnings:
          type: array
          description: Piezas que no se pudieron calcular en la respuesta. Solo aparece cuando algo falló, y los campos que dependían de ella llegan en su valor vacío (`null` o lista vacía).
          x-translations:
            en:
              description: Pieces that could not be computed while assembling the response. It only appears when one failed, and the fields that depended on it arrive at their empty value (`null` or an empty list).
          items:
            type: string
            enum:
              - subscription_unavailable
              - permissions_unavailable
              - notifications_unread_count_unavailable
              - notifications_push_unavailable
              - notifications_telegram_unavailable
              - legal_accepted_unavailable
          example:
            - subscription_unavailable
    RetryPolicy:
      type: object
      description: 'Política de reintentos de una validación: activación, límite, intervalo y resultados elegibles.'
      x-translations:
        en:
          description: |
            Validation retry policy: activation, limit, interval, and eligible outcomes.
      properties:
        enabled:
          type: boolean
          description: '`true` cuando el ciclo de reintentos está activo. Si es `false`, los demás campos pueden ser ignorados.'
          example: true
          x-translations:
            en:
              description: '`true` when the retry cycle is active. With `false`, the other fields are ignored.'
        max_retries:
          type: integer
          minimum: 1
          description: Número máximo de reintentos automáticos. Una vez alcanzado, el ciclo pasa al estado terminal `exhausted`.
          example: 3
          x-translations:
            en:
              description: |
                Maximum number of automatic retries. Once reached, the cycle enters the `exhausted` terminal state. The allowed limit depends on the plan.
        interval_seconds:
          type: integer
          minimum: 300
          maximum: 86400
          description: 'Segundos de espera entre cada reintento. Valores válidos: 300–86400 (5 min a 24h).'
          example: 600
          x-translations:
            en:
              description: |
                Seconds to wait between each retry (300–86400, i.e., 5 minutes to 24 hours).
        outcomes:
          type: array
          minItems: 1
          uniqueItems: true
          description: |
            Resultados que originan un reintento. De forma predeterminada son `not_found`, `cep_unavailable` y `error`; la combinación `["not_found", "cep_unavailable"]` excluye `error`.
          items:
            type: string
            enum:
              - not_found
              - cep_unavailable
              - error
          example:
            - not_found
            - cep_unavailable
            - error
          x-translations:
            en:
              description: |
                Outcomes that trigger a retry. By default, they are `not_found`, `cep_unavailable`, and `error`; the combination `["not_found", "cep_unavailable"]` excludes `error`.
    ValidationRequest:
      type: object
      required:
        - fecha
        - monto
      anyOf:
        - required:
            - clave_rastreo
        - required:
            - referencia_numerica
      allOf:
        - oneOf:
            - required:
                - cuenta_beneficiaria
            - required:
                - cuentas_candidatas
      description: |
        Cuerpo de `POST /v1/validate` para una validación manual SPEI; exige `clave_rastreo` o `referencia_numerica`, y una sola de las dos formas de indicar la cuenta: `cuenta_beneficiaria` o `cuentas_candidatas`.
      x-translations:
        en:
          description: |
            Body for `POST /v1/validate` for a manual SPEI validation; requires `clave_rastreo` or `referencia_numerica`, and exactly one of the two ways to give the account: `cuenta_beneficiaria` or `cuentas_candidatas`.
      properties:
        fecha:
          type: string
          format: date
          pattern: ^\d{4}-\d{2}-\d{2}$
          minLength: 10
          maxLength: 10
          description: Fecha de envío de la transferencia en formato ISO 8601 (YYYY-MM-DD). Con ella Banxico localiza el CEP, y puede diferir del día de operación que el CEP imprime (`banxico_result.operationDate`).
          example: '2025-03-15'
          x-translations:
            en:
              description: Sending date of the transfer in ISO 8601 format (YYYY-MM-DD). Banxico locates the CEP with it, and it can differ from the operation day the CEP prints (`banxico_result.operationDate`).
          x-invalid-examples:
            - 15/03/2025
            - 2025-3-15
            - '20250315'
        monto:
          type: number
          format: double
          exclusiveMinimum: 0
          description: Importe de la transferencia (en pesos mexicanos — MXN), mayor que cero y con hasta dos decimales. No se acepta en notación científica (por ejemplo `1e-5`) cuando se envía como texto.
          example: 15000.5
          x-translations:
            en:
              description: Transfer amount in Mexican pesos (MXN), greater than zero and with up to two decimal places. Scientific notation (e.g. `1e-5`) is rejected when sent as text.
          x-invalid-examples:
            - 0
            - -100
            - '1e-5'
        clave_rastreo:
          type: string
          minLength: 1
          maxLength: 30
          description: Clave de rastreo de la transferencia (entre 1 a 30 caracteres). Acepta letras, números, guiones, diagonales y espacios. Se recorta y se le quitan caracteres invisibles de ancho cero antes de validarse, para que la misma clave copiada y pegada compare igual en toda la plataforma. Requerida si `referencia_numerica` no es enviada; ambas pueden incluirse simultáneamente para mayor precisión de búsqueda.
          example: MXBA20250315001234
          x-translations:
            en:
              description: Transfer tracking key, between 1 and 30 characters. Accepts letters, numbers, hyphens, slashes, and spaces. It is trimmed and stripped of zero-width invisible characters before validation, so the same copy-pasted key compares equal everywhere in the platform. Required when `referencia_numerica` is not sent; both can be included for a more precise search.
          x-invalid-examples:
            - ''
            - MXBA202503150012345678901234567890
            - MXBA<2025>0315
        referencia_numerica:
          type: string
          minLength: 1
          pattern: '^(?: *\d){1,7} *$'
          description: Referencia numérica de la transferencia (entre 1 y 7 dígitos). Acepta solo números y espacios. Requerida si `clave_rastreo` no es enviada; ambas pueden incluirse simultáneamente para mayor precisión de búsqueda.
          example: '1234567'
          x-translations:
            en:
              description: Numeric transfer reference, between 1 and 7 digits. Accepts only numbers and spaces. Required when `clave_rastreo` is not sent; both can be included for a more precise search.
          x-invalid-examples:
            - '12345678'
            - ABC1234
        emisor:
          type: string
          maxLength: 255
          description: '**Nombre o código SPEI** del banco emisor. Cuando falta o es incorrecto, la clave de rastreo puede aportarlo. Si aun así no puede resolverse, la respuesta es un estado HTTP `422`.'
          example: BANCO NACIONAL DE MEXICO
          x-translations:
            en:
              description: '**SPEI name or code** of the sending bank. When it is missing or incorrect, the tracking key may provide it. If it still cannot be resolved, the response is an HTTP status `422`.'
        receptor:
          type: string
          maxLength: 255
          description: '**Nombre / código SPEI** del banco receptor. Es opcional cuando el banco puede identificarse a partir de `cuenta_beneficiaria`. Si no puede resolverse, la respuesta será un estado HTTP `422`.'
          example: BBVA MEXICO
          x-translations:
            en:
              description: '**SPEI name / code** of the receiving bank. Optional when the bank can be identified from `cuenta_beneficiaria`. If it cannot be resolved, the response is HTTP status `422`.'
        cuenta_beneficiaria:
          type: string
          pattern: ^(\d{10}|\d{16}|\d{18})$
          description: 'Cuenta bancaria receptora de la transferencia. Es obligatoria cuando no se envía `cuentas_candidatas`: si faltan las dos, la respuesta es un estado HTTP `422` (con `preflight_failed` y el error de campo `cuenta_required`); si vienen las dos, un `422` con `cuenta_y_candidatas_excluyentes`. Puede ser CLABE (18 dígitos), tarjeta (16 dígitos) o celular DiMo (10 dígitos), y el tipo se identifica por la longitud de sus dígitos. Los espacios y guiones que separan grupos ("0121 8000 4412 345678") se ignoran automáticamente antes de contar la longitud. Para celular DiMo, si el banco receptor no puede resolverse por el campo `receptor`, por los beneficiarios registrados ni por el directorio de cuentas, la respuesta es un estado HTTP `422` (con `bank_code_unresolvable_for_phone` en el cuerpo).'
          example: '012180004412345678'
          x-translations:
            en:
              description: 'Receiving bank account for the transfer. It is required when `cuentas_candidatas` is not sent: when both are missing, the response is an HTTP status `422` (with `preflight_failed` and the field error `cuenta_required`); when both are sent, a `422` with `cuenta_y_candidatas_excluyentes`. It can be a CLABE (18 digits), a card (16 digits) or a DiMo phone (10 digits), and the type is identified by the length of its digits. Spaces and hyphens separating groups ("0121 8000 4412 345678") are stripped automatically before the length is counted. For a DiMo phone, if the receiving bank cannot be resolved through the `receptor` field, through the registered beneficiaries or through the account directory, the response is an HTTP status `422` (with `bank_code_unresolvable_for_phone` in the body).'
          x-invalid-examples:
            - '12345678'
            - '1234567890123456789012'
            - ABCDEFGHIJKLMNOPQR
        cuentas_candidatas:
          type: array
          minItems: 2
          maxItems: 10
          uniqueItems: true
          items:
            type: string
            pattern: ^(\d{10}|\d{16}|\d{18})$
          description: |-
            Cuentas entre las que está la receptora de la transferencia, para cuando no se sabe cuál fue. Va en lugar de `cuenta_beneficiaria`: enviar las dos responde un estado HTTP `422` (con `cuenta_y_candidatas_excluyentes` en el cuerpo).

            Admite de 2 al máximo vigente de la plataforma, 3 por defecto y nunca más de 10; el `422` por exceso informa el máximo en `meta.max`. Cada cuenta es una CLABE (18 dígitos), una tarjeta (16) o un celular DiMo (10), con su dígito verificador válido y sin repetirse. Una lista que no cumple responde un `422` con `cuentas_candidatas_invalidas`, y señala en `field_errors` la posición que falló. Los dos errores ocurren antes de consumir cuota.

            Consume una sola unidad de cuota, lleve las cuentas que lleve. La consulta recorre las candidatas en el orden enviado y adopta la primera que coincide con la transferencia. La ganadora se publica completa en `normalized_data.cuenta_beneficiaria`, y `candidate_match` indica su posición. Si ninguna coincide, la respuesta es un estado HTTP `422` con el motivo en `code` (`cuenta_unresolvable_after_probes` cuando no hay un diagnóstico más preciso).
          example:
            - '012180004412345678'
            - '002010077777777771'
          x-translations:
            en:
              description: |-
                Accounts among which the receiving one is, for when it is not known which. It replaces `cuenta_beneficiaria`: sending both responds with an HTTP status `422` (with `cuenta_y_candidatas_excluyentes` in the body).

                It accepts from 2 up to the platform's current maximum, 3 by default and never more than 10; the `422` for an excess reports the maximum in `meta.max`. Each account is a CLABE (18 digits), a card (16) or a DiMo phone (10), with a valid check digit and no repeats. A list that does not comply responds with a `422` with `cuentas_candidatas_invalidas`, and points out in `field_errors` the position that failed. Both errors happen before any quota is consumed.

                It consumes a single quota unit, however many accounts it carries. The query goes through the candidates in the order sent and adopts the first one that matches the transfer. The winner is published in full in `normalized_data.cuenta_beneficiaria`, and `candidate_match` reports its position. If none matches, the response is an HTTP status `422` with the reason in `code` (`cuenta_unresolvable_after_probes` when there is no more precise diagnosis).
          x-invalid-examples:
            - - '012180004412345678'
            - - '012180004412345678'
              - '012180004412345678'
        receptor_participante:
          type: integer
          enum:
            - 0
            - 1
          default: 0
          description: 'Indica si el beneficiario de la transferencia es directamente la institución receptora del pago («Pago a Banco» en el CEP de Banxico) y no uno de sus cuentahabientes. `0` para una transferencia a un cuentahabiente (valor por defecto); `1` para pagos cuyo beneficiario es el banco —pago de tarjeta de crédito, de crédito o de servicios a la propia institución. Opcional: si se omite se asume `0`.'
          example: 0
          x-translations:
            en:
              description: 'Whether the transfer beneficiary is the receiving institution itself («Pago a Banco» in the Banxico CEP) rather than one of its account holders. `0` for a transfer to an account holder (the default); `1` for payments whose beneficiary is the bank —credit card, loan, or services paid to the institution itself. Optional: omitted means `0`.'
          x-invalid-examples:
            - 2
            - -1
            - true
        retry_policy:
          allOf:
            - $ref: '#/components/schemas/RetryPolicy'
          description: Política de **reintentos automáticos** para esta validación. Si se omite, se aplica la política general configurada para el usuario. Los reintentos NO consumen cuota de validaciones.
          x-translations:
            en:
              description: '**Automatic retry** policy for this validation. If omitted, the general policy configured for the user is applied. Retries do NOT consume validation quota.'
        client_ref:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[^\x00-\x1f\x7f]+$
          description: Referencia propia que se devuelve tal cual en la validación, en los webhooks de validación y como filtro exacto de `GET /v1/validations`. Texto de 1 a 64 caracteres, sin saltos de línea ni emoji; se recorta antes de guardarse. Un valor que no cumple se rechaza con un estado HTTP `422` (con `invalid_client_ref` en el cuerpo). No debe contener datos personales.
          example: orden-4812
          x-translations:
            en:
              description: Reference of your own that is returned as sent in the validation, in the validation webhooks, and as an exact filter of `GET /v1/validations`. Text of 1 to 64 characters, without line breaks or emoji; it is trimmed before being stored. A value that does not comply is rejected with an HTTP status `422` (with `invalid_client_ref` in the body). It must not contain personal data.
          x-invalid-examples:
            - ''
            - |-
              orden
              4812
    ValidationErrorCode:
      type: string
      description: |
        Código estable del fallo de una validación.
      enum:
        - network
        - captcha
        - max_retries_exceeded
        - ttl_expired
        - preflight_failed
        - rate_limit_exhausted
        - dispatch_failed
        - bank_code_unresolvable_for_phone
        - intra_bank_no_cep
        - not_found_identity
        - not_found_account
        - not_found_amount
        - not_found_account_and_amount
        - not_found_account_or_amount
      x-translations:
        en:
          description: |
            Stable code for a validation failure.
    RetryStateFull:
      type: object
      description: |
        Estado completo del ciclo de reintentos, con configuración, avance y situación actual.
      x-translations:
        en:
          description: |
            Full retry cycle state with configuration, progress, and current status.
      properties:
        enabled:
          type: boolean
          description: '`true` cuando existe un ciclo de reintentos activo.'
          example: true
          x-translations:
            en:
              description: '`true` when an active retry cycle exists.'
        max_retries:
          type:
            - integer
            - 'null'
          minimum: 1
          description: Número máximo de reintentos configurado; `null` cuando `enabled` es `false`.
          example: 3
          x-translations:
            en:
              description: |
                Configured maximum number of retries; `null` when `enabled` is `false`.
        interval_seconds:
          type:
            - integer
            - 'null'
          minimum: 300
          maximum: 86400
          description: Intervalo en segundos entre reintentos, de `300` a `86400`; `null` cuando `enabled` es `false`.
          example: 600
          x-translations:
            en:
              description: |
                Interval between retries in seconds, from `300` to `86400`; `null` when `enabled` is `false`.
        outcomes:
          type:
            - array
            - 'null'
          description: 'Resultados que originan un reintento: `not_found`, `cep_unavailable` y `error`; `null` cuando `enabled` es `false`.'
          items:
            type: string
            enum:
              - not_found
              - cep_unavailable
              - error
          example:
            - not_found
            - cep_unavailable
            - error
          x-translations:
            en:
              description: |
                Outcomes that trigger a retry: `not_found`, `cep_unavailable`, and `error`; `null` when `enabled` is `false`.
        attempts_completed:
          type: integer
          minimum: 0
          description: Cantidad de reintentos completados hasta el momento.
          example: 1
          x-translations:
            en:
              description: Number of retries completed so far.
        next_attempt_at:
          oneOf:
            - $ref: '#/components/schemas/TimestampUTC'
            - type: 'null'
          description: Fecha del próximo reintento; `null` cuando no hay uno programado.
          x-translations:
            en:
              description: |
                Date of the next retry; `null` when none is scheduled.
        resolved_at:
          oneOf:
            - $ref: '#/components/schemas/TimestampUTC'
            - type: 'null'
          description: |
            Fecha en que un reintento obtuvo `valid`; `null` mientras no ocurra.
          x-translations:
            en:
              description: |
                Date when a retry produced `valid`; `null` until that occurs.
        exhausted_at:
          oneOf:
            - $ref: '#/components/schemas/TimestampUTC'
            - type: 'null'
          description: |
            Fecha en que se agotaron los intentos; `null` mientras no ocurra.
          x-translations:
            en:
              description: |
                Date when attempts were exhausted; `null` until that occurs.
        cancelled_at:
          oneOf:
            - $ref: '#/components/schemas/TimestampUTC'
            - type: 'null'
          description: |
            Fecha de cancelación del ciclo; `null` mientras no ocurra.
          x-translations:
            en:
              description: |
                Cycle cancellation date; `null` until that occurs.
        terminal_state:
          type:
            - string
            - 'null'
          enum:
            - pending
            - resolved
            - exhausted
            - cancelled
            - null
          description: 'Situación del ciclo: `pending`, `resolved`, `exhausted` o `cancelled`. Es `null` cuando la validación no tiene un ciclo de reintentos.'
          example: pending
          x-translations:
            en:
              description: |
                Cycle state: `pending`, `resolved`, `exhausted`, or `cancelled`. It is `null` when the validation has no retry cycle.
    Validation:
      type: object
      description: |
        Recurso JSON:API de una validación SPEI devuelto por la consulta individual y la validación síncrona.
      x-translations:
        en:
          description: |
            JSON:API SPEI validation resource returned by the individual lookup and synchronous validation.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          required:
            - id
            - type
            - attributes
          properties:
            id:
              type: string
              format: uuid
              description: Identificador único de la validación (UUID v4).
              example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
              x-translations:
                en:
                  description: Unique identifier of the validation (UUID v4).
            type:
              type: string
              enum:
                - validation
              description: Tipo de recurso JSON:API. Siempre `validation`.
              example: validation
              x-translations:
                en:
                  description: JSON:API resource type. Always `validation`.
            attributes:
              type: object
              description: Datos canónicos de la validación.
              x-translations:
                en:
                  description: Canonical validation data.
              properties:
                validation_type:
                  type: string
                  enum:
                    - direct
                    - ocr
                  description: 'Modalidad de validación — `direct`: Captura manual de los campos; `ocr`: Lectura de la imagen del comprobante.'
                  example: direct
                  x-translations:
                    en:
                      description: 'Validation mode — `direct`: Manual entry of the fields; `ocr`: Reading of the receipt image.'
                is_playground:
                  type: boolean
                  description: '`true` cuando el resultado proviene de una simulación del banco de pruebas (Playground). La simulación se ejecuta sin llamadas a Banxico, sin consumo de cuota, sin persistencia y sin emisión de webhooks o notificaciones.'
                  example: false
                  x-translations:
                    en:
                      description: '`true` when the result comes from a playground simulation. The simulation runs without Banxico calls, quota consumption, persistence, webhooks, or notifications.'
                status:
                  type: string
                  enum:
                    - queued
                    - processing
                    - valid
                    - not_found
                    - cep_unavailable
                    - invalid
                    - returned
                    - failed
                    - error
                  x-terminal-values:
                    - valid
                    - not_found
                    - cep_unavailable
                    - invalid
                    - returned
                    - failed
                    - error
                  description: 'Estado del ciclo de vida — `queued`: Pendiente de procesamiento; `processing`: En proceso; `valid`: El CEP fue encontrado y cuadra; `not_found`: Banxico no encuentra la operación; `cep_unavailable`: Banxico reconoce la transacción pero el CEP no está disponible; `invalid`: Banxico no devolvió un veredicto reconocible; `returned`: la operación se liquidó y después se devolvió; `failed`: La validación se detuvo por un problema en los datos enviados; `error`: La validación se detuvo por un fallo del servicio.'
                  example: valid
                  x-translations:
                    en:
                      description: 'Lifecycle state — `queued`: Awaiting processing; `processing`: In progress; `valid`: The CEP was found and matches; `not_found`: Banxico cannot find the operation; `cep_unavailable`: Banxico recognizes the transaction but the CEP is not available; `invalid`: Banxico did not return a recognizable verdict; `returned`: the transfer was settled and later returned; `failed`: The validation stopped because of a problem in the data sent; `error`: The validation stopped because of a service failure.'
                banxico_status:
                  type:
                    - string
                    - 'null'
                  description: 'Veredicto reportado por Banxico. Puede ser: `valid`, `not_found`, `cep_unavailable`, `invalid`, `returned` o `error`. Permanece `pending` mientras no exista un veredicto de Banxico. `returned` significa que la operación se liquidó y la institución beneficiaria la devolvió después — el CEP, si existe, se sigue entregando. Un `valid` no es definitivo: es el veredicto del momento de la consulta, y pasa a `returned` cuando Banxico reporta la devolución en una revisión posterior, que se pide con `POST /v1/validations/{id}/recheck` o que el servicio hace solo durante las primeras 72 horas (se emite el webhook `validation.returned`). Solo toma `error` cuando la petición llegó a Banxico y el servicio falló; `error_code` identifica la causa.'
                  example: valid
                  x-translations:
                    en:
                      description: 'Verdict reported by Banxico. It can be `valid`, `not_found`, `cep_unavailable`, `invalid`, `returned`, or `error`. It remains `pending` while no Banxico verdict exists. `returned` means the transfer settled and the beneficiary institution later sent it back — the CEP, when there is one, is still delivered. A `valid` is not final: it is the verdict of the moment of the query, and it moves to `returned` when Banxico reports the return in a follow-up check, which is requested with `POST /v1/validations/{id}/recheck` or which the service runs by itself during the first 72 hours (the `validation.returned` webhook is emitted). It becomes `error` only when the request reached Banxico and the service failed; `error_code` identifies the cause.'
                processing_time_ms:
                  type:
                    - integer
                    - 'null'
                  description: Tiempo (en milisegundos) empleados en procesar la validación.
                  example: 1320
                  x-translations:
                    en:
                      description: Milliseconds spent processing the validation.
                request_data:
                  type: object
                  description: Copia de los campos de la petición original. `fecha` conserva la fecha de envío tal como se envió, aunque Banxico haya encontrado el pago con otra (`banxico_result._fecha`).
                  additionalProperties: true
                  x-translations:
                    en:
                      description: Copy of the original request fields. `fecha` keeps the sending date exactly as sent, even when Banxico found the payment with another one (`banxico_result._fecha`).
                client_ref:
                  type: string
                  minLength: 1
                  maxLength: 64
                  description: Referencia propia que se envió en la petición (`client_ref`), devuelta tal cual. Solo aparece cuando la petición la incluyó. Sirve para relacionar la validación con un pedido propio, y `GET /v1/validations` la acepta como filtro exacto. No debe contener datos personales.
                  example: orden-4812
                  x-translations:
                    en:
                      description: Reference of your own sent in the request (`client_ref`), returned as sent. It only appears when the request included it. It lets you tie the validation to an order of your own, and `GET /v1/validations` accepts it as an exact filter. It must not contain personal data.
                created_at:
                  type: string
                  format: date-time
                  description: Fecha y hora, en ISO 8601 UTC, de creación de la validación.
                  example: '2025-03-15T14:22:10Z'
                  x-translations:
                    en:
                      description: Validation creation timestamp in ISO 8601 UTC.
                completed_at:
                  type:
                    - string
                    - 'null'
                  format: date-time
                  description: Fecha y hora de la finalización, en ISO 8601 UTC. `null` mientras no haya terminado.
                  example: '2025-03-15T14:22:11Z'
                  x-translations:
                    en:
                      description: Date and time of completion, in ISO 8601 UTC. `null` while it has not finished.
                enqueued_at:
                  type:
                    - string
                    - 'null'
                  format: date-time
                  description: Fecha y hora, en ISO 8601 UTC, de entrada a la cola.
                  x-translations:
                    en:
                      description: Queue-entry timestamp in ISO 8601 UTC.
                  example: '2026-04-30T10:15:00Z'
                processing_started_at:
                  type:
                    - string
                    - 'null'
                  format: date-time
                  description: Fecha y hora, en ISO 8601 UTC, de inicio del procesamiento.
                  x-translations:
                    en:
                      description: Processing start timestamp in ISO 8601 UTC.
                  example: '2026-04-30T10:15:00Z'
                expires_at:
                  type:
                    - string
                    - 'null'
                  format: date-time
                  description: Fecha y hora, en ISO 8601 UTC, de expiración para validaciones en cola. Tras esta marca, la validación pasa a `failed`.
                  x-translations:
                    en:
                      description: Expiration timestamp for queued validations in ISO 8601 UTC. After this, the validation moves to `failed`.
                  example: '2026-04-30T10:15:00Z'
                etag_version:
                  type:
                    - integer
                    - 'null'
                  description: Versión incremental (usada con `If-None-Match` en consultas condicionales).
                  example: 1
                  x-translations:
                    en:
                      description: Incremental version used with `If-None-Match` in conditional requests.
                image_path:
                  type:
                    - string
                    - 'null'
                  description: 'Referencia relativa del comprobante, imagen o PDF (solo si `validation_type` es del tipo `ocr`). No se publica cuando el archivo no se conserva: ver `image_retained`.'
                  x-translations:
                    en:
                      description: 'Relative reference to the receipt, image or PDF, only when `validation_type` is `ocr`. It is not published when the file is not kept: see `image_retained`.'
                  example: ocr/2026/04/a1b2c3d4.jpg
                image_retained:
                  type: boolean
                  description: |-
                    `true` cuando la plataforma conserva el archivo del comprobante; `false` cuando no, porque la petición envió `retain_image=false` o porque la validación se purgó. Solo aparece en las validaciones de tipo `ocr`.

                    Con `false`, `GET /v1/validations/{id}/image` responde un estado HTTP `410` (con `image_not_retained` o `validation_purged` en el cuerpo).
                  example: true
                  x-translations:
                    en:
                      description: |-
                        `true` when the platform keeps the receipt file; `false` when it does not, because the request sent `retain_image=false` or because the validation was purged. It only appears on validations of type `ocr`.

                        With `false`, `GET /v1/validations/{id}/image` responds with an HTTP status `410` (with `image_not_retained` or `validation_purged` in the body).
                purged_at:
                  type: string
                  format: date-time
                  description: |-
                    Fecha y hora, en ISO 8601 UTC, en que la validación se purgó con `POST /v1/validations/{id}/purge/execute`. Solo aparece en una validación purgada, que queda como una lápida.

                    Una lápida conserva `id`, `validation_type`, `status`, `banxico_status`, las fechas, `processing_time_ms`, `error_code` y el monto en `normalized_data.monto`. No conserva el archivo, el CEP, `request_data`, `ocr_result` ni `banxico_result`, y sus recursos derivados responden un estado HTTP `410` (con `validation_purged` en el cuerpo).
                  example: '2026-10-02T09:30:00Z'
                  x-translations:
                    en:
                      description: |-
                        Date and time, in ISO 8601 UTC, when the validation was purged with `POST /v1/validations/{id}/purge/execute`. It only appears on a purged validation, which is left as a tombstone.

                        A tombstone keeps `id`, `validation_type`, `status`, `banxico_status`, the dates, `processing_time_ms`, `error_code`, and the amount in `normalized_data.monto`. It does not keep the file, the CEP, `request_data`, `ocr_result`, or `banxico_result`, and its derived resources respond with an HTTP status `410` (with `validation_purged` in the body).
                ocr_result:
                  type:
                    - object
                    - 'null'
                  additionalProperties: true
                  description: |-
                    Campos extraídos del comprobante (solo si `validation_type` es del tipo `ocr`).

                    `extraction_source` vale `cep_pdf` cuando el comprobante era el CEP de Banxico en PDF y sus datos se leyeron
                    de su texto, sin OCR. `comprobantes_detectados` es el número de comprobantes que se encontraron en un PDF.
                  x-translations:
                    en:
                      description: |-
                        Fields extracted from the receipt, only when `validation_type` is `ocr`.

                        `extraction_source` is `cep_pdf` when the receipt was the Banxico CEP as a PDF and its data was read
                        from its text, without OCR. `comprobantes_detectados` is the number of receipts found in a PDF.
                ocr_confidence:
                  type:
                    - number
                    - 'null'
                  format: float
                  minimum: 0
                  maximum: 1
                  description: Puntaje de la extracción OCR 0–1 (solo si `validation_type` es del tipo `ocr`).
                  example: 0.94
                  x-translations:
                    en:
                      description: OCR score from 0 to 1, only when `validation_type` is `ocr`.
                normalized_data:
                  type:
                    - object
                    - 'null'
                  additionalProperties: true
                  description: Campos normalizados después de la extracción OCR (solo si `validation_type` es del tipo `ocr`).
                  x-translations:
                    en:
                      description: Fields normalized after OCR, only when `validation_type` is `ocr`.
                normalization_warnings:
                  type:
                    - array
                    - 'null'
                  items:
                    type: string
                  description: Advertencias de normalización presentes. Permiten explicar un resultado `not_found` inesperado.
                  x-translations:
                    en:
                      description: Normalization warnings present. They can explain an unexpected `not_found` result.
                duplicate_of:
                  type: object
                  required:
                    - id
                    - created_at
                  description: |-
                    Validación previa que ya había confirmado esta misma transferencia en la misma cuenta. Solo aparece cuando existe una validación `valid`, no retirada, con la misma clave de rastreo; nunca apunta a una validación de otra cuenta.

                    Es la forma estructurada del aviso que también se añade a `normalization_warnings`. No cambia el veredicto: la validación se consulta y se cobra igual.
                  properties:
                    id:
                      type: string
                      format: uuid
                      description: Identificador de la validación previa.
                      example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
                      x-translations:
                        en:
                          description: Identifier of the earlier validation.
                    created_at:
                      type: string
                      format: date-time
                      description: Fecha y hora, en ISO 8601 UTC, de creación de la validación previa.
                      example: '2026-10-01T15:04:05Z'
                      x-translations:
                        en:
                          description: Creation date and time of the earlier validation, in ISO 8601 UTC.
                  x-translations:
                    en:
                      description: |-
                        Earlier validation that had already confirmed this same transfer in the same account. It only appears when a `valid`, non-withdrawn validation with the same tracking key exists; it never points to a validation of another account.

                        It is the structured form of the warning that is also added to `normalization_warnings`. It does not change the verdict: the validation is queried and charged all the same.
                account_conflict:
                  type: object
                  required:
                    - sent_last4
                    - read_last4
                  description: |-
                    Conflicto entre la cuenta enviada y la que muestra la imagen. Solo aparece en validaciones de tipo `ocr`, cuando se envió `cuenta_beneficiaria` y la imagen muestra otra cuenta completa de la misma longitud, válida y con confianza suficiente.

                    Prevalece la cuenta enviada, que es la que se consulta en Banxico: el campo no cambia el veredicto. Si el pago fue a la cuenta de la imagen, lo esperable es un `not_found`. Solo trae los últimos 4 dígitos de cada cuenta, y el conflicto también se añade a `normalization_warnings`.
                  properties:
                    sent_last4:
                      type: string
                      pattern: ^\d{1,4}$
                      description: Últimos 4 dígitos de la cuenta enviada en `cuenta_beneficiaria`.
                      example: '5678'
                      x-translations:
                        en:
                          description: Last 4 digits of the account sent in `cuenta_beneficiaria`.
                    read_last4:
                      type: string
                      pattern: ^\d{1,4}$
                      description: Últimos 4 dígitos de la cuenta completa que muestra la imagen.
                      example: '9012'
                      x-translations:
                        en:
                          description: Last 4 digits of the full account shown in the image.
                  x-translations:
                    en:
                      description: |-
                        Conflict between the account sent and the one shown in the image. It only appears on `ocr` validations, when `cuenta_beneficiaria` was sent and the image shows another full account of the same length, valid and with enough confidence.

                        The account sent prevails and is the one queried at Banxico: the field does not change the verdict. If the payment went to the account in the image, a `not_found` is the expected outcome. It carries only the last 4 digits of each account, and the conflict is also added to `normalization_warnings`.
                candidate_match:
                  type: object
                  required:
                    - index
                    - account_last4
                  description: |-
                    Cuál de las `cuentas_candidatas` enviadas ganó. Solo aparece cuando la petición llevó esa lista y una de las cuentas coincidió con la transferencia.

                    La cuenta completa va en `normalized_data.cuenta_beneficiaria`; este objeto indica qué posición de la lista es, para correlacionarla con la entidad propia del integrador sin comparar cuentas. No cambia el veredicto.
                  properties:
                    index:
                      type: integer
                      minimum: 0
                      description: Posición (desde 0) de la cuenta ganadora en el arreglo `cuentas_candidatas`, tal como se envió.
                      example: 1
                      x-translations:
                        en:
                          description: Position (from 0) of the winning account in the `cuentas_candidatas` array, as sent.
                    account_last4:
                      type: string
                      pattern: ^\d{1,4}$
                      description: Últimos 4 dígitos de la cuenta ganadora.
                      example: '7771'
                      x-translations:
                        en:
                          description: Last 4 digits of the winning account.
                  x-translations:
                    en:
                      description: |-
                        Which of the `cuentas_candidatas` sent won. It only appears when the request carried that list and one of the accounts matched the transfer.

                        The full account is in `normalized_data.cuenta_beneficiaria`; this object says which position of the list it is, so it can be correlated with the integrator's own entity without comparing accounts. It does not change the verdict.
                is_masked:
                  type:
                    - boolean
                    - 'null'
                  description: Indica si la cuenta beneficiaria viene enmascarada en el comprobante cargado, sea una CLABE, una tarjeta o un celular. Solo se incluye cuando vale `true` y solo si `validation_type` es del tipo `ocr`.
                  x-translations:
                    en:
                      description: 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 `true`, and only when `validation_type` is `ocr`.
                banxico_result:
                  type:
                    - object
                    - 'null'
                  additionalProperties: true
                  description: |-
                    Datos devueltos por Banxico. Las propiedades tipadas abajo son las 20 que declara el esquema oficial del complemento SPEI.

                    `_ocr_correction` conserva los campos corregidos, su lectura original (`requested`), el valor consultado (`used`) y la confirmación (`confirmed_by`). `status_query` indica una hipótesis confirmada por la consulta de estado; `cep` indica que también se obtuvo el comprobante. La consulta de estado por sí sola no verifica una transferencia.

                    `_cep_upload` aparece cuando el comprobante subido era el CEP en PDF y Banxico confirmó el pago.
                    `authenticated` es `true` cuando su sello y su cadena original coinciden con los del CEP oficial;
                    con `false`, `differing_fields` lista los campos que difieren y `normalization_warnings` lo avisa.
                    El veredicto sigue siendo el de Banxico.

                    `_payment_status` es el estado oficial del pago, tal como lo dio Banxico la última vez que se le preguntó:
                    `code` (`liquidado`, `en_proceso`, `cancelado`, `rechazado`, `en_proceso_devolucion`, `devuelto` o `desconocido`),
                    `label`, `settled`, `reversed` y `checked_at` (ISO 8601 UTC).
                    Su ausencia significa que no se pudo saber, nunca que el pago esté liquidado.
                    Se actualiza en cada revisión posterior, y con `devuelto` o `en_proceso_devolucion` la validación pasa a `returned`.

                    `_fecha` aparece cuando Banxico no encontró el pago con la fecha enviada y sí con otra.
                    `used` es la fecha con la que se encontró, `requested` es la fecha con la que se consultó primero
                    y `reason` es el motivo por el que se probó `used`.
                    Valores de `reason`:
                    `clave_embedded_date`: La clave de rastreo incrusta otra fecha, a un día hábil SPEI o menos de la enviada;
                    `cutoff_18h`: La clave no incrusta fecha y el comprobante indica una hora cercana al corte de las 18:00,
                    así que se probó el día siguiente;
                    `dia_operacion`: La clave incrusta la fecha enviada, porque el comprobante trae la de operación,
                    y se probó un día anterior hasta el día hábil previo.
                    El veredicto sigue siendo el de Banxico, y `request_data.fecha` conserva la fecha enviada.
                  x-translations:
                    en:
                      description: |-
                        Data returned by the Banxico CEP. The typed properties below are the 20 declared by the official SPEI complement schema.

                        `_ocr_correction` records corrected fields, their original reading (`requested`), the queried value (`used`) and confirmation (`confirmed_by`). `status_query` indicates a hypothesis confirmed by the payment status query; `cep` indicates that the receipt was also obtained. A status query alone does not verify a transfer.

                        `_cep_upload` appears when the uploaded receipt was the CEP as a PDF and Banxico confirmed the payment.
                        `authenticated` is `true` when its seal and original string match those of the official CEP;
                        with `false`, `differing_fields` lists the fields that differ and `normalization_warnings` reports it.
                        The verdict is still Banxico's.

                        `_payment_status` is the official payment status, as Banxico gave it the last time it was asked:
                        `code` (`liquidado`, `en_proceso`, `cancelado`, `rechazado`, `en_proceso_devolucion`, `devuelto`, or `desconocido`),
                        `label`, `settled`, `reversed`, and `checked_at` (ISO 8601 UTC).
                        Its absence means it could not be determined, never that the payment is settled.
                        It is updated on every follow-up check, and with `devuelto` or `en_proceso_devolucion` the validation moves to `returned`.

                        `_fecha` appears when Banxico did not find the payment with the date sent and did find it with another.
                        `used` is the date it was found with, `requested` is the date first queried, and `reason` is why `used` was tried.
                        Values of `reason`:
                        `clave_embedded_date`: The tracking key embeds another date, at most one SPEI business day from the one sent;
                        `cutoff_18h`: The key embeds no date and the receipt shows a time close to the 18:00 cutoff,
                        so the next day was tried;
                        `dia_operacion`: The key embeds the date sent, because the receipt carries the operation date,
                        and an earlier day was tried back to the previous business day.
                        The verdict is still Banxico's, and `request_data.fecha` keeps the date sent.
                  properties:
                    operationDate:
                      type: string
                      description: 'Día hábil SPEI que Banxico asignó a la operación, tal como lo reporta el CEP (`YYYY-MM-DD`), en hora del centro de México. Puede diferir de la fecha de envío: lo enviado a partir de las 18:00, en sábado, en domingo o en un día inhábil opera el siguiente día hábil.'
                      example: '2025-03-15'
                      x-translations:
                        en:
                          description: 'SPEI business day that Banxico assigned to the operation, as reported by the CEP (`YYYY-MM-DD`), in Mexico central time. It can differ from the sending date: what is sent from 18:00 on, on a Saturday, on a Sunday, or on a non-business day operates on the next business day.'
                    processingTime:
                      type: string
                      description: Hora de la operación tal como la reporta el CEP (`HH:MM:SS`), en hora del centro de México y no en UTC.
                      example: '14:22:10'
                      x-translations:
                        en:
                          description: Operation time as reported by the CEP (`HH:MM:SS`), in Mexico central time, not UTC.
                    speiKey:
                      type: string
                      description: Clave de la operación dentro del sistema SPEI (`ClaveSPEI` del CEP). Distinta de la clave de rastreo.
                      x-translations:
                        en:
                          description: Operation key within the SPEI system (the CEP's `ClaveSPEI`). Distinct from the tracking key.
                    trackingKey:
                      type: string
                      description: Clave de rastreo de la operación SPEI — la misma que se envía en la petición de validación.
                      example: MBAN01002503151422ABCDEF
                      x-translations:
                        en:
                          description: SPEI tracking key of the operation — the same one sent in the validation request.
                    digitalSignature:
                      type: string
                      description: Sello digital del CEP (`sello`) — la firma que Banxico calcula sobre la operación.
                      x-translations:
                        en:
                          description: Digital seal of the CEP (`sello`) — the signature Banxico computes over the operation.
                    certificateNumber:
                      type: string
                      description: Número del certificado digital que Banxico usó para sellar el CEP.
                      x-translations:
                        en:
                          description: Serial number of the digital certificate Banxico used to seal the CEP.
                    cadenaCda:
                      type: string
                      description: 'Cadena original de la operación (`cadenaCDA`): los 47 campos que la institución envió a Banxico, separados por `|`, incluidas ambas fechas (operación y captura) y el tipo de pago. Permite auditar el CEP sin volver a pedirlo.'
                      x-translations:
                        en:
                          description: 'Original data string of the operation (`cadenaCDA`): the 47 fields the institution sent to Banxico, pipe-separated, including both dates (operation and capture) and the payment type. Lets the CEP be audited without requesting it again.'
                    senderBank:
                      type: string
                      description: Banco emisor de la operación, tal como lo identifica el CEP.
                      example: BBVA
                      x-translations:
                        en:
                          description: Sending bank of the operation, as identified by the CEP.
                    senderName:
                      type: string
                      description: Nombre del ordenante de la operación, tal como lo reporta el CEP.
                      x-translations:
                        en:
                          description: Name of the operation's sender, as reported by the CEP.
                    senderAccountType:
                      type: string
                      description: Tipo de cuenta del ordenante (por ejemplo, CLABE o tarjeta), tal como lo reporta el CEP.
                      x-translations:
                        en:
                          description: Sender's account type (e.g. CLABE or card), as reported by the CEP.
                    senderAccount:
                      type: string
                      description: Cuenta del ordenante, tal como la reporta el CEP, sin enmascarar en este recurso (consulta privada, propia del dueño de la validación).
                      x-translations:
                        en:
                          description: Sender's account number, as reported by the CEP — unmasked in this resource (a private lookup, owned by the validation's account).
                    senderRfc:
                      type: string
                      description: RFC del ordenante, tal como lo reporta el CEP.
                      x-translations:
                        en:
                          description: Sender's RFC (Mexican tax ID), as reported by the CEP.
                    receiverBank:
                      type: string
                      description: Banco receptor de la operación, tal como lo identifica el CEP.
                      example: STP
                      x-translations:
                        en:
                          description: Receiving bank of the operation, as identified by the CEP.
                    beneficiaryName:
                      type: string
                      description: Nombre del beneficiario de la operación, tal como lo reporta el CEP.
                      x-translations:
                        en:
                          description: Name of the operation's beneficiary, as reported by the CEP.
                    beneficiaryAccountType:
                      type: string
                      description: Tipo de cuenta del beneficiario (por ejemplo, CLABE o tarjeta), tal como lo reporta el CEP.
                      x-translations:
                        en:
                          description: Beneficiary's account type (e.g. CLABE or card), as reported by the CEP.
                    beneficiaryAccount:
                      type: string
                      description: Cuenta del beneficiario, tal como la reporta el CEP, sin enmascarar en este recurso (consulta privada, propia del dueño de la validación). Los webhooks salientes sí la enmascaran a los últimos 4 dígitos en `banxico_confirmed.beneficiaryAccount` (ver la guía de webhooks).
                      x-translations:
                        en:
                          description: 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 `banxico_confirmed.beneficiaryAccount` (see the webhooks guide).
                    beneficiaryRfc:
                      type: string
                      description: RFC del beneficiario, tal como lo reporta el CEP.
                      x-translations:
                        en:
                          description: Beneficiary's RFC (Mexican tax ID), as reported by the CEP.
                    amount:
                      type: number
                      format: float
                      description: Monto de la operación que Banxico confirmó por el CEP, en pesos.
                      example: 1500
                      x-translations:
                        en:
                          description: Operation amount Banxico confirmed via the CEP, in pesos.
                    iva:
                      type: number
                      format: float
                      description: IVA de la operación reportado por el CEP, en pesos.
                      x-translations:
                        en:
                          description: VAT (IVA) of the operation reported by the CEP, in pesos.
                    paymentConcept:
                      type: string
                      description: Concepto de pago de la operación, tal como lo reporta el CEP.
                      x-translations:
                        en:
                          description: Payment concept of the operation, as reported by the CEP.
                error_message:
                  type:
                    - string
                    - 'null'
                  description: Mensaje legible del error terminal (si aplica).
                  x-translations:
                    en:
                      description: Human-readable error message when terminal.
                  example: Banxico no respondió tras tres intentos.
                error_code:
                  oneOf:
                    - $ref: '#/components/schemas/ValidationErrorCode'
                    - type: 'null'
                  description: |-
                    Código estable del error terminal (si aplica).

                    Tras `not_found`, el diagnóstico puede indicar `not_found_identity` (fecha, criterio o bancos), `not_found_account` (cuenta), `not_found_amount` (importe), `not_found_account_and_amount` (ambos) o `not_found_account_or_amount` (operación encontrada, sin aislar el campo). Un diagnóstico no cambia el veredicto ni acredita el pago. Sin respuesta concluyente, el campo queda en `null`.
                  x-translations:
                    en:
                      description: |-
                        Stable code of the terminal error, when there is one.

                        After `not_found`, diagnosis may indicate `not_found_identity` (date, criterion or banks), `not_found_account` (account), `not_found_amount` (amount), `not_found_account_and_amount` (both) or `not_found_account_or_amount` (payment found, field not isolated). A diagnosis does not change the verdict or prove payment. Without a conclusive response, the field remains `null`.
                batch_id:
                  type:
                    - integer
                    - 'null'
                  description: Identificador del trabajo de importación masiva (si aplica).
                  x-translations:
                    en:
                      description: Bulk import job identifier, when applicable.
                  example: 318
                batch_position:
                  type:
                    - integer
                    - 'null'
                  description: Posición dentro del trabajo (comenzando en 1).
                  x-translations:
                    en:
                      description: Position within the job, starting at 1.
                  example: 12
                retry_state:
                  allOf:
                    - $ref: '#/components/schemas/RetryStateFull'
                  description: Estado completo del ciclo de reintentos (siempre presente). Si la validación no tiene reintentos activos, `enabled` es `false` y los campos de política son `null`. Las validaciones de importación masiva siempre tienen `enabled` igual a `false`.
                  x-translations:
                    en:
                      description: |
                        Full retry cycle state. Always present; if retries are not active, `enabled` is `false` and policy fields are `null`. Bulk import rows always have `enabled` equal to `false`.
            links:
              type: object
              description: Enlaces relacionados (JSON:API `links`).
              x-translations:
                en:
                  description: Related links (JSON:API `links`).
              properties:
                self:
                  type: string
                  description: Enlace canónico al detalle de la validación.
                  example: /v1/validations/3fa85f64-5717-4562-b3fc-2c963f66afa6
                  x-translations:
                    en:
                      description: Canonical link to the validation details.
                cep_xml:
                  type:
                    - string
                    - 'null'
                  description: URL del CEP en formato XML. `null` cuando `status` no es `valid`, o es `returned` sin comprobante (la operación se devolvió antes de emitirse un CEP).
                  x-translations:
                    en:
                      description: URL of the CEP in XML format. `null` when `status` is not `valid`, or is `returned` with no receipt (the transfer was returned before a CEP was ever issued).
                  example: /v1/validations/a1b2c3d4-e5f6-7890-abcd-ef0123456789/cep.xml
                cep_pdf:
                  type:
                    - string
                    - 'null'
                  description: URL del CEP en formato PDF. `null` cuando `status` no es `valid`, o es `returned` sin comprobante (la operación se devolvió antes de emitirse un CEP).
                  x-translations:
                    en:
                      description: URL of the CEP in PDF format. `null` when `status` is not `valid`, or is `returned` with no receipt (the transfer was returned before a CEP was ever issued).
                  example: /v1/validations/a1b2c3d4-e5f6-7890-abcd-ef0123456789/cep.pdf
    ValidationQueued:
      type: object
      description: |
        Respuesta asíncrona de `POST /v1/validate?async=1` y `POST /v1/validate-ocr?async=1`. Contiene la validación encolada y los datos necesarios para consultar su resultado en `GET /v1/validations/{id}`.
      x-translations:
        en:
          description: |
            Asynchronous response from `POST /v1/validate?async=1` and `POST /v1/validate-ocr?async=1`. Contains the queued validation and the data needed to retrieve its result from `GET /v1/validations/{id}`.
      properties:
        data:
          type: object
          description: Envoltorio JSON:API del recurso encolado.
          x-translations:
            en:
              description: JSON:API wrapper for the queued resource.
          allOf:
            - $ref: '#/components/schemas/JsonApiResourceBase'
            - type: object
              properties:
                type:
                  type: string
                  enum:
                    - validation
                  description: Tipo de recurso JSON:API. Siempre `validation`.
                  example: validation
                  x-translations:
                    en:
                      description: JSON:API resource type. Always `validation`.
                id:
                  type: string
                  format: uuid
                  description: Identificador único de la validación creada (UUID v4), utilizable en `GET /v1/validations/{id}`.
                  example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
                  x-translations:
                    en:
                      description: Unique identifier of the created validation (UUID v4), usable in `GET /v1/validations/{id}`.
                attributes:
                  type: object
                  description: Datos de la validación encolada.
                  x-translations:
                    en:
                      description: Fields of the queued resource.
                  properties:
                    validation_id:
                      type: string
                      format: uuid
                      description: UUID de la validación (idéntico a `data.id`).
                      example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
                      x-translations:
                        en:
                          description: UUID of the validation (identical to `data.id`).
                    status:
                      type: string
                      enum:
                        - queued
                      description: Estado inmediato tras el encolamiento. Siempre `queued`.
                      example: queued
                      x-translations:
                        en:
                          description: Immediate status after queuing. Always `queued`.
                    etag_version:
                      type: integer
                      minimum: 0
                      description: |
                        Versión inicial del recurso para polling condicional con `If-None-Match`. Enviarla como ETag evita respuestas innecesarias cuando el estado no ha cambiado.
                      example: 0
                      x-translations:
                        en:
                          description: |
                            Initial resource version for conditional polling with `If-None-Match`. Sending it as an ETag avoids unnecessary responses when the status has not changed.
                    enqueued_at:
                      $ref: '#/components/schemas/TimestampUTC'
                    expires_at:
                      $ref: '#/components/schemas/TimestampUTC'
                    retry_state:
                      allOf:
                        - $ref: '#/components/schemas/RetryStateFull'
                      description: |
                        Estado del ciclo de reintentos automáticos al momento del encolamiento. Siempre presente; si no se configuró `retry_policy` en el body ni en la política del usuario, `enabled=false` y los campos de política son `null`.
                      x-translations:
                        en:
                          description: |
                            Automatic retry cycle state at queue time. Always present; if neither a body `retry_policy` nor a user default policy was provided, `enabled=false` and policy fields are `null`.
                    client_ref:
                      type: string
                      minLength: 1
                      maxLength: 64
                      description: Referencia propia enviada en la petición (`client_ref`), devuelta tal cual. Solo aparece cuando la petición la incluyó.
                      example: orden-4812
                      x-translations:
                        en:
                          description: Reference of your own sent in the request (`client_ref`), returned as sent. It only appears when the request included it.
        meta:
          type: object
          description: Metadatos de control del flujo de polling.
          x-translations:
            en:
              description: Control metadata for the polling flow.
          properties:
            next_poll_after_seconds:
              type: integer
              minimum: 1
              description: |
                Segundos recomendados de espera antes del primer poll a `GET /v1/validations/{id}`. Sondear antes de este intervalo puede activar el límite de solicitudes.
              example: 2
              x-translations:
                en:
                  description: |
                    Recommended seconds to wait before the first poll to `GET /v1/validations/{id}`. Polling before this interval can trigger the request limit.
            playground:
              type: boolean
              description: '`true` cuando la validación pertenece al banco de pruebas (Playground). Campo reservado para compatibilidad: las respuestas asíncronas actuales lo omiten, porque el banco de pruebas no encola validaciones.'
              example: false
              x-translations:
                en:
                  description: '`true` when the validation belongs to the playground. Field reserved for compatibility: current asynchronous responses omit it, because the playground does not queue validations.'
    OcrValidationRequest:
      type: object
      anyOf:
        - required:
            - image
        - required:
            - image_url
      not:
        required:
          - cuenta_beneficiaria
          - cuentas_candidatas
      description: |
        Cuerpo de `POST /v1/validate-ocr` con el comprobante en `image` o `image_url`; exige al menos uno de ambos campos. `cuenta_beneficiaria` y `cuentas_candidatas` no pueden ir juntas.
      x-translations:
        en:
          description: |
            Body for `POST /v1/validate-ocr` with the receipt in `image` or `image_url`; requires at least one of the two fields. `cuenta_beneficiaria` and `cuentas_candidatas` cannot be sent together.
      properties:
        image:
          type: string
          format: byte
          description: 'Comprobante codificado en base64, en imagen o en PDF. Si también se proporciona `image_url`, solo se considera `image`. Formatos aceptados: JPEG, PNG, WebP o PDF de 1 a 3 páginas. Tamaño máximo: `12 MB`. Dimensiones máximas de una imagen: `12000px` por lado. Admite el prefijo `data:image/...;base64,` o `data:application/pdf;base64,`.'
          x-translations:
            en:
              description: 'Receipt encoded in base64, as an image or a PDF. If `image_url` is also provided, only `image` is considered. Accepted formats: JPEG, PNG, WebP, or a PDF of 1 to 3 pages. Maximum size: `12 MB`. Maximum dimensions of an image: `12000px` per side. Accepts the `data:image/...;base64,` or `data:application/pdf;base64,` prefix.'
          example: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==
        image_url:
          type: string
          format: uri
          description: 'URL pública (HTTPS) del comprobante. Sigue los mismos límites de formato y tamaño que `image`: JPEG, PNG, WebP o PDF de 1 a 3 páginas, con un máximo de `12 MB`.'
          example: https://storage.example.com/receipts/comprobante-2025-03.jpg
          x-translations:
            en:
              description: |
                Public HTTP or HTTPS URL of the receipt. The file is fetched when the request arrives and follows the same format and size limits as `image`: JPEG, PNG, WebP, or a PDF of 1 to 3 pages, up to `12 MB`.
        cuenta_beneficiaria:
          type: string
          pattern: ^(\d{10}|\d{16}|\d{18})$
          description: 'Cuenta receptora de la transferencia (útil cuando falta o está incompleta en la imagen). Requerida para celular DiMo; opcional si la imagen muestra la cuenta completa o sus últimos dígitos, que se completan con los beneficiarios guardados (estos no sustituyen a este campo). Acepta CLABE (18 dígitos), tarjeta (16 dígitos) o celular DiMo (10 dígitos). No puede enviarse junto con `cuentas_candidatas`: enviar las dos responde un estado HTTP `422` (con `cuenta_y_candidatas_excluyentes` en el cuerpo).'
          example: '012180004412345678'
          x-translations:
            en:
              description: '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 `cuentas_candidatas`: sending both responds with an HTTP status `422` (with `cuenta_y_candidatas_excluyentes` in the body).'
        cuentas_candidatas:
          type: array
          minItems: 2
          maxItems: 10
          uniqueItems: true
          items:
            type: string
            pattern: ^(\d{10}|\d{16}|\d{18})$
          description: |-
            Cuentas entre las que está la receptora de la transferencia, para cuando la imagen no la muestra y no se sabe cuál fue. Va en lugar de `cuenta_beneficiaria`: enviar las dos responde un estado HTTP `422` (con `cuenta_y_candidatas_excluyentes` en el cuerpo). Tiene prioridad sobre la cuenta que se lea en la imagen.

            Admite de 2 al máximo vigente de la plataforma, 3 por defecto y nunca más de 10; el `422` por exceso informa el máximo en `meta.max`. Cada cuenta es una CLABE (18 dígitos), una tarjeta (16) o un celular DiMo (10), con su dígito verificador válido y sin repetirse. Una lista que no cumple responde un `422` con `cuentas_candidatas_invalidas`, y señala en `field_errors` la posición que falló. Los dos errores ocurren antes de consumir cuota.

            Consume una sola unidad de cuota, lleve las cuentas que lleve, también con `?async=1`. La consulta recorre las candidatas en el orden enviado y adopta la primera que coincide con la transferencia. La ganadora se publica completa en `normalized_data.cuenta_beneficiaria`, y `candidate_match` indica su posición. Si ninguna coincide, la respuesta es un estado HTTP `422` con el motivo en `code` (`cuenta_unresolvable_after_probes` cuando no hay un diagnóstico más preciso).
          example:
            - '012180004412345678'
            - '002010077777777771'
          x-translations:
            en:
              description: |-
                Accounts among which the receiving one is, for when the image does not show it and it is not known which. It replaces `cuenta_beneficiaria`: sending both responds with an HTTP status `422` (with `cuenta_y_candidatas_excluyentes` in the body). It takes priority over the account read from the image.

                It accepts from 2 up to the platform's current maximum, 3 by default and never more than 10; the `422` for an excess reports the maximum in `meta.max`. Each account is a CLABE (18 digits), a card (16) or a DiMo phone (10), with a valid check digit and no repeats. A list that does not comply responds with a `422` with `cuentas_candidatas_invalidas`, and points out in `field_errors` the position that failed. Both errors happen before any quota is consumed.

                It consumes a single quota unit, however many accounts it carries, also with `?async=1`. The query goes through the candidates in the order sent and adopts the first one that matches the transfer. The winner is published in full in `normalized_data.cuenta_beneficiaria`, and `candidate_match` reports its position. If none matches, the response is an HTTP status `422` with the reason in `code` (`cuenta_unresolvable_after_probes` when there is no more precise diagnosis).
          x-invalid-examples:
            - - '012180004412345678'
            - - '012180004412345678'
              - '012180004412345678'
        retry_policy:
          allOf:
            - $ref: '#/components/schemas/RetryPolicy'
          description: Política de reintentos automáticos para esta validación. Si se omite, se aplica la política general del usuario, configurada en `PUT /v1/users/me/retry-policy`. **Los reintentos no consumen cuota de validaciones**.
          x-translations:
            en:
              description: |
                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.
        client_ref:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[^\x00-\x1f\x7f]+$
          description: Referencia propia que se devuelve tal cual en la validación, en los webhooks de validación y como filtro exacto de `GET /v1/validations`. Texto de 1 a 64 caracteres, sin saltos de línea ni emoji; se recorta antes de guardarse. Un valor que no cumple se rechaza con un estado HTTP `422` (con `invalid_client_ref` en el cuerpo). No debe contener datos personales.
          example: orden-4812
          x-translations:
            en:
              description: Reference of your own that is returned as sent in the validation, in the validation webhooks, and as an exact filter of `GET /v1/validations`. Text of 1 to 64 characters, without line breaks or emoji; it is trimmed before being stored. A value that does not comply is rejected with an HTTP status `422` (with `invalid_client_ref` in the body). It must not contain personal data.
          x-invalid-examples:
            - ''
            - |-
              orden
              4812
        retain_image:
          type: boolean
          default: true
          description: |-
            `true` cuando la plataforma conserva el archivo del comprobante después de validarlo, que es lo habitual. Con `false`, el archivo se borra en cuanto la validación llega a un estado terminal del que ya no se necesita; el veredicto y los datos extraídos se conservan.

            Con `false`, la validación publica `image_retained=false` y `GET /v1/validations/{id}/image` responde un estado HTTP `410` (con `image_not_retained` en el cuerpo). Vale igual con `?async=1`.

            El archivo existe mientras la validación se procesa. Si entró como un CEP en PDF y tiene reintentos automáticos activos, el borrado espera a que ese ciclo cierre, porque cada reintento vuelve a comparar el sello del PDF. Un valor que no es booleano responde un estado HTTP `422` (con `invalid_retain_image` en el cuerpo), antes de consumir cuota.
          example: false
          x-translations:
            en:
              description: |-
                `true` when the platform keeps the receipt file after validating it, which is the usual case. With `false`, the file is deleted as soon as the validation reaches a terminal status in which it is no longer needed; the verdict and the extracted data are kept.

                With `false`, the validation publishes `image_retained=false` and `GET /v1/validations/{id}/image` responds with an HTTP status `410` (with `image_not_retained` in the body). It applies the same with `?async=1`.

                The file exists while the validation is being processed. If it came in as a CEP in PDF and has automatic retries active, the deletion waits for that cycle to close, because each retry compares the PDF's seal again. A value that is not a boolean responds with an HTTP status `422` (with `invalid_retain_image` in the body), before any quota is consumed.
          x-invalid-examples:
            - quizás
            - 2
    RetryStateCompact:
      type: object
      description: |
        Estado resumido del ciclo de reintentos, con la configuración representada como `null`.
      x-translations:
        en:
          description: |
            Summary retry cycle state with configuration represented as `null`.
      properties:
        enabled:
          type: boolean
          description: '`true` cuando existe un ciclo de reintentos activo.'
          example: true
          x-translations:
            en:
              description: '`true` when an active retry cycle exists.'
        max_retries:
          type:
            - integer
            - 'null'
          description: Siempre `null` en esta forma.
          example: null
          x-translations:
            en:
              description: Always `null` in the summary shape.
        interval_seconds:
          type:
            - integer
            - 'null'
          description: Siempre `null` en esta forma.
          example: null
          x-translations:
            en:
              description: Always `null` in the summary shape.
        outcomes:
          type:
            - array
            - 'null'
          items:
            type: string
          description: Siempre `null` en esta forma.
          example: null
          x-translations:
            en:
              description: Always `null` in the summary shape.
        attempts_completed:
          type: integer
          minimum: 0
          description: Cantidad de reintentos completados hasta el momento.
          example: 2
          x-translations:
            en:
              description: Number of retries completed so far.
        next_attempt_at:
          oneOf:
            - $ref: '#/components/schemas/TimestampUTC'
            - type: 'null'
          description: Fecha del próximo reintento; `null` cuando no hay uno programado.
          x-translations:
            en:
              description: |
                Date of the next retry; `null` when none is scheduled.
        resolved_at:
          oneOf:
            - $ref: '#/components/schemas/TimestampUTC'
            - type: 'null'
          description: |
            Fecha en que un reintento obtuvo `valid`; `null` mientras no ocurra.
          x-translations:
            en:
              description: |
                Date when a retry produced `valid`; `null` until that occurs.
        exhausted_at:
          oneOf:
            - $ref: '#/components/schemas/TimestampUTC'
            - type: 'null'
          description: |
            Fecha en que se agotaron los intentos; `null` mientras no ocurra.
          x-translations:
            en:
              description: |
                Date when attempts were exhausted; `null` until that occurs.
        cancelled_at:
          oneOf:
            - $ref: '#/components/schemas/TimestampUTC'
            - type: 'null'
          description: |
            Fecha de cancelación del ciclo; `null` mientras no ocurra.
          x-translations:
            en:
              description: |
                Cycle cancellation date; `null` until that occurs.
        terminal_state:
          type:
            - string
            - 'null'
          enum:
            - pending
            - resolved
            - exhausted
            - cancelled
            - null
          description: 'Situación del ciclo: `pending`, `resolved`, `exhausted` o `cancelled`. Es `null` cuando la validación no tiene un ciclo de reintentos.'
          example: pending
          x-translations:
            en:
              description: |
                Cycle state: `pending`, `resolved`, `exhausted`, or `cancelled`. It is `null` when the validation has no retry cycle.
    PublicValidationListItem:
      type: object
      description: |
        Recurso JSON:API resumido de una validación en el historial de la cuenta.
      x-translations:
        en:
          description: |
            Summary JSON:API resource for a validation in the account history.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          enum:
            - validation
        id:
          type: string
          format: uuid
        attributes:
          type: object
          description: Datos mostrados en el historial de validaciones.
          x-translations:
            en:
              description: Data shown in the validation history.
          properties:
            bank_name:
              type: string
              description: Nombre del banco de la contraparte. Prioriza `receptor_name` o `emisor_name` guardado; si falta, intenta resolver el código de receptor o emisor. Si tampoco puede resolverlo, devuelve ese código o `Banco no identificado`.
              x-translations:
                en:
                  description: Counterparty bank name. It first uses the stored `receptor_name` or `emisor_name`; if absent, it tries to resolve the receiver or sender code. If resolution also fails, it returns that code or `Banco no identificado`.
            bank_code:
              type:
                - string
                - 'null'
              description: 'Clave Banxico canónica (3-5 dígitos) del banco de la contraparte, resuelta por la autoridad del dominio. `null` cuando el banco no pudo resolverse. Es aditiva respecto de `bank_name`: sirve para resolver recursos/avatares por clave, nunca por el nombre visible.'
              x-translations:
                en:
                  description: 'Canonical Banxico key (3-5 digits) of the counterparty bank, resolved by the domain authority. `null` when the bank could not be resolved. Additive to `bank_name`: it is used to resolve resources/avatars by key, never by the visible name.'
            beneficiary_label:
              type:
                - string
                - 'null'
              description: Etiqueta del beneficiario receptor registrado (si existe).
              x-translations:
                en:
                  description: Registered beneficiary label, when available.
            amount:
              type:
                - number
                - 'null'
              description: Importe de la transferencia (en pesos mexicanos — MXN).
              x-translations:
                en:
                  description: Transfer amount in Mexican pesos (MXN).
            tracking_key:
              type: string
              description: Identificador asignado por el banco emisor de la transferencia (clave de rastreo).
              x-translations:
                en:
                  description: Identifier assigned by the sending bank of the transfer (tracking key).
            referencia_numerica:
              type: string
              description: Serie numérica de hasta 7 dígitos que identifica la transferencia (no es único, puede repetirse).
              x-translations:
                en:
                  description: Numeric series of up to 7 digits that identifies the transfer. It is not unique and may repeat.
            beneficiary_account:
              type: string
              description: Número de cuenta (CLABE, tarjeta o celular/DiMo) receptor de la transferencia.
              x-translations:
                en:
                  description: CLABE, card, or mobile number that received the transfer.
            status:
              type: string
              enum:
                - queued
                - processing
                - valid
                - not_found
                - cep_unavailable
                - invalid
                - returned
                - failed
                - error
              description: 'Estado del ciclo de vida — `queued`: Pendiente de procesamiento; `processing`: En proceso; `valid`: El CEP fue encontrado y cuadra; `not_found`: Banxico no encuentra la operación; `cep_unavailable`: Banxico reconoce la transacción pero el CEP no está disponible; `invalid`: Banxico no devolvió un veredicto reconocible; `returned`: la operación se liquidó y después se devolvió; `failed`: La validación se detuvo por un problema en los datos enviados; `error`: La validación se detuvo por un fallo del servicio.'
              x-translations:
                en:
                  description: 'Lifecycle state — `queued`: Awaiting processing; `processing`: In progress; `valid`: The CEP was found and matches; `not_found`: Banxico cannot find the operation; `cep_unavailable`: Banxico recognizes the transaction but the CEP is not available; `invalid`: Banxico did not return a recognizable verdict; `returned`: the transfer was settled and later returned; `failed`: The validation stopped because of a problem in the data sent; `error`: The validation stopped because of a service failure.'
            banxico_status:
              type: string
              enum:
                - pending
                - valid
                - not_found
                - cep_unavailable
                - invalid
                - returned
                - error
              description: Veredicto reportado por Banxico.
              x-translations:
                en:
                  description: Verdict reported by Banxico.
            validation_type:
              type: string
              enum:
                - direct
                - ocr
              description: 'Modalidad de validación — `direct`: Captura manual de los campos; `ocr`: Lectura de la imagen del comprobante.'
              x-translations:
                en:
                  description: 'Validation mode — `direct`: Manual entry of the fields; `ocr`: Reading of the receipt image.'
            is_playground:
              type: boolean
              description: '`true` cuando la validación pertenece al banco de pruebas (Playground).'
              x-translations:
                en:
                  description: '`true` when the validation belongs to the playground.'
            created_at:
              $ref: '#/components/schemas/TimestampUTC'
            deleted_at:
              oneOf:
                - $ref: '#/components/schemas/TimestampUTC'
                - type: 'null'
              description: Fecha de retiro del historial, en ISO 8601 UTC.
              x-translations:
                en:
                  description: History withdrawal date in ISO 8601 UTC.
            purged_at:
              allOf:
                - $ref: '#/components/schemas/TimestampUTC'
              description: Fecha y hora, en ISO 8601 UTC, en que la validación se purgó. Solo aparece en una validación purgada, que queda como una lápida con su veredicto, sus fechas y su monto.
              x-translations:
                en:
                  description: Date and time, in ISO 8601 UTC, when the validation was purged. It only appears on a purged validation, which is left as a tombstone with its verdict, its dates, and its amount.
            retry_state:
              $ref: '#/components/schemas/RetryStateCompact'
            client_ref:
              type: string
              minLength: 1
              maxLength: 64
              description: Referencia propia enviada al validar (`client_ref`), devuelta tal cual. Solo aparece cuando la petición la incluyó.
              example: orden-4812
              x-translations:
                en:
                  description: Reference of your own sent when validating (`client_ref`), returned as sent. It only appears when the request included it.
    ValidationStats:
      type: object
      description: |
        Recurso JSON:API con contadores por `banxico_status`, estado del ciclo de vida y modalidad de validación.
      x-translations:
        en:
          description: |
            JSON:API resource with validation counters by `banxico_status`, lifecycle state, and validation mode.
      properties:
        type:
          type: string
          enum:
            - validation_stats
          description: Tipo de recurso JSON:API. Siempre `validation_stats`.
          example: validation_stats
          x-translations:
            en:
              description: JSON:API resource type. Always `validation_stats`.
        attributes:
          type: object
          description: |
            Contadores agregados bajo los filtros de la petición.
          x-translations:
            en:
              description: |
                Aggregated counters under the request filters.
          properties:
            total:
              type: integer
              minimum: 0
              description: Cantidad de validaciones que cumplen los filtros, sin distinguir veredicto.
              example: 47
              x-translations:
                en:
                  description: |
                    Validations matching the filters, regardless of verdict.
            valid:
              type: integer
              minimum: 0
              description: Cantidad de validaciones confirmadas por Banxico (CEP disponible).
              example: 35
              x-translations:
                en:
                  description: Number of validations confirmed by Banxico (CEP available).
            not_found:
              type: integer
              minimum: 0
              description: Cantidad de validaciones sin CEP y sin coincidencia en Banxico.
              example: 5
              x-translations:
                en:
                  description: Number of transfers with no match in Banxico.
            cep_unavailable:
              type: integer
              minimum: 0
              description: Cantidad de validaciones cuyo CEP no estaba disponible aunque Banxico reconoció la transacción.
              example: 2
              x-translations:
                en:
                  description: Number of validations whose CEP was not available even though Banxico recognized the transaction.
            returned:
              type: integer
              minimum: 0
              description: Cantidad de validaciones cuya operación se liquidó y después se devolvió.
              example: 0
              x-translations:
                en:
                  description: Number of validations whose transfer was settled and later returned.
            error:
              type: integer
              minimum: 0
              description: Cantidad de validaciones sin veredicto concluyente por un error.
              example: 1
              x-translations:
                en:
                  description: |
                    Validations without a conclusive verdict because of an error.
            pending:
              type: integer
              minimum: 0
              description: Cantidad de validaciones cuyo procesamiento está en curso.
              example: 1
              x-translations:
                en:
                  description: Number of validations whose processing is under way.
            deleted:
              type: integer
              minimum: 0
              description: Cantidad de validaciones retiradas que cumplen los demás filtros.
              example: 2
              x-translations:
                en:
                  description: |
                    Withdrawn validations matching the remaining filters.
            other:
              type: integer
              minimum: 0
              description: |
                Diferencia entre `total` y los seis contadores de veredicto.
              example: 1
              x-translations:
                en:
                  description: |
                    Difference between `total` and the six verdict counters.
            by_type:
              type: object
              description: |
                Desglose por tipo de validación. `direct + ocr` siempre suma `total`.
              x-translations:
                en:
                  description: |
                    Breakdown by validation type. `direct + ocr` always sums to `total`.
              properties:
                direct:
                  type: integer
                  minimum: 0
                  description: Cantidad de validaciones realizadas mediante captura manual.
                  example: 30
                  x-translations:
                    en:
                      description: Manual validations using CLABE, card, or phone fields.
                ocr:
                  type: integer
                  minimum: 0
                  description: Cantidad de validaciones realizadas mediante imagen (OCR).
                  example: 17
                  x-translations:
                    en:
                      description: OCR validations (from a receipt image).
              required:
                - direct
                - ocr
            by_status:
              type: object
              description: Desglose por estado del ciclo de vida. Las propiedades siempre están presentes y suman `total`.
              x-translations:
                en:
                  description: |
                    Breakdown by lifecycle state. All nine properties are always present and add up to `total`.
              properties:
                queued:
                  type: integer
                  minimum: 0
                  description: Cantidad de validaciones aceptadas que esperan procesamiento.
                  example: 3
                  x-translations:
                    en:
                      description: Accepted validations awaiting processing.
                processing:
                  type: integer
                  minimum: 0
                  description: Cantidad de validaciones cuyo procesamiento está en curso.
                  example: 1
                  x-translations:
                    en:
                      description: Validations currently being processed.
                valid:
                  type: integer
                  minimum: 0
                  description: Cantidad de validaciones confirmadas por Banxico (CEP disponible).
                  example: 35
                  x-translations:
                    en:
                      description: Validations with a transfer confirmed in the CEP.
                not_found:
                  type: integer
                  minimum: 0
                  description: Cantidad de validaciones sin CEP y sin coincidencia en Banxico.
                  example: 5
                  x-translations:
                    en:
                      description: Validations with no matching transfer in the CEP.
                cep_unavailable:
                  type: integer
                  minimum: 0
                  description: Cantidad de validaciones cuyo CEP no estaba disponible aunque Banxico reconoció la transacción.
                  example: 2
                  x-translations:
                    en:
                      description: Number of validations whose CEP was not available even though Banxico recognized the transaction.
                invalid:
                  type: integer
                  minimum: 0
                  description: Cantidad de validaciones rechazadas por datos inválidos.
                  example: 1
                  x-translations:
                    en:
                      description: Validations rejected because of invalid data.
                returned:
                  type: integer
                  minimum: 0
                  description: Cantidad de validaciones cuya operación se liquidó y después se devolvió.
                  example: 0
                  x-translations:
                    en:
                      description: Validations whose transfer was settled and later returned.
                failed:
                  type: integer
                  minimum: 0
                  description: Cantidad de validaciones con un fallo terminal de procesamiento.
                  example: 0
                  x-translations:
                    en:
                      description: Validations with a terminal processing failure.
                error:
                  type: integer
                  minimum: 0
                  description: Cantidad de validaciones cuyo procesamiento terminó con un error.
                  example: 1
                  x-translations:
                    en:
                      description: Validations whose processing ended with an error.
              required:
                - queued
                - processing
                - valid
                - not_found
                - cep_unavailable
                - invalid
                - returned
                - failed
                - error
    RetryAttempt:
      type: object
      description: |
        Intento de reintento automático de una validación.
      x-translations:
        en:
          description: |
            Automatic retry attempt for a validation.
      properties:
        attempt_number:
          type: integer
          minimum: 1
          description: Posición del intento en el ciclo, comenzando en `1`.
          example: 1
          x-translations:
            en:
              description: Attempt position in the cycle, starting at `1`.
        dispatched_at:
          $ref: '#/components/schemas/TimestampUTC'
        started_at:
          $ref: '#/components/schemas/TimestampUTC'
        finished_at:
          $ref: '#/components/schemas/TimestampUTC'
        prev_banxico_status:
          type: string
          enum:
            - valid
            - not_found
            - cep_unavailable
            - invalid
            - error
          description: |
            Veredicto de Banxico anterior al intento.
          example: not_found
          x-translations:
            en:
              description: |
                Banxico verdict before the attempt.
        new_banxico_status:
          type:
            - string
            - 'null'
          enum:
            - valid
            - not_found
            - cep_unavailable
            - invalid
            - error
            - null
          description: Veredicto de Banxico obtenido en el intento; `null` cuando no hubo respuesta.
          example: valid
          x-translations:
            en:
              description: Banxico verdict obtained by the attempt; `null` when there was no response.
        prev_status:
          type: string
          enum:
            - queued
            - processing
            - valid
            - not_found
            - cep_unavailable
            - invalid
            - failed
            - error
          description: Estado de la validación antes del intento.
          example: not_found
          x-translations:
            en:
              description: Validation state before the attempt.
        new_status:
          type:
            - string
            - 'null'
          enum:
            - queued
            - processing
            - valid
            - not_found
            - cep_unavailable
            - invalid
            - failed
            - error
            - null
          description: Estado de la validación después del intento; `null` cuando no hubo transición.
          example: valid
          x-translations:
            en:
              description: Validation state after the attempt; `null` when there was no transition.
        processing_time_ms:
          type:
            - integer
            - 'null'
          minimum: 0
          description: Tiempo de procesamiento del intento (en milisegundos). `null` cuando el intento no completó.
          example: 1250
          x-translations:
            en:
              description: Processing time for the attempt in milliseconds. `null` when the attempt did not complete.
        error_code:
          oneOf:
            - $ref: '#/components/schemas/ValidationErrorCode'
            - type: 'null'
          description: Código del error producido por el intento; `null` cuando no hubo error.
          example: network
          x-translations:
            en:
              description: Error code produced by the attempt; `null` when there was no error.
        proxy_pool_member:
          type:
            - string
            - 'null'
          description: Identificador de la ruta de salida por la que se consultó a Banxico.
          example: proxy-03
          x-translations:
            en:
              description: |
                Identifier of the outbound route used to query Banxico.
    UpdateValidationRetryPolicyRequest:
      type: object
      description: Cuerpo de `PUT /v1/validations/{id}/retry-policy`.
      x-translations:
        en:
          description: Body of `PUT /v1/validations/{id}/retry-policy`.
      required:
        - retry_policy
      properties:
        retry_policy:
          description: Política que define cuándo y cuántas veces se reprocesa la validación tras un resultado elegible (`not_found`, `cep_unavailable` o `error`).
          x-translations:
            en:
              description: Policy that defines when and how many times the validation is reprocessed after an eligible result (`not_found`, `cep_unavailable` or `error`).
          $ref: '#/components/schemas/RetryPolicy'
    UpdateValidationRetryPolicyAttributes:
      type: object
      description: Estado completo del ciclo después de actualizar su política de reintentos.
      x-translations:
        en:
          description: Full retry cycle state after updating its retry policy.
      properties:
        retry_state:
          $ref: '#/components/schemas/RetryStateFull'
    CancelValidationRetriesAttributes:
      type: object
      description: Estado completo del ciclo después de cancelar los reintentos pendientes de una validación.
      x-translations:
        en:
          description: Full retry cycle state after canceling a validation's pending retries.
      properties:
        retry_state:
          $ref: '#/components/schemas/RetryStateFull'
    ValidationRecheckMeta:
      type: object
      description: Resultado de la revisión que acompaña, dentro de `meta`, la respuesta de `POST /v1/validations/{id}/recheck`.
      x-translations:
        en:
          description: Result of the follow-up check that comes with the response of `POST /v1/validations/{id}/recheck`, inside `meta`.
      required:
        - checked_at
        - changed
        - previous_status
      properties:
        checked_at:
          type:
            - string
            - 'null'
          format: date-time
          description: Fecha y hora, en ISO 8601 UTC, en que se consultó el estado del pago a Banxico. `null` cuando no se consultó nada porque la validación ya estaba en `returned`.
          example: '2026-10-01T18:42:07Z'
          x-translations:
            en:
              description: Date and time, in ISO 8601 UTC, when the payment status was queried from Banxico. `null` when nothing was queried because the validation was already `returned`.
        changed:
          type: boolean
          description: '`true` cuando esta consulta pasó la validación de `valid` a `returned`. En ese caso también se emitió el webhook `validation.returned`.'
          example: false
          x-translations:
            en:
              description: '`true` when this query moved the validation from `valid` to `returned`. In that case the `validation.returned` webhook was also emitted.'
        previous_status:
          type: string
          enum:
            - valid
            - returned
          description: 'Veredicto de Banxico antes de esta consulta — `valid`: La validación seguía confirmada; `returned`: La validación ya estaba devuelta y no se consultó nada.'
          example: valid
          x-translations:
            en:
              description: 'Banxico verdict before this query — `valid`: The validation was still confirmed; `returned`: The validation was already returned and nothing was queried.'
    ValidationPurgePrepareResponse:
      type: object
      description: 'Respuesta de `POST /v1/validations/{id}/purge/prepare`: lo que borraría el borrado definitivo, lo que conservaría y el token que lo confirma. Todavía no ha cambiado nada.'
      x-translations:
        en:
          description: 'Response of `POST /v1/validations/{id}/purge/prepare`: what the permanent deletion would delete, what it would keep, and the token that confirms it. Nothing has changed yet.'
      required:
        - data
      properties:
        data:
          description: Recurso JSON:API de la preparación. Su `id` es el de la validación.
          x-translations:
            en:
              description: JSON:API resource of the preparation. Its `id` is the validation's.
          type: object
          allOf:
            - $ref: '#/components/schemas/JsonApiResourceBase'
            - type: object
              required:
                - type
                - id
                - attributes
              properties:
                type:
                  type: string
                  enum:
                    - validation_purge
                  description: Tipo del recurso, fijo para esta operación. Forma parte de su identidad en la envoltura JSON:API. Siempre `validation_purge`.
                  example: validation_purge
                  x-translations:
                    en:
                      description: Resource type, fixed for this operation. Part of its identity in the JSON:API envelope. Always `validation_purge`.
                id:
                  type: string
                  format: uuid
                  description: Identificador de la validación que se borraría.
                  example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
                  x-translations:
                    en:
                      description: Identifier of the validation that would be deleted.
                attributes:
                  type: object
                  description: Resumen del borrado y token de confirmación.
                  x-translations:
                    en:
                      description: Summary of the deletion and the confirmation token.
                  required:
                    - confirmation_token
                    - expires_in
                    - irreversible
                    - will_delete
                    - will_keep
                    - cancels_pending_retries
                    - refunds_quota
                    - same_image_validation_ids
                  properties:
                    confirmation_token:
                      type: string
                      description: Token de un solo uso, atado a la cuenta y a esta validación, que `POST /v1/validations/{id}/purge/execute` exige para borrar. Caduca en `expires_in` segundos.
                      example: eyJhZG1pbl9pZCI6Ii4uLiJ9.q1w2e3r4t5y6u7i8o9p0
                      x-translations:
                        en:
                          description: Single-use token, bound to the account and to this validation, that `POST /v1/validations/{id}/purge/execute` requires in order to delete. It expires in `expires_in` seconds.
                    expires_in:
                      type: integer
                      description: Segundos de vigencia del token.
                      example: 120
                      x-translations:
                        en:
                          description: Seconds the token stays valid.
                    irreversible:
                      type: boolean
                      description: '`true` siempre: el borrado no se puede deshacer. Un respaldo de infraestructura no lo revierte para el cliente.'
                      example: true
                      x-translations:
                        en:
                          description: '`true` in every response: the deletion cannot be undone. An infrastructure backup does not reverse it for the client.'
                    will_delete:
                      type: object
                      description: Lo que borraría el borrado definitivo.
                      x-translations:
                        en:
                          description: What the permanent deletion would delete.
                      required:
                        - image
                        - cep
                        - stored_data
                        - retry_attempts
                        - probe_attempts
                        - webhook_deliveries
                        - notifications
                        - idempotency_responses
                        - audit_traces
                      properties:
                        image:
                          type: boolean
                          description: '`true` cuando hay un archivo del comprobante (imagen o PDF) que se borraría.'
                          example: true
                          x-translations:
                            en:
                              description: '`true` when there is a receipt file (image or PDF) that would be deleted.'
                        cep:
                          type: boolean
                          description: '`true` cuando hay un CEP (el XML o el PDF) que se borraría.'
                          example: true
                          x-translations:
                            en:
                              description: '`true` when there is a CEP (the XML or the PDF) that would be deleted.'
                        stored_data:
                          type: array
                          items:
                            type: string
                            enum:
                              - request_data
                              - ocr_result
                              - normalized_data
                              - normalization_warnings
                              - banxico_result
                          description: Campos de la validación con contenido que se vaciarían. De `normalized_data` se conserva únicamente el monto.
                          example:
                            - request_data
                            - ocr_result
                            - normalized_data
                            - banxico_result
                          x-translations:
                            en:
                              description: Validation fields with content that would be emptied. Of `normalized_data`, only the amount is kept.
                        retry_attempts:
                          type: integer
                          description: Cantidad de intentos de reintento que se borrarían.
                          example: 2
                          x-translations:
                            en:
                              description: Number of retry attempts that would be deleted.
                        probe_attempts:
                          type: integer
                          description: Cantidad de sondeos de cuenta candidata que se borrarían.
                          example: 0
                          x-translations:
                            en:
                              description: Number of candidate-account probes that would be deleted.
                        webhook_deliveries:
                          type: integer
                          description: Cantidad de entregas de webhook de esta validación a las que se les quitaría el cuerpo enviado y la respuesta del receptor. El registro de cada entrega se conserva.
                          example: 1
                          x-translations:
                            en:
                              description: Number of webhook deliveries of this validation whose sent body and receiver response would be removed. The record of each delivery is kept.
                        notifications:
                          type: integer
                          description: Cantidad de entregas de notificación de esta validación cuyo contenido se vaciaría. Las notificaciones de la bandeja de la cuenta se borrarían.
                          example: 1
                          x-translations:
                            en:
                              description: Number of notification deliveries of this validation whose content would be emptied. The notifications in the account inbox would be deleted.
                        idempotency_responses:
                          type: integer
                          description: Cantidad de respuestas guardadas de `Idempotency-Key` que se borrarían.
                          example: 1
                          x-translations:
                            en:
                              description: Number of stored `Idempotency-Key` responses that would be deleted.
                        audit_traces:
                          type: boolean
                          description: '`true` siempre: los cuerpos de las filas de auditoría que nombran la validación se quitarían, y el contexto libre de sus eventos de seguridad se reduciría a ids y códigos.'
                          example: true
                          x-translations:
                            en:
                              description: '`true` in every response: the bodies of the audit rows that name the validation would be removed, and the free-form context of its security events would be reduced to ids and codes.'
                    will_keep:
                      type: array
                      items:
                        type: string
                      description: 'Campos que conserva la lápida en que queda la validación: bastan para que la cuota, las estadísticas, finanzas y el historial sigan cuadrando.'
                      example:
                        - id
                        - validation_type
                        - status
                        - banxico_status
                        - amount
                        - created_at
                        - purged_at
                      x-translations:
                        en:
                          description: 'Fields the tombstone the validation is left as keeps: enough for quota, statistics, finance, and history to keep adding up.'
                    cancels_pending_retries:
                      type: boolean
                      description: '`true` cuando la validación tiene un ciclo de reintentos abierto, que el borrado cancela.'
                      example: false
                      x-translations:
                        en:
                          description: '`true` when the validation has an open retry cycle, which the deletion cancels.'
                    refunds_quota:
                      type: boolean
                      description: '`false` siempre: el borrado no devuelve cuota ni cambia lo cobrado.'
                      example: false
                      x-translations:
                        en:
                          description: '`false` in every response: the deletion neither refunds quota nor changes what was charged.'
                    same_image_validation_ids:
                      type: array
                      items:
                        type: string
                        format: uuid
                      description: 'Identificadores de otras validaciones de la misma cuenta con el mismo comprobante (hasta 20). No se tocan: cada una guarda su propia copia de lo extraído y se borra por separado.'
                      example: []
                      x-translations:
                        en:
                          description: 'Identifiers of other validations of the same account with the same receipt (up to 20). They are not touched: each keeps its own copy of what was extracted and is deleted separately.'
    ValidationPurgeExecuteRequest:
      type: object
      description: 'Cuerpo de `POST /v1/validations/{id}/purge/execute`: el token del paso de preparación.'
      x-translations:
        en:
          description: 'Body of `POST /v1/validations/{id}/purge/execute`: the token from the preparation step.'
      required:
        - confirmation_token
      properties:
        confirmation_token:
          type: string
          description: Token que devolvió `POST /v1/validations/{id}/purge/prepare` para esta misma validación y esta misma cuenta. Es de un solo uso y caduca en el plazo que indicó `expires_in`.
          example: eyJhZG1pbl9pZCI6Ii4uLiJ9.q1w2e3r4t5y6u7i8o9p0
          x-translations:
            en:
              description: Token returned by `POST /v1/validations/{id}/purge/prepare` for this same validation and this same account. It is single-use and expires within the period `expires_in` indicated.
    ValidationPurgeExecuteResponse:
      type: object
      description: 'Respuesta de `POST /v1/validations/{id}/purge/execute`: la validación quedó purgada y esto es lo que se borró.'
      x-translations:
        en:
          description: 'Response of `POST /v1/validations/{id}/purge/execute`: the validation is now purged and this is what was deleted.'
      required:
        - data
      properties:
        data:
          description: Recurso JSON:API del borrado. Su `id` es el de la validación.
          x-translations:
            en:
              description: JSON:API resource of the deletion. Its `id` is the validation's.
          type: object
          allOf:
            - $ref: '#/components/schemas/JsonApiResourceBase'
            - type: object
              required:
                - type
                - id
                - attributes
              properties:
                type:
                  type: string
                  enum:
                    - validation_purge
                  description: Tipo del recurso, fijo para esta operación. Forma parte de su identidad en la envoltura JSON:API. Siempre `validation_purge`.
                  example: validation_purge
                  x-translations:
                    en:
                      description: Resource type, fixed for this operation. Part of its identity in the JSON:API envelope. Always `validation_purge`.
                id:
                  type: string
                  format: uuid
                  description: Identificador de la validación purgada.
                  example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
                  x-translations:
                    en:
                      description: Identifier of the purged validation.
                attributes:
                  type: object
                  description: Resultado del borrado.
                  x-translations:
                    en:
                      description: Result of the deletion.
                  required:
                    - purged_at
                    - file_removal
                    - deleted
                  properties:
                    purged_at:
                      type: string
                      format: date-time
                      description: Fecha y hora, en ISO 8601 UTC, en que la validación quedó purgada.
                      example: '2026-10-02T09:30:00Z'
                      x-translations:
                        en:
                          description: Date and time, in ISO 8601 UTC, when the validation was purged.
                    file_removal:
                      type: string
                      enum:
                        - complete
                        - pending
                      description: 'Estado del borrado de los archivos — `complete`: Los archivos ya no existen; `pending`: Algún archivo no se pudo borrar en este momento y el barrido diario lo termina. El contenido de la base de datos ya está borrado en los dos casos y el archivo no se sirve.'
                      example: complete
                      x-translations:
                        en:
                          description: 'State of the file deletion — `complete`: The files no longer exist; `pending`: A file could not be deleted at this moment and the daily sweep finishes it. The database content is already deleted in both cases and the file is not served.'
                    deleted:
                      type: object
                      description: Lo que se borró. Los contadores cuentan filas tocadas; los booleanos dicen si había algo que borrar.
                      x-translations:
                        en:
                          description: What was deleted. The counters count rows touched; the booleans say whether there was something to delete.
                      properties:
                        image:
                          type: boolean
                          description: '`true` cuando había un archivo del comprobante.'
                          x-translations:
                            en:
                              description: '`true` when there was a receipt file.'
                        cep_pdf:
                          type: boolean
                          description: '`true` cuando había un PDF del CEP.'
                          x-translations:
                            en:
                              description: '`true` when there was a CEP PDF.'
                        retry_cycle_cancelled:
                          type: boolean
                          description: '`true` cuando el borrado cerró un ciclo de reintentos que seguía abierto.'
                          x-translations:
                            en:
                              description: '`true` when the deletion closed a retry cycle that was still open.'
                        retry_attempts:
                          type: integer
                          description: Intentos de reintento borrados.
                          x-translations:
                            en:
                              description: Retry attempts deleted.
                        probe_attempts:
                          type: integer
                          description: Sondeos de cuenta candidata borrados.
                          x-translations:
                            en:
                              description: Candidate-account probes deleted.
                        webhook_deliveries:
                          type: integer
                          description: Entregas de webhook a las que se les quitó el cuerpo enviado y la respuesta del receptor.
                          x-translations:
                            en:
                              description: Webhook deliveries whose sent body and receiver response were removed.
                        notification_deliveries:
                          type: integer
                          description: Entregas de notificación cuyo contenido se vació.
                          x-translations:
                            en:
                              description: Notification deliveries whose content was emptied.
                        notifications:
                          type: integer
                          description: Notificaciones de la bandeja que se borraron.
                          x-translations:
                            en:
                              description: Inbox notifications that were deleted.
                        idempotency_responses:
                          type: integer
                          description: Respuestas guardadas de `Idempotency-Key` que se borraron.
                          x-translations:
                            en:
                              description: Stored `Idempotency-Key` responses that were deleted.
                        import_rows:
                          type: integer
                          description: Filas de importación masiva cuyo contenido se vació.
                          x-translations:
                            en:
                              description: Bulk-import rows whose content was emptied.
                        audit_log_rows:
                          type: integer
                          description: Filas de auditoría a las que se les quitaron los cuerpos. Falta cuando ese barrido no se pudo completar.
                          x-translations:
                            en:
                              description: Audit rows whose bodies were removed. Missing when that sweep could not complete.
                        security_events_rows:
                          type: integer
                          description: Eventos de seguridad cuyo contexto se redactó. Falta cuando ese barrido no se pudo completar.
                          x-translations:
                            en:
                              description: Security events whose context was redacted. Missing when that sweep could not complete.
    BankResource:
      type: object
      description: Banco participante del catálogo SPEI, en formato JSON:API. `id` y `attributes.code` son siempre iguales (`id` se conserva como identificador estable del recurso aunque `code` lo duplique).
      x-translations:
        en:
          description: SPEI participant bank in JSON:API shape. `id` and `attributes.code` are always equal (`id` is kept as the stable resource identifier even though `code` duplicates it).
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              enum:
                - bank
              description: Tipo del recurso API (`bank` por defecto).
              example: bank
              x-translations:
                en:
                  description: API resource type (`bank` by default).
            id:
              type: string
              pattern: ^\d{5}$
              minLength: 5
              maxLength: 5
              description: Código bancario de la institución (5 dígitos).
              example: '40012'
              x-translations:
                en:
                  description: 5-digit code of the banking institution.
            attributes:
              type: object
              description: Datos de la institución bancaria.
              x-translations:
                en:
                  description: Attributes of the banking institution.
              required:
                - code
                - name
              properties:
                code:
                  type: string
                  pattern: ^\d{5}$
                  minLength: 5
                  maxLength: 5
                  description: Código bancario de la institución (5 dígitos). Mismo valor que `id`.
                  example: '40012'
                  x-translations:
                    en:
                      description: Code of the banking institution (5 digits). Same value as `id`.
                name:
                  type: string
                  description: Nombre oficial de la institución bancaria.
                  example: BBVA MEXICO
                  x-translations:
                    en:
                      description: Official name of the banking institution.
                aliases:
                  type: array
                  description: Nombres alternativos (marca/abreviaturas) de la institución bancaria.
                  items:
                    type: string
                  example:
                    - bancomer
                    - bbva bancomer
                  x-translations:
                    en:
                      description: Alternative names (brand/abbreviations) of the banking institution.
    ListBanksResponse:
      type: object
      description: |
        Respuesta de `GET /v1/public/banks`. Catálogo completo (~95 instituciones) de participantes SPEI activos. Cacheable con ETag — el snapshot cambia semanalmente cuando el sincronizador de Banxico refresca el catálogo.
      x-translations:
        en:
          description: |
            Response from `GET /v1/public/banks`. Full SPEI participant catalog (~95 active institutions). ETag-cacheable — the snapshot updates weekly when the Banxico sync refreshes the catalog.
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la respuesta del catálogo de bancos SPEI.
          x-translations:
            en:
              description: Schema body specific to the SPEI bank catalog response.
          required:
            - data
          properties:
            data:
              type: array
              description: Lista de bancos participantes SPEI.
              x-translations:
                en:
                  description: List of SPEI participant banks.
              items:
                $ref: '#/components/schemas/BankResource'
    BinResource:
      type: object
      description: 'Resultado de resolución de un BIN contra el catálogo local de BIN, en formato JSON:API. `id` y `attributes.bin` son iguales: el BIN de 6-8 dígitos que efectivamente coincidió (nunca el número completo que se envió).'
      x-translations:
        en:
          description: 'A BIN resolution result from the local BIN catalog, in JSON:API shape. `id` and `attributes.bin` are equal: the 6-8 digit BIN that actually matched (never the full number the caller sent).'
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              enum:
                - bin_lookup
              description: Tipo del recurso API (`bin_lookup` por defecto).
              example: bin_lookup
              x-translations:
                en:
                  description: API resource type (`bin_lookup` by default).
            id:
              type: string
              pattern: ^\d{6,8}$
              minLength: 6
              maxLength: 8
              description: BIN (6-8 dígitos) que coincidió en la búsqueda.
              example: '45320151'
              x-translations:
                en:
                  description: The 6-8 digit BIN that matched in the lookup.
            attributes:
              type: object
              description: Datos del BIN y del banco asociado.
              x-translations:
                en:
                  description: Attributes of the BIN and the associated bank.
              required:
                - bin
                - bank_name
                - banxico_code
                - country_iso
              properties:
                bin:
                  type: string
                  pattern: ^\d{6,8}$
                  description: BIN (6-8 dígitos) que coincidió en la búsqueda (mismo valor que `id`).
                  example: '45320151'
                  x-translations:
                    en:
                      description: The 6-8 digit BIN that matched in the lookup (same value as `id`).
                bank_name:
                  type: string
                  description: Nombre oficial del banco, resuelto a partir de su `bank_code`.
                  example: BBVA MEXICO
                  x-translations:
                    en:
                      description: Official name of the bank, resolved from its `bank_code`.
                banxico_code:
                  type:
                    - string
                    - 'null'
                  pattern: ^\d{5}$
                  description: 'Código de la institución bancaria ante el sistema SPEI (5 dígitos), o `null` cuando el banco no es participante SPEI (ej: Amex).'
                  example: '40012'
                  x-translations:
                    en:
                      description: 5-digit code of the banking institution in the SPEI system, or `null` when the bank is not a SPEI participant (e.g. Amex).
                card_brand:
                  type:
                    - string
                    - 'null'
                  description: Red de la tarjeta (VISA, MASTERCARD, …), o `null` cuando se desconoce.
                  example: VISA
                  x-translations:
                    en:
                      description: Card network (VISA, MASTERCARD, …), or `null` when unknown.
                card_type:
                  type:
                    - string
                    - 'null'
                  description: Tipo de tarjeta (CREDIT, DEBIT, PREPAID, …), o `null` cuando se desconoce.
                  example: CREDIT
                  x-translations:
                    en:
                      description: Card type (CREDIT, DEBIT, PREPAID, …), or `null` when unknown.
                card_level:
                  type:
                    - string
                    - 'null'
                  description: Nivel de la tarjeta (GOLD, PLATINUM, …), o `null` cuando se desconoce.
                  example: GOLD
                  x-translations:
                    en:
                      description: Card level (GOLD, PLATINUM, …), or `null` when unknown.
                country_iso:
                  type: string
                  description: Código del país emisor (en ISO-3166 alpha-2).
                  example: MX
                  x-translations:
                    en:
                      description: Issuing country code (in ISO-3166 alpha-2).
    BinLookupResponse:
      type: object
      description: Respuesta de `GET /v1/public/bin-lookup/{bin}`. Un único recurso con el banco emisor SPEI resuelto contra el catálogo local de BIN (lectura pura, sin consulta externa). Un BIN desconocido devuelve `404`, no este cuerpo.
      x-translations:
        en:
          description: Response from `GET /v1/public/bin-lookup/{bin}`. A single resource with the issuing SPEI bank resolved against the local BIN catalog (pure read, no external lookup). An unknown BIN returns `404`, not this body.
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la respuesta de resolución de BIN.
          x-translations:
            en:
              description: Schema body specific to the BIN resolution response.
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/BinResource'
    SpeiCalendarResource:
      type: object
      description: 'Calendario de días inhábiles SPEI de un año, en formato JSON:API. `id` y `attributes.year` son siempre iguales: el año del calendario.'
      x-translations:
        en:
          description: 'SPEI non-business-day calendar of one year, in JSON:API shape. `id` and `attributes.year` are always equal: the year of the calendar.'
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              enum:
                - spei_calendar
              description: Tipo de recurso JSON:API. Siempre `spei_calendar`.
              example: spei_calendar
              x-translations:
                en:
                  description: JSON:API resource type. Always `spei_calendar`.
            id:
              type: string
              pattern: ^\d{4}$
              minLength: 4
              maxLength: 4
              description: Año del calendario (cuatro dígitos).
              example: '2026'
              x-translations:
                en:
                  description: Calendar year (four digits).
            attributes:
              type: object
              description: Datos del calendario del año.
              x-translations:
                en:
                  description: Data of the year's calendar.
              required:
                - year
                - non_business_days
                - source
              properties:
                year:
                  type: integer
                  minimum: 1000
                  maximum: 9999
                  description: Año del calendario (cuatro dígitos). Mismo valor que `id`.
                  example: 2026
                  x-translations:
                    en:
                      description: Calendar year (four digits). Same value as `id`.
                non_business_days:
                  type: array
                  items:
                    type: string
                    format: date
                    pattern: ^\d{4}-\d{2}-\d{2}$
                  description: Días inhábiles que caen de lunes a viernes (`YYYY-MM-DD`), de menor a mayor. Los sábados y los domingos también son inhábiles y no se repiten aquí.
                  example:
                    - '2026-01-01'
                    - '2026-02-02'
                    - '2026-03-16'
                    - '2026-04-02'
                    - '2026-04-03'
                    - '2026-05-01'
                    - '2026-09-16'
                    - '2026-11-02'
                    - '2026-11-16'
                    - '2026-12-25'
                  x-translations:
                    en:
                      description: Non-business days that fall from Monday to Friday (`YYYY-MM-DD`), from lowest to highest. Saturdays and Sundays are also non-business days and are not repeated here.
                source:
                  type: object
                  description: Disposición oficial de la que sale el calendario.
                  x-translations:
                    en:
                      description: Official provision the calendar comes from.
                  required:
                    - issuer
                    - published_in
                    - published_on
                    - title
                  properties:
                    issuer:
                      type: string
                      description: Siglas de la autoridad que emite la disposición, por ejemplo `CNBV`.
                      example: CNBV
                      x-translations:
                        en:
                          description: Acronym of the authority that issues the provision, for example `CNBV`.
                    published_in:
                      type: string
                      description: Siglas del medio oficial en el que se publicó la disposición, por ejemplo `DOF`.
                      example: DOF
                      x-translations:
                        en:
                          description: Acronym of the official gazette that published the provision, for example `DOF`.
                    published_on:
                      type: string
                      format: date
                      pattern: ^\d{4}-\d{2}-\d{2}$
                      description: Fecha de publicación de la disposición (`YYYY-MM-DD`).
                      example: '2025-12-10'
                      x-translations:
                        en:
                          description: Publication date of the provision (`YYYY-MM-DD`).
                    title:
                      type: string
                      description: Título oficial de la disposición, en español.
                      example: Disposiciones de carácter general que señalan los días del año 2026 en que las entidades financieras sujetas a la supervisión de la Comisión Nacional Bancaria y de Valores deberán cerrar sus puertas y suspender operaciones
                      x-translations:
                        en:
                          description: Official title of the provision, in Spanish.
    SpeiCalendarResponse:
      type: object
      description: Respuesta de `GET /v1/public/spei-calendar/{year}`. Un único recurso con los días inhábiles SPEI del año y su fuente, y en `meta.covered_years` los años que tienen calendario. Un año sin calendario responde `404`, no este cuerpo.
      x-translations:
        en:
          description: Response from `GET /v1/public/spei-calendar/{year}`. A single resource with the SPEI non-business days of the year and its source, and in `meta.covered_years` the years that have a calendar. A year without a calendar responds `404`, not this body.
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la respuesta del calendario SPEI.
          x-translations:
            en:
              description: Schema body specific to the SPEI calendar response.
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/SpeiCalendarResource'
            meta:
              type: object
              description: Metadatos de la respuesta.
              x-translations:
                en:
                  description: Response metadata.
              properties:
                covered_years:
                  type: array
                  items:
                    type: integer
                    minimum: 1000
                    maximum: 9999
                  description: Años con calendario publicado, de menor a mayor.
                  example:
                    - 2024
                    - 2025
                    - 2026
                  x-translations:
                    en:
                      description: Years with a published calendar, from lowest to highest.
    BeneficiaryCapacity:
      type: object
      description: Cupo de beneficiarios activos del plan y ocupación actual de la cuenta autenticada.
      x-translations:
        en:
          description: Active-beneficiary allowance of the plan and current occupancy of the authenticated account.
      properties:
        max:
          type: integer
          minimum: -1
          description: Tope de beneficiarios activos. `-1` significa sin tope.
          x-translations:
            en:
              description: Active-beneficiary cap. `-1` means uncapped.
        current:
          type: integer
          minimum: 0
          description: Cantidad de beneficiarios activos guardados, independientemente del filtro de archivados.
          x-translations:
            en:
              description: Number of saved active beneficiaries, regardless of the archived filter.
        required:
          type: integer
          minimum: 0
          description: Cantidad de lugares necesarios para crear o reactivar cuentas del trabajo de importación.
          x-translations:
            en:
              description: Number of slots needed to create or reactivate accounts in the import job.
        exceeded:
          type: boolean
          description: '`true` cuando el trabajo de importación necesita lugares nuevos y supera el cupo disponible.'
          x-translations:
            en:
              description: '`true` when the import job needs new slots and exceeds the available allowance.'
    Beneficiary:
      type: object
      description: Cuenta beneficiaria registrada como recurso JSON:API. Puede ser una CLABE, un número de tarjeta o un número de celular DiMo. El tipo de cuenta se autodetecta por longitud al momento de inserción.
      x-translations:
        en:
          description: Registered beneficiary account as a JSON:API resource. Can be a CLABE, a card number, or a DiMo phone number. The account type is auto-detected by length at insertion time.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              enum:
                - beneficiary
              description: Tipo de recurso JSON:API. Siempre `beneficiary`.
              example: beneficiary
              x-translations:
                en:
                  description: JSON:API resource type. Always `beneficiary`.
            id:
              type: string
              pattern: ^[0-9]+$
              description: Identificador numérico del beneficiario en el sistema.
              example: '42'
              x-translations:
                en:
                  description: |
                    Numeric identifier of the beneficiary in the system.
            attributes:
              type: object
              description: 'Datos canónicos del beneficiario: los de la cuenta y los del registro.'
              x-translations:
                en:
                  description: Canonical beneficiary attributes (recipient account data and record metadata).
              required:
                - account_number
                - account_type
                - bank_code
                - bank_name
                - status
                - created_at
              properties:
                account_number:
                  type: string
                  pattern: ^(\d{10}|\d{16}|\d{18})$
                  description: 'Número de cuenta almacenado: CLABE (18 dígitos), tarjeta (16 dígitos) o celular/DiMo (10 dígitos). Use `account_type` para desambiguar el tipo exacto.'
                  example: '012180004412345678'
                  x-translations:
                    en:
                      description: 'Stored account number: 18-digit CLABE, 16-digit card, or 10-digit DiMo phone. Use `account_type` to disambiguate the exact type.'
                account_type:
                  type: string
                  enum:
                    - clabe
                    - card
                    - phone
                  description: 'Tipo de cuenta: `clabe` (18 dígitos), `card` (16 dígitos) o `phone` (10 dígitos).'
                  example: clabe
                  x-translations:
                    en:
                      description: 'Auto-detected account type: `clabe` (18 digits), `card` (16 digits), or `phone` (10 digits, DiMo mobile).'
                bank_code:
                  type: string
                  pattern: ^(\d{5})?$
                  maxLength: 5
                  description: 'Código SPEI (5 dígitos) del banco resuelto. Es la cadena vacía (`""`) cuando la cuenta no tiene un banco conocido: una tarjeta cuyo BIN no está en el directorio. En ese caso `bank_name` trae un texto de relleno en el idioma de la petición.'
                  example: '40012'
                  x-translations:
                    en:
                      description: 'SPEI code (5 digits) of the resolved bank. It is the empty string (`""`) when the account has no known bank: a card whose BIN is not in the directory. In that case `bank_name` carries a placeholder text in the language of the request.'
                bank_name:
                  type: string
                  description: Nombre oficial del banco, resuelto a partir de su `bank_code`. Con un `bank_code` vacío es un texto de relleno («BIN no reconocido (411111)»), que se traduce al idioma de la petición.
                  example: BBVA MEXICO
                  x-translations:
                    en:
                      description: Official name of the bank, resolved from its `bank_code`. With an empty `bank_code` it is a placeholder text ("Unrecognized BIN (411111)"), translated into the language of the request.
                label:
                  type:
                    - string
                    - 'null'
                  maxLength: 100
                  description: Etiqueta descriptiva libre del usuario. `null` cuando no fue asignada.
                  example: Proveedor ABC
                  x-translations:
                    en:
                      description: User-assigned descriptive label. `null` when not provided.
                status:
                  type: string
                  enum:
                    - active
                    - inactive
                  description: Estado del beneficiario. `inactive` si el beneficiario fue archivado. `active` si está en uso.
                  example: active
                  x-translations:
                    en:
                      description: Beneficiary status. `inactive` if the beneficiary was archived. `active` if it is in use.
                created_at:
                  type: string
                  format: date-time
                  description: Fecha y hora, en ISO 8601 UTC, de creación.
                  example: '2026-01-15T10:00:00Z'
                  x-translations:
                    en:
                      description: Date and time, in ISO 8601 UTC, of creation.
    ListBeneficiariesResponse:
      type: object
      description: 'Respuesta de `GET /v1/beneficiaries`: las cuentas beneficiarias del usuario autenticado, en un arreglo plano y sin paginar. El parámetro `?with_archived` decide cuáles entran.'
      x-translations:
        en:
          description: 'Response for `GET /v1/beneficiaries`: the authenticated user’s beneficiary accounts, as a flat array with no pagination. The `?with_archived` parameter decides which ones are included.'
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la respuesta del listado de beneficiarios del usuario.
          x-translations:
            en:
              description: Schema body specific to the user's beneficiaries listing response.
          required:
            - data
          properties:
            meta:
              type: object
              description: Metadatos de la respuesta y cupo del plan activo.
              x-translations:
                en:
                  description: Response metadata and active-plan allowance.
              properties:
                plan_cap:
                  $ref: '#/components/schemas/BeneficiaryCapacity'
            data:
              type: array
              description: Lista plana de beneficiarios del usuario.
              x-translations:
                en:
                  description: Flat list of the user's beneficiaries.
              items:
                $ref: '#/components/schemas/Beneficiary'
    CreateBeneficiaryRequest:
      type: object
      description: 'Cuerpo de `POST /v1/beneficiaries`. Acepta dos formas: un objeto plano con los atributos en la raíz, o el envoltorio JSON:API `{ data: { attributes: {...} } }`. El servidor prueba primero `data.attributes` y, si está ausente, toma el objeto raíz. El tipo de cuenta (`clabe`, `card`, `phone`) se autodetecta por la longitud de los dígitos: 18 = CLABE, 16 = tarjeta, 10 = celular (DiMo). Cuando el tipo detectado es `phone`, `bank_code` es **obligatorio**; para CLABE y tarjeta el `bank_code` enviado por el cliente se ignora y se deriva del prefijo CLABE o del BIN.'
      x-translations:
        en:
          description: 'Body for `POST /v1/beneficiaries`. Accepts two shapes: a flat object with attributes at the root, or the JSON:API envelope `{ data: { attributes: {...} } }`. The server tries `data.attributes` first and falls back to the root object. The account type (`clabe`, `card`, `phone`) is auto-detected by digit length: 18 = CLABE, 16 = card, 10 = phone (DiMo). When the detected type is `phone`, `bank_code` is **mandatory**; for CLABE and card the caller-supplied `bank_code` is ignored and derived from the CLABE prefix or BIN.'
      required:
        - account_number
      properties:
        account_number:
          type: string
          pattern: ^\d{10,19}$
          description: 'Número de cuenta. Se aceptan: CLABE (18 dígitos), tarjeta (16 dígitos) o celular/DiMo (10 dígitos). Los separadores, espacios y guiones se eliminan.'
          example: '012180004412345678'
          x-translations:
            en:
              description: 'Account number. Accepted: 18-digit CLABE, 16-digit card (only 18, reserved for CLABE) or 10-digit DiMo phone. Separators (spaces, hyphens) are stripped before validation.'
        bank_code:
          type: string
          pattern: ^\d{4,5}$
          description: Código SPEI (4 o 5 dígitos). **Obligatorio cuando el tipo detectado es** `phone`; ignorado para CLABE y tarjeta.
          example: '40012'
          x-translations:
            en:
              description: SPEI code (4 or 5 digits). **Required when the detected type is** `phone`; ignored for CLABE and card.
        label:
          type: string
          maxLength: 100
          description: Etiqueta opcional con la que la cuenta se identifica en la lista de beneficiarios.
          example: Proveedor ABC
          x-translations:
            en:
              description: |
                Optional label the account is identified by in the beneficiary list.
    CreateBeneficiaryResponse:
      type: object
      description: 'Respuesta de `POST /v1/beneficiaries` con el beneficiario en `data`. Devuelve `201` al crear un registro nuevo, y `200` cuando reactiva una cuenta archivada con el mismo `account_number`: Ahí `meta.reactivated` viene en `true`.'
      x-translations:
        en:
          description: 'Response for `POST /v1/beneficiaries`, with the beneficiary under `data`. Returns `201` when it creates a new record, and `200` when it reactivates an archived account with the same `account_number`: There `meta.reactivated` comes back `true`.'
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la respuesta al crear o reactivar un beneficiario.
          x-translations:
            en:
              description: Schema body specific to the create-or-reactivate beneficiary response.
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/Beneficiary'
    UpdateBeneficiaryRequest:
      type: object
      description: 'Cuerpo de `PUT /v1/beneficiaries/{id}`. Acepta envoltorio JSON:API `{ data: { attributes: {...} } }` o un objeto plano. Todos los campos son opcionales — solo se actualizan los presentes. Si el cuerpo no contiene ningún campo editable, el servidor responde `422 no_valid_fields`. Hay tres rutas de actualización. Con solo `label`, se sanea y se guarda. Con un `account_number` nuevo, se vuelven a derivar `account_type`, `bank_code` y `bank_name` con las mismas reglas que `POST /v1/beneficiaries`. Con solo `bank_code`, el cambio vale únicamente si el beneficiario es de tipo `phone`: En CLABE y tarjeta se ignora sin avisar—, y deja el evento de auditoría `beneficiary.phone_bank_updated` cuando el código cambia.'
      x-translations:
        en:
          description: 'Body for `PUT /v1/beneficiaries/{id}`. Accepts JSON:API envelope `{ data: { attributes: {...} } }` or a flat object. All fields are optional — only those present are updated. If the body contains no editable field the server returns `422 no_valid_fields`. There are three update paths. With `label` alone, it is sanitized and stored. With a new `account_number`, `account_type`, `bank_code`, and `bank_name` are re-derived using the same rules as `POST /v1/beneficiaries`. With `bank_code` alone, the change only counts when the beneficiary is of type `phone`: On CLABE and card it is ignored silently — and it leaves the `beneficiary.phone_bank_updated` audit event when the code changes.'
      properties:
        label:
          type: string
          maxLength: 100
          description: Etiqueta opcional con la que la cuenta se identifica en la lista de beneficiarios.
          example: Proveedor XYZ
          x-translations:
            en:
              description: Optional label the account is identified by in the beneficiary list.
        account_number:
          type: string
          pattern: ^\d{10,19}$
          description: Nuevo número de cuenta. Reemplaza el existente y re-deriva `account_type`, `bank_code` y `bank_name`. Para tipo `phone`, `bank_code` debe acompañar la petición siempre.
          example: '012180004412345678'
          x-translations:
            en:
              description: New account number. Replaces the existing one and triggers the re-derivation of `account_type`, `bank_code` and `bank_name`. For type `phone`, `bank_code` must accompany the request.
        bank_code:
          type: string
          pattern: ^\d{4,5}$
          description: Código SPEI (4 o 5 dígitos) del banco del beneficiario. Para CLABE/card el campo se ignora. Para celular/DiMo puede enviarse aislado para reasignar el banco del receptor.
          example: '40021'
          x-translations:
            en:
              description: SPEI code (4 or 5 digits) of the beneficiary's bank. For CLABE/card the field is ignored. For phone/DiMo it may be sent alone to reassign the receiving bank.
    UpdateBeneficiaryResponse:
      type: object
      description: 'Respuesta de `PUT /v1/beneficiaries/{id}`: el beneficiario actualizado, con los campos derivados ya recalculados.'
      x-translations:
        en:
          description: 'Response for `PUT /v1/beneficiaries/{id}`: the updated beneficiary, with the derived fields already recalculated.'
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la respuesta al actualizar un beneficiario existente.
          x-translations:
            en:
              description: Schema body specific to the update-beneficiary response.
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/Beneficiary'
    BeneficiaryLookupResource:
      type: object
      description: Recurso JSON:API con la cuenta beneficiaria resuelta para el usuario.
      x-translations:
        en:
          description: JSON:API resource carrying the resolved beneficiary account for the user.
      required:
        - type
        - attributes
      properties:
        type:
          type: string
          enum:
            - beneficiary_lookup
          description: Tipo del recurso JSON:API. Siempre `beneficiary_lookup`.
          example: beneficiary_lookup
          x-translations:
            en:
              description: JSON:API resource type. Always `beneficiary_lookup`.
        attributes:
          type: object
          description: Datos de la cuenta resuelta.
          x-translations:
            en:
              description: Resolved account metadata.
          required:
            - account_number
            - account_type
            - bank_code
            - bank_name
          properties:
            account_number:
              type: string
              pattern: ^(\d{10}|\d{16}|\d{18})$
              description: Número de cuenta con el que se buscó, ya sea CLABE, tarjeta o celular DiMo.
              example: '012180004412345678'
              x-translations:
                en:
                  description: Account number the lookup was made with, whether CLABE, card, or DiMo phone.
            account_type:
              type: string
              enum:
                - clabe
                - card
                - phone
              description: 'Tipo de cuenta: `clabe` (18 dígitos), `card` (16 dígitos) o `phone` (10 dígitos).'
              example: clabe
              x-translations:
                en:
                  description: 'Account type auto-detected at registration. `clabe`: 18-digit CLABE; `card`: 16-digit card; `phone`: 10-digit phone.'
            bank_code:
              type: string
              pattern: ^(\d{5})?$
              maxLength: 5
              description: Código SPEI (5 dígitos) del banco resuelto. Es la cadena vacía (`""`) para una tarjeta cuyo BIN no está en el directorio.
              example: '40012'
              x-translations:
                en:
                  description: SPEI code (5 digits) of the resolved bank. It is the empty string (`""`) for a card whose BIN is not in the directory.
            bank_name:
              type: string
              description: Nombre oficial del banco, resuelto a partir de su `bank_code`.
              example: BBVA MEXICO
              x-translations:
                en:
                  description: Official name of the bank, resolved from its `bank_code`.
            label:
              type:
                - string
                - 'null'
              maxLength: 100
              description: Etiqueta libre del beneficiario, `null` cuando no fue asignada.
              example: Proveedor ABC
              x-translations:
                en:
                  description: Free-form label, `null` when none was assigned.
    LookupBeneficiaryAccountResponse:
      type: object
      description: 'Respuesta de `GET /v1/beneficiaries/lookup`: los metadatos del beneficiario cuando la cuenta consultada está entre las del usuario autenticado.'
      x-translations:
        en:
          description: 'Response for `GET /v1/beneficiaries/lookup`: the beneficiary metadata when the queried account is among the authenticated user’s own.'
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la consulta de cuenta en la lista blanca del usuario.
          x-translations:
            en:
              description: Schema body specific to the user beneficiary whitelist lookup.
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/BeneficiaryLookupResource'
    CreateBeneficiaryImportRequest:
      type: object
      description: 'Cuerpo multipart de `POST /v1/beneficiaries/imports`: el archivo a importar y el modo de lectura con el que se procesa.'
      x-translations:
        en:
          description: 'Multipart body for `POST /v1/beneficiaries/imports`: the file to import and the parse mode it is read with.'
      required:
        - file
        - parse_mode
      properties:
        file:
          type: string
          format: binary
          description: 'Archivo a importar. Formatos aceptados: CSV, XLS, XLSX, TXT o PDF. Tamaño máximo: 20 MB.'
          x-translations:
            en:
              description: 'File to import. Accepted formats: CSV, XLS, XLSX, TXT or PDF. Maximum size: 20 MB.'
          example: beneficiarios-marzo.csv
        parse_mode:
          type: string
          enum:
            - template
            - free
          description: 'Modo de lectura del archivo — `template`: Respeta los encabezados canónicos; `free`: Formato libre, el sistema deduce la estructura.'
          x-translations:
            en:
              description: 'File reading mode — `template`: Follows the canonical headers; `free`: Free-form, the system infers the structure.'
          example: template
    CreateBeneficiaryImportResource:
      type: object
      description: Recurso JSON:API mínimo con el ID de la importación y su estado inicial.
      x-translations:
        en:
          description: Minimal JSON:API resource with the job ID and its initial status.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              enum:
                - beneficiary_import
              example: beneficiary_import
              description: Tipo del recurso JSON:API. Siempre `beneficiary_import`.
              x-translations:
                en:
                  description: JSON:API resource type. Always `beneficiary_import`.
            id:
              type: string
              description: Identificador numérico del trabajo de importación (expresado como string).
              example: '42'
              x-translations:
                en:
                  description: Numeric job ID expressed as a string (JSON:API format).
            attributes:
              type: object
              description: Datos iniciales del trabajo de importación, devueltos al crearlo.
              x-translations:
                en:
                  description: Initial job attributes returned upon creation.
              required:
                - status
              properties:
                status:
                  type: string
                  enum:
                    - pending
                  description: Estado inicial del trabajo de importación. El único valor posible es `pending`.
                  example: pending
                  x-translations:
                    en:
                      description: Initial job status. The only possible value is `pending`.
    CreateBeneficiaryImportResponse:
      type: object
      description: 'Respuesta `202` de `POST /v1/beneficiaries/imports`: el ID de la importación recién creada y su estado inicial (`pending`).'
      x-translations:
        en:
          description: 'A `202` response for `POST /v1/beneficiaries/imports`: the ID of the newly created import and its initial status (`pending`).'
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la creación de la importación de beneficiarios.
          x-translations:
            en:
              description: Schema body specific to the beneficiary import job creation acknowledgement.
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/CreateBeneficiaryImportResource'
    BeneficiaryImportJob:
      type: object
      description: 'Importación masiva de beneficiarios. Ciclo de vida: `pending` → `parsing` → `preview_ready` → `committing` → `completed` (o `failed` / `cancelled`). Envuelto bajo `{ type, id, attributes }` siguiendo el formato JSON:API.'
      x-translations:
        en:
          description: 'Bulk beneficiary import job. Lifecycle: `pending` → `parsing` → `preview_ready` → `committing` → `completed` (or `failed` / `cancelled`). Wrapped under `{ type, id, attributes }` following JSON:API format.'
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          properties:
            type:
              type: string
              enum:
                - beneficiary_import
              description: Tipo del recurso JSON:API. Siempre `beneficiary_import`.
              example: beneficiary_import
              x-translations:
                en:
                  description: JSON:API resource type. Always `beneficiary_import`.
            id:
              type: string
              description: Identificador numérico del trabajo de importación (expresado como string).
              example: '42'
              x-translations:
                en:
                  description: Numeric job ID expressed as a string (JSON:API format).
            attributes:
              type: object
              description: Datos del trabajo de importación.
              x-translations:
                en:
                  description: Import job fields.
              properties:
                plan_cap:
                  $ref: '#/components/schemas/BeneficiaryCapacity'
                status:
                  type: string
                  enum:
                    - pending
                    - parsing
                    - preview_ready
                    - committing
                    - completed
                    - failed
                    - cancelled
                  description: 'Estado del trabajo de importación — `pending`: Subido y en espera de parseo; `parsing`: En proceso de extracción de filas; `preview_ready`: Listo para revisión del usuario; `committing`: Persistiendo filas confirmadas; `completed`: Importación completada; `failed`: Fallo no recuperable (ver `error_code`); `cancelled`: Cancelado por el usuario.'
                  example: preview_ready
                  x-translations:
                    en:
                      description: 'Job status — `pending`: Uploaded, awaiting parsing; `parsing`: Extracting rows; `preview_ready`: Ready for user review; `committing`: Persisting confirmed rows; `completed`: Commit finished; `failed`: Non-recoverable failure (see `error_code`); `cancelled`: Cancelled by the user.'
                file_format:
                  type: string
                  enum:
                    - csv
                    - xls
                    - xlsx
                    - txt
                    - pdf
                  description: 'Formato del archivo subido. Aceptados: `csv`, `xls`, `xlsx`, `txt` o `pdf`.'
                  example: csv
                  x-translations:
                    en:
                      description: 'Format of the uploaded file. Accepted: `csv`, `xls`, `xlsx`, `txt` or `pdf`.'
                parse_mode:
                  type: string
                  enum:
                    - template
                    - free
                  description: 'Modo de lectura del archivo — `template`: Respeta los encabezados canónicos; `free`: Formato libre, el sistema deduce la estructura.'
                  example: template
                  x-translations:
                    en:
                      description: 'File reading mode — `template`: Follows the canonical headers; `free`: Free-form, the system infers the structure.'
                total_rows:
                  type: integer
                  minimum: 0
                  description: Cantidad de filas extraídas del archivo. Sólo disponible cuando el estado es `preview_ready` o posterior.
                  example: 150
                  x-translations:
                    en:
                      description: Number of rows extracted from the file. Only available once the status is `preview_ready` or later.
                valid_count:
                  type: integer
                  minimum: 0
                  description: Cantidad de filas sin errores, listas para la confirmación.
                  example: 120
                  x-translations:
                    en:
                      description: Number of rows without errors, ready to be committed.
                correctable_count:
                  type: integer
                  minimum: 0
                  description: Cantidad de filas con errores que admiten corrección. Se persisten salvo que el usuario las rechace.
                  example: 20
                  x-translations:
                    en:
                      description: Number of rows with errors that can be corrected. They are persisted unless the user rejects them.
                fatal_count:
                  type: integer
                  minimum: 0
                  description: Cantidad de filas que no pueden procesarse con sus datos actuales. No se persisten y se omiten de la confirmación.
                  example: 5
                  x-translations:
                    en:
                      description: Number of rows that cannot be processed with their current data. They are not persisted and are skipped on commit.
                duplicate_count:
                  type: integer
                  minimum: 0
                  description: Cantidad de filas detectadas como duplicadas dentro del trabajo. Las de `duplicate_account` se omiten de la confirmación, salvo que la cuenta esté archivada, en cuyo caso se reactiva; las de `duplicate_alias` se persisten con un sufijo en el alias.
                  example: 5
                  x-translations:
                    en:
                      description: Number of rows detected as duplicates within the job. Those with `duplicate_account` are skipped on commit, unless the account is archived, in which case it is reactivated; those with `duplicate_alias` are persisted with a suffix on the alias.
                committed_count:
                  type: integer
                  minimum: 0
                  description: Filas efectivamente persistidas en la lista de beneficiarios (solo disponible una vez que el estado es `completed`).
                  example: 140
                  x-translations:
                    en:
                      description: Rows effectively persisted in the beneficiary list. Available once status is `completed`.
                skipped_count:
                  type: integer
                  minimum: 0
                  description: Cantidad de filas omitidas durante la ejecución del trabajo. Son las fatales y las `duplicate_account` sin reactivación posible.
                  example: 10
                  x-translations:
                    en:
                      description: Number of rows skipped while the job was running. They are the fatal ones and the `duplicate_account` ones with no possible reactivation.
                llm_invoked:
                  type: boolean
                  description: '`true` cuando el motor de IA (LLM) se utilizó durante el parseo para resolver filas ambiguas en modo libre.'
                  example: false
                  x-translations:
                    en:
                      description: '`true` when the AI engine was used during parsing to resolve ambiguous rows in free-form mode.'
                error_code:
                  type:
                    - string
                    - 'null'
                  description: Código estable del fallo del trabajo. `null` cuando no terminó en `failed`. Por ejemplo, `file_corrupt` o `plan_cap_exceeded`.
                  example: null
                  x-translations:
                    en:
                      description: Stable code of the job failure. `null` when it did not end in `failed`. For example, `file_corrupt` or `plan_cap_exceeded`.
                error_summary:
                  type:
                    - string
                    - 'null'
                  description: Resumen legible del fallo del trabajo. `null` cuando no terminó en `failed`. Los números de tarjeta aparecen enmascarados.
                  example: null
                  x-translations:
                    en:
                      description: Readable summary of the job failure. `null` when it did not end in `failed`. Card numbers appear masked.
                created_at:
                  $ref: '#/components/schemas/TimestampUTC'
                parsed_at:
                  type:
                    - string
                    - 'null'
                  format: date-time
                  description: Fecha y hora del fin de la lectura del archivo, en ISO 8601 UTC. `null` mientras no haya terminado.
                  example: null
                  x-translations:
                    en:
                      description: Date and time when the file reading finished, in ISO 8601 UTC. `null` while it has not finished.
                committed_at:
                  type:
                    - string
                    - 'null'
                  format: date-time
                  description: Fecha y hora del inicio de la confirmación, en ISO 8601 UTC. `null` mientras no se haya confirmado.
                  example: null
                  x-translations:
                    en:
                      description: Date and time when the commit started, in ISO 8601 UTC. `null` while it has not been committed.
                completed_at:
                  type:
                    - string
                    - 'null'
                  format: date-time
                  description: Fecha y hora de la finalización, en ISO 8601 UTC. `null` mientras no haya terminado.
                  example: null
                  x-translations:
                    en:
                      description: Date and time of completion, in ISO 8601 UTC. `null` while it has not finished.
    BeneficiaryImportJobResponse:
      type: object
      description: 'Respuesta de `GET /v1/beneficiaries/imports/{id}`: la importación con su estado actual y los contadores por grupo de clasificación.'
      x-translations:
        en:
          description: 'Response for `GET /v1/beneficiaries/imports/{id}`: the import with its current status and the per-group counters.'
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la respuesta de la importación de beneficiarios.
          x-translations:
            en:
              description: Schema body specific to the beneficiary import job response.
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/BeneficiaryImportJob'
    BeneficiaryImportRow:
      type: object
      description: Fila extraída de un archivo de importación de beneficiarios, con su grupo de clasificación y los campos parseados. La cuenta viaja completa para el dueño de la importación, que la revisa y corrige antes de confirmar; la vista admin de otro usuario la enmascara. Envuelta bajo `{ type, id, attributes }` siguiendo el formato JSON:API.
      x-translations:
        en:
          description: Row extracted from a beneficiary import file, with its classification bucket and parsed fields. The account travels in full for the import owner, who reviews and corrects it before committing; the cross-user admin view masks it. Wrapped under `{ type, id, attributes }` following JSON:API format.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          properties:
            type:
              type: string
              enum:
                - beneficiary_import_row
              description: Tipo del recurso JSON:API. Siempre `beneficiary_import_row`.
              example: beneficiary_import_row
              x-translations:
                en:
                  description: JSON:API resource type. Always `beneficiary_import_row`.
            id:
              type: string
              description: Identificador numérico de la fila (expresado como string).
              example: '123'
              x-translations:
                en:
                  description: Numeric row ID expressed as a string (JSON:API format).
            attributes:
              type: object
              description: Datos de la fila interpretada.
              x-translations:
                en:
                  description: Parsed row fields.
              properties:
                row_index:
                  type: integer
                  minimum: 0
                  description: Posición de la fila dentro del archivo original, empezando en `0`. Permite localizar la fila en el documento original.
                  example: 5
                  x-translations:
                    en:
                      description: Position of the row within the original file, starting at `0`. It allows locating the row in the source document.
                status:
                  type: string
                  enum:
                    - valid
                    - correctable
                    - fatal
                    - duplicate_account
                    - duplicate_alias
                  description: 'Grupo de clasificación — `valid`: Lista para confirmación; `correctable`: Corregida automáticamente; `fatal`: Error no corregible, se omite; `duplicate_account`: Cuenta ya registrada (se reactiva si estaba archivada); `duplicate_alias`: Alias duplicado, se persiste con sufijo.'
                  example: valid
                  x-translations:
                    en:
                      description: 'Classification bucket — `valid`: Ready for commit; `correctable`: Auto-corrected, confirmable; `fatal`: Uncorrectable error, skipped; `duplicate_account`: Account already registered (reactivated if archived); `duplicate_alias`: Duplicate alias, persisted with suffix.'
                parsed_account:
                  type:
                    - string
                    - 'null'
                  description: Número de cuenta extraído del archivo (normalizado). El propietario de la importación puede ver y editar este valor en la vista de vista previa; `null` cuando no fue posible extraer una cuenta.
                  example: '012180004412345678'
                  x-translations:
                    en:
                      description: Normalized account number extracted from the file. The job owner can view and edit this value in the preview; `null` when no account could be extracted.
                parsed_account_type:
                  type:
                    - string
                    - 'null'
                  enum:
                    - clabe
                    - card
                    - phone
                    - null
                  description: Tipo de cuenta detectado. Los tipos son `clabe`, `card` y `phone`. Además de `null` cuando `parsed_account` es nulo.
                  example: clabe
                  x-translations:
                    en:
                      description: Detected account type. `null` when `parsed_account` is null. The types are `clabe`, `card`, and `phone`.
                parsed_bank_code:
                  type:
                    - string
                    - 'null'
                  pattern: ^\d{5}$
                  description: Código SPEI (5 dígitos) del banco resuelto, derivado de \ `parsed_account`. Es `null` cuando no pudo resolverse.
                  example: '40012'
                  x-translations:
                    en:
                      description: 5-digit SPEI code of the resolved bank, derived from `parsed_account`. `null` when it could not be resolved.
                parsed_bank_name:
                  type:
                    - string
                    - 'null'
                  description: Nombre oficial del banco resuelto resuelto. Es `null` cuando no pudo resolverse.
                  example: BBVA MEXICO
                  x-translations:
                    en:
                      description: Resolved name of the receiving bank. `null` when it could not be resolved.
                parsed_label:
                  type:
                    - string
                    - 'null'
                  description: Etiqueta/alias extraída del archivo (o asignada automáticamente). `null` cuando no fue posible extraer una etiqueta y no se asignó automáticamente.
                  example: Proveedor ABC
                  x-translations:
                    en:
                      description: Label/alias extracted from the file or auto-assigned. `null` when no label could be extracted and none was auto-assigned.
                error_codes:
                  type: array
                  items:
                    type: string
                  description: Códigos de error estables de la fila (p.ej. `clabe_checksum_failed`, `alias_missing`). Vacío para filas `valid`.
                  example: []
                  x-translations:
                    en:
                      description: Stable error codes for the row (e.g. `clabe_checksum_failed`, `alias_missing`). Empty for `valid` rows.
                corrections_applied:
                  type: object
                  additionalProperties: true
                  description: 'Auto-correcciones que el sistema aplicó a esta fila (p.ej. `{ "alias_auto_assigned": "Proveedor 001" }`). Vacío si no hubo correcciones.'
                  example: {}
                  x-translations:
                    en:
                      description: 'Auto-corrections applied by the system to this row (e.g. `{ "alias_auto_assigned": "Proveedor 001" }`). Empty if no corrections were made.'
                user_overrides:
                  type: object
                  additionalProperties: true
                  description: Correcciones manuales del usuario enviadas mediante `PATCH /v1/beneficiaries/imports/{id}/rows/{rowId}`. Tienen precedencia sobre los valores parseados en la confirmación.
                  example: {}
                  x-translations:
                    en:
                      description: Manual user overrides submitted via `PATCH /v1/beneficiaries/imports/{id}/rows/{rowId}`. Take precedence over parsed values during commit.
                raw_preview:
                  type: object
                  additionalProperties: true
                  description: Fragmento del archivo original para diagnóstico. Los dígitos de 6 o más caracteres consecutivos aparecen enmascarados (`••••`) para evitar exposición de números de tarjeta en los cuerpos de depuración.
                  example: {}
                  x-translations:
                    en:
                      description: Snippet of the original file for diagnostic purposes. Sequences of 6 or more consecutive digits are masked (`••••`) to prevent PAN exposure in debug payloads.
                created_beneficiary_id:
                  type:
                    - integer
                    - 'null'
                  description: Identificador numérico del beneficiario creado tras la confirmación. `null` hasta que la importación completó y esta fila fue persistida.
                  example: null
                  x-translations:
                    en:
                      description: ID of the beneficiary record created after commit. `null` until the job completed and this row was persisted.
    BeneficiaryImportPreviewResponse:
      type: object
      description: 'Respuesta de `GET /v1/beneficiaries/imports/{id}/preview`: las filas parseadas, paginadas, con una instantánea de la importación en `meta`. Las cuentas viajan completas para el propietario de la importación; la vista admin de otro usuario las enmascara.'
      x-translations:
        en:
          description: 'Response for `GET /v1/beneficiaries/imports/{id}/preview`: the parsed rows, paginated, with a snapshot of the import under `meta`. Accounts travel in full for the import owner; the cross-user admin view masks them.'
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la vista previa de filas y metadatos de paginación de la importación.
          x-translations:
            en:
              description: Schema body specific to the preview rows and job pagination metadata.
          required:
            - data
            - meta
          properties:
            data:
              type: array
              description: Filas extraídas del archivo, paginadas según los parámetros.
              x-translations:
                en:
                  description: Rows extracted from the file, paginated per the parameters.
              items:
                $ref: '#/components/schemas/BeneficiaryImportRow'
            meta:
              type: object
              description: Metadatos de la vista previa (paginación y snapshot de la importación).
              x-translations:
                en:
                  description: Preview metadata (pagination and job snapshot).
              properties:
                pagination:
                  type: object
                  description: Información de paginación de las filas devueltas.
                  x-translations:
                    en:
                      description: Pagination details for the returned rows.
                  required:
                    - page
                    - per_page
                    - total
                    - total_pages
                  properties:
                    page:
                      type: integer
                      minimum: 1
                      description: Página devuelta, empezando en 1.
                      example: 1
                      x-translations:
                        en:
                          description: Page returned, starting at 1.
                    per_page:
                      type: integer
                      minimum: 1
                      maximum: 100
                      description: Filas por página (1–100).
                      example: 25
                      x-translations:
                        en:
                          description: Rows per page (1–100).
                    total:
                      type: integer
                      minimum: 0
                      description: Cantidad de filas para los filtros aplicados.
                      example: 150
                      x-translations:
                        en:
                          description: Number of rows for the applied filters.
                    total_pages:
                      type: integer
                      minimum: 0
                      description: Número total de páginas para los filtros aplicados.
                      example: 6
                      x-translations:
                        en:
                          description: Number of pages for the applied filters.
                job:
                  $ref: '#/components/schemas/BeneficiaryImportJob'
    PatchBeneficiaryImportRowRequest:
      type: object
      description: 'Cuerpo de `PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}`. Acepta envoltorio JSON:API `{ data: { attributes: {...} } }` o un objeto plano. Todos los atributos son opcionales — solo se sobreescriben los presentes. Cinco campos editables: `parsed_account`, `parsed_label`, `parsed_account_type`, `parsed_bank_code`, `parsed_bank_name`. Cualquier otra clave se ignora. Si el cuerpo no contiene ningún campo editable reconocido → `422 no_valid_fields`. Si la importación no está en estado `preview_ready` → `422 job_not_editable`. Tras persistir la corrección, el servidor re-procesa la fila (re-deriva banco a partir del prefijo CLABE / BIN nuevos) para que los contadores de la importación y la columna Banco de la vista previa queden sincronizados sin esperar a la confirmación.'
      x-translations:
        en:
          description: 'Body for `PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}`. Accepts the JSON:API envelope `{ data: { attributes: {...} } }` or a flat object. All attributes are optional — only those present are overwritten. Five editable fields: `parsed_account`, `parsed_label`, `parsed_account_type`, `parsed_bank_code`, `parsed_bank_name`. Any other key is ignored. If no recognized editable field is present → `422 no_valid_fields`. If the job is not in `preview_ready` state → `422 job_not_editable`. After persisting the override, the server re-processes the row (re-derives bank from the new CLABE prefix / BIN) so that job counters and the preview''s Bank column stay in sync without waiting for commit.'
      properties:
        parsed_account:
          type: string
          pattern: ^[0-9 \-\xC2\xA0]{0,32}$
          maxLength: 32
          description: Número de cuenta corregido por el usuario. Acepta máximo 32 caracteres de dígitos. Se re-normaliza al re-procesar.
          example: '012180004412345678'
          x-translations:
            en:
              description: User-corrected account number. Accepts up to 32 characters of digits. It is re-normalized on re-processing.
        parsed_label:
          type: string
          maxLength: 100
          description: Etiqueta corregida de la fila. Si empieza por `=`, `+`, `-` o `@`, se guarda con un apóstrofo delante para que una hoja de cálculo no la lea como fórmula.
          example: Mamá
          x-translations:
            en:
              description: Corrected row label. If it starts with `=`, `+`, `-` or `@`, it is stored with a leading apostrophe so a spreadsheet does not read it as a formula.
        parsed_account_type:
          type: string
          enum:
            - clabe
            - card
            - phone
          description: 'Tipo de cuenta corregido. `clabe`: CLABE (18 dígitos); `card`: tarjeta (16 dígitos); `phone`: celular/DiMo (10 dígitos).'
          example: clabe
          x-translations:
            en:
              description: 'Corrected account type. `clabe`: CLABE (18 digits); `card`: card (16 digits); `phone`: phone/DiMo (10 digits).'
        parsed_bank_code:
          type: string
          pattern: ^\d{4,5}$
          description: Código SPEI corregido (5 dígitos). Útil para cuentas tipo `phone` que necesitan reasignación de banco. Para CLABE/card se sobrescribe en su re-derivación.
          example: '40012'
          x-translations:
            en:
              description: Corrected SPEI code (5 digits). Useful for `phone` rows that need bank reassignment. For CLABE/card it is overridden on re-derivation.
        parsed_bank_name:
          type: string
          maxLength: 50
          description: Nombre del banco corregido. Puede sobrescribirse durante la re-derivación.
          example: BBVA MEXICO
          x-translations:
            en:
              description: Corrected bank name. May be overridden during re-derivation.
    PatchBeneficiaryImportRowResponse:
      type: object
      description: 'Respuesta de `PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}`: la fila ya corregida y reprocesada. Su grupo de clasificación (`status`) puede haber cambiado con la corrección.'
      x-translations:
        en:
          description: 'Response for `PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}`: the row once corrected and reprocessed. Its classification group (`status`) may have changed with the correction.'
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la respuesta al editar una fila del import (devuelve la fila re-procesada).
          x-translations:
            en:
              description: Schema body specific to the import row edit response (returns the re-processed row).
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/BeneficiaryImportRow'
    CommitBeneficiaryImportResource:
      type: object
      description: Recurso JSON:API mínimo con el ID de la importación y su nuevo estado tras la confirmación.
      x-translations:
        en:
          description: Minimal JSON:API resource with the job ID and the new status after commit.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              enum:
                - beneficiary_import
              example: beneficiary_import
              description: Tipo del recurso JSON:API. Siempre `beneficiary_import`.
              x-translations:
                en:
                  description: JSON:API resource type. Always `beneficiary_import`.
            id:
              type: string
              description: Identificador numérico del trabajo de importación (expresado como string).
              example: '42'
              x-translations:
                en:
                  description: Numeric job ID expressed as a string.
            attributes:
              type: object
              description: Datos del trabajo de importación tras la confirmación, con su estado de transición.
              x-translations:
                en:
                  description: Import job data after the commit, with its transition status.
              required:
                - status
              properties:
                status:
                  type: string
                  enum:
                    - committing
                  description: Nuevo estado tras encolar la confirmación. El único valor posible es `committing`.
                  example: committing
                  x-translations:
                    en:
                      description: New status after enqueuing the commit. The only possible value is `committing`.
    CommitBeneficiaryImportResponse:
      type: object
      description: 'Respuesta `202` de `POST /v1/beneficiaries/imports/{id}/commit`: el ID de la importación y su estado tras encolar la confirmación (`committing`).'
      x-translations:
        en:
          description: 'A `202` response for `POST /v1/beneficiaries/imports/{id}/commit`: the import ID and its status once the commit has been queued (`committing`).'
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la confirmación de la confirmación de la importación.
          x-translations:
            en:
              description: Schema body specific to the import job commit acknowledgement.
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/CommitBeneficiaryImportResource'
    PlanQuotaState:
      type: object
      description: 'Estado de la cuota de validaciones vigente: límite, consumo, saldo y reinicio.'
      x-translations:
        en:
          description: 'Current validation quota state: limit, usage, balance, and reset.'
      properties:
        plan_slug:
          type: string
          description: Identificador único y legible del plan. Corresponde al plan activo del usuario.
          example: free
          x-translations:
            en:
              description: Unique, human-readable plan identifier. It corresponds to the user's active plan.
        plan_name:
          type: string
          description: Nombre del plan que muestran las interfaces.
          example: Free
          x-translations:
            en:
              description: Plan name shown in the interfaces.
        limit:
          type: integer
          minimum: 0
          description: Límite efectivo de validaciones para el ciclo actual.
          example: 500
          x-translations:
            en:
              description: Effective validation limit for the current cycle.
        used:
          type: integer
          minimum: 0
          description: Validaciones consumidas en el ciclo actual.
          example: 350
          x-translations:
            en:
              description: Validations consumed in the current cycle.
        remaining:
          type: integer
          minimum: 0
          description: Validaciones disponibles en el ciclo actual (`max(0, limit - used)`).
          example: 150
          x-translations:
            en:
              description: Validations available in the current cycle (`max(0, limit - used)`).
        resets_at:
          $ref: '#/components/schemas/TimestampUTC'
    ApiUsage:
      type: object
      description: |
        Estadísticas de uso de la API, cuota de validaciones y estado de Banxico para la cuenta.
      x-translations:
        en:
          description: |
            API usage statistics, validation quota, and Banxico status for the account.
      properties:
        requests_today:
          type: integer
          minimum: 0
          description: Cantidad de peticiones a la API del usuario en el día calendario actual (UTC).
          example: 42
          x-translations:
            en:
              description: Number of API requests on the current calendar day (UTC).
        requests_month:
          type: integer
          minimum: 0
          description: Cantidad de peticiones a la API del usuario en el mes calendario actual (UTC).
          example: 580
          x-translations:
            en:
              description: Number of API requests in the current calendar month (UTC).
        quota:
          $ref: '#/components/schemas/PlanQuotaState'
        recent_requests:
          type: array
          description: Últimas peticiones a la API del usuario, de la más reciente a la más antigua.
          x-translations:
            en:
              description: |
                User's most recent API requests, from most to least recent.
          items:
            type: object
            description: Entrada del registro de actividad reciente de la API del usuario.
            x-translations:
              en:
                description: Recent API activity log entry for the user.
            properties:
              method:
                type: string
                enum:
                  - GET
                  - POST
                  - PUT
                  - PATCH
                  - DELETE
                description: Verbo HTTP de la petición. Uno de `GET`, `POST`, `PUT`, `PATCH` o `DELETE`.
                example: POST
                x-translations:
                  en:
                    description: HTTP verb of the request. One of `GET`, `POST`, `PUT`, `PATCH` or `DELETE`.
              endpoint:
                type: string
                description: Ruta normalizada de la petición, sin la cadena de consulta.
                example: /v1/validate
                x-translations:
                  en:
                    description: Normalized request path, without the query string.
              response_status:
                type: integer
                minimum: 100
                maximum: 599
                description: Código de estado HTTP con el que respondió la API.
                example: 200
                x-translations:
                  en:
                    description: HTTP status code the API responded with.
              processing_time_ms:
                type: integer
                minimum: 0
                description: Duración del handler, en milisegundos.
                example: 1250
                x-translations:
                  en:
                    description: Handler duration, in milliseconds.
              is_playground:
                type: boolean
                description: '`true` cuando la petición se hizo en el banco de pruebas (Playground).'
                example: false
                x-translations:
                  en:
                    description: '`true` when the request was made in the test bench (Playground).'
              created_at:
                $ref: '#/components/schemas/TimestampUTC'
        banxico_status:
          type: string
          enum:
            - operational
            - degraded
            - down
            - unknown
          description: 'Estado actual del servicio Banxico CEP — `operational`: Servicio completamente disponible; `degraded`: Disponible pero con errores parciales; `down`: Completamente no disponible; `unknown`: Sin datos suficientes para determinar el estado.'
          example: operational
          x-translations:
            en:
              description: |
                Current Banxico CEP service status — `operational`: Available and responding normally; `degraded`: Available but with partial errors; `down`: Completely unavailable (all recent records are errors); `unknown`: Insufficient recent data to determine status.
        usage_history:
          type: array
          description: Historial de validaciones consumidas por el usuario (por mes calendario).
          x-translations:
            en:
              description: Validations consumed per calendar month (history).
          items:
            type: object
            description: Entrada del historial mensual de consumo.
            x-translations:
              en:
                description: Monthly usage history entry.
            properties:
              period:
                type: string
                pattern: ^\d{4}-\d{2}$
                description: Mes en formato `YYYY-MM`.
                example: 2026-04
                x-translations:
                  en:
                    description: Month in `YYYY-MM` format.
              used:
                type: integer
                minimum: 0
                description: Cantidad de validaciones consumidas en ese mes.
                example: 120
                x-translations:
                  en:
                    description: Number of validations consumed in that month.
    DashboardSummary:
      type: object
      description: |
        Resumen JSON:API del panel de la cuenta, con métricas agregadas, progreso de configuración y validaciones recientes.
      x-translations:
        en:
          description: |
            JSON:API account dashboard summary with aggregate metrics, setup progress, and recent validations.
      properties:
        type:
          description: Tipo de recurso JSON:API. Siempre `dashboard_summary`.
          x-translations:
            en:
              description: JSON:API resource type. Always `dashboard_summary`.
          type: string
          enum:
            - dashboard_summary
          example: dashboard_summary
        attributes:
          type: object
          description: 'Datos agregados del panel del usuario: conteos, tasa de éxito y progreso del onboarding.'
          x-translations:
            en:
              description: 'Aggregated user dashboard data: counts, success rate, and onboarding progress.'
          properties:
            total_validations:
              type: integer
              minimum: 0
              description: Cantidad histórica de validaciones del usuario. Incluye las retiradas y las del banco de pruebas (Playground).
              x-translations:
                en:
                  description: Lifetime number of the user's validations. Includes withdrawn and playground validations.
              example: 1284
            monthly_validations:
              type: integer
              minimum: 0
              description: Cantidad de validaciones del mes calendario UTC en curso. Incluye las retiradas y las del banco de pruebas (Playground).
              x-translations:
                en:
                  description: Number of validations in the current UTC calendar month. Includes withdrawn and playground validations.
              example: 96
            pending_month:
              type: integer
              minimum: 0
              description: Cantidad de validaciones no retiradas del mes UTC en curso cuyo estado almacenado es `pending` o `processing`.
              x-translations:
                en:
                  description: Number of non-withdrawn validations in the current UTC month whose stored state is `pending` or `processing`.
              example: 3
            valid_validations:
              type: integer
              minimum: 0
              description: Cantidad histórica de validaciones del usuario con veredicto `valid`. Incluye las retiradas y las del banco de pruebas (Playground).
              x-translations:
                en:
                  description: Lifetime number of the user's validations with verdict `valid`. Includes withdrawn and playground validations.
              example: 1160
            avg_ticket:
              type: number
              minimum: 0
              description: Monto promedio por validación en MXN. Se calcula sobre toda la historia del usuario y sólo con montos mayores que `0`; excluye las validaciones del banco de pruebas (Playground) y las retiradas. Devuelve `0` cuando no hay ningún monto positivo.
              x-translations:
                en:
                  description: Average amount per validation in MXN. It is computed over the user's full history and only with amounts greater than `0`; it excludes playground and withdrawn validations. Returns `0` when there is no positive amount.
              example: 4302.32
            success_rate_percent:
              type: number
              minimum: 0
              maximum: 100
              description: 'Tasa de éxito, expresada de `0` a `100`: `valid_validations` entre `total_validations`, redondeada a un decimal. La población histórica es la misma e incluye las validaciones retiradas y las del banco de pruebas (Playground); devuelve `0` cuando el total es `0`.'
              x-translations:
                en:
                  description: 'Success rate from `0` to `100`: `valid_validations` divided by `total_validations`, rounded to one decimal place. It uses the same lifetime population, including withdrawn and playground validations; returns `0` when the total is `0`.'
              example: 0
            beneficiaries_count:
              type: integer
              minimum: 0
              description: Cantidad de beneficiarios activos del usuario.
              x-translations:
                en:
                  description: Count of the user's active beneficiaries.
              example: 12
            comenzar_milestones_completed:
              type: integer
              minimum: 0
              maximum: 4
              description: |
                Cantidad de hitos completados del wizard `/comenzar`: (1) existe al menos una validación OCR no retirada, (2) existe al menos una validación manual no retirada, (3) existe al menos un beneficiario activo y (4) se cumplen a la vez (1) y (2). Total máximo: `4`.
              x-translations:
                en:
                  description: |
                    Number of milestones completed in the `/comenzar` wizard: (1) at least one non-withdrawn OCR validation exists, (2) at least one non-withdrawn manual validation exists, (3) at least one active beneficiary exists, and (4) both (1) and (2) are met. Maximum: `4`.
              example: 0
        relationships:
          type: object
          description: Relaciones JSON:API del panel (referencias a recursos asociados).
          x-translations:
            en:
              description: Dashboard JSON:API relationships (references to associated resources).
          properties:
            recent_validations:
              type: object
              description: Relación JSON:API con las validaciones recientes mostradas en el panel.
              x-translations:
                en:
                  description: JSON:API relationship pointing to the recent validations shown in the dashboard.
              properties:
                data:
                  description: Validaciones recientes, cada una como recurso compacto.
                  x-translations:
                    en:
                      description: Recent validations, each as a compact resource.
                  type: array
                  items:
                    type: object
                    description: Referencia compacta a una validación reciente, optimizada para el panel.
                    x-translations:
                      en:
                        description: Compact reference to a recent validation, optimized for the dashboard view.
                    allOf:
                      - $ref: '#/components/schemas/JsonApiResourceBase'
                      - type: object
                        properties:
                          type:
                            description: Tipo de recurso JSON:API. Siempre `dashboard_validation`.
                            x-translations:
                              en:
                                description: JSON:API resource type. Always `dashboard_validation`.
                            type: string
                            enum:
                              - dashboard_validation
                            example: dashboard_validation
                          id:
                            description: Identificador único de la validación (UUID v4).
                            x-translations:
                              en:
                                description: Unique identifier of the validation (UUID v4).
                            type: string
                            format: uuid
                            example: a1b2c3d4-e5f6-7890-abcd-ef0123456789
                          attributes:
                            type: object
                            description: Datos resumidos de la validación reciente mostrados en el panel.
                            x-translations:
                              en:
                                description: Summary data of the recent validation shown in the dashboard.
                            properties:
                              bank_name:
                                description: Nombre del banco de la contraparte. Prioriza `receptor_name` o `emisor_name` guardado; si falta, intenta resolver el código de receptor o emisor. Si tampoco puede resolverlo, devuelve ese código o `Banco no identificado`.
                                x-translations:
                                  en:
                                    description: Counterparty bank name. It first uses the stored `receptor_name` or `emisor_name`; if absent, it tries to resolve the receiver or sender code. If resolution also fails, it returns that code or `Banco no identificado`.
                                type: string
                                example: BBVA MEXICO
                              beneficiary_label:
                                description: Etiqueta del beneficiario receptor registrado (si existe).
                                x-translations:
                                  en:
                                    description: Registered beneficiary label, when available.
                                type:
                                  - string
                                  - 'null'
                                example: Proveedor Norte
                              amount:
                                description: Importe de la transferencia (en pesos mexicanos — MXN).
                                x-translations:
                                  en:
                                    description: Transfer amount in Mexican pesos (MXN).
                                type:
                                  - number
                                  - 'null'
                                example: 15000.5
                              tracking_key:
                                description: Identificador asignado por el banco emisor de la transferencia (clave de rastreo).
                                x-translations:
                                  en:
                                    description: Identifier assigned by the sending bank of the transfer (tracking key).
                                type: string
                                example: MXBA20250315001234
                              referencia_numerica:
                                description: Serie numérica de hasta 7 dígitos que identifica la transferencia (no es único, puede repetirse).
                                x-translations:
                                  en:
                                    description: Numeric series of up to 7 digits that identifies the transfer. It is not unique and may repeat.
                                type: string
                                example: '1234567'
                              beneficiary_account:
                                description: Número de cuenta (CLABE, tarjeta o celular/DiMo) receptor de la transferencia.
                                x-translations:
                                  en:
                                    description: CLABE, card, or mobile number that received the transfer.
                                type: string
                                example: '012180004412345678'
                              status:
                                description: 'Estado del ciclo de vida — `queued`: Pendiente de procesamiento; `processing`: En proceso; `valid`: El CEP fue encontrado y cuadra; `not_found`: Banxico no encuentra la operación; `cep_unavailable`: Banxico reconoce la transacción pero el CEP no está disponible; `invalid`: Banxico no devolvió un veredicto reconocible; `returned`: la operación se liquidó y después se devolvió; `failed`: La validación se detuvo por un problema en los datos enviados; `error`: La validación se detuvo por un fallo del servicio.'
                                x-translations:
                                  en:
                                    description: 'Lifecycle state — `queued`: Awaiting processing; `processing`: In progress; `valid`: The CEP was found and matches; `not_found`: Banxico cannot find the operation; `cep_unavailable`: Banxico recognizes the transaction but the CEP is not available; `invalid`: Banxico did not return a recognizable verdict; `returned`: the transfer was settled and later returned; `failed`: The validation stopped because of a problem in the data sent; `error`: The validation stopped because of a service failure.'
                                type: string
                                enum:
                                  - queued
                                  - processing
                                  - valid
                                  - not_found
                                  - cep_unavailable
                                  - invalid
                                  - returned
                                  - failed
                                  - error
                                example: queued
                              banxico_status:
                                description: Veredicto reportado por Banxico. `pending` mientras aún no existe un veredicto; después puede ser `valid`, `not_found`, `cep_unavailable`, `invalid`, `returned` o `error`.
                                x-translations:
                                  en:
                                    description: Verdict reported by Banxico. It is `pending` while no verdict exists yet; afterwards it can be `valid`, `not_found`, `cep_unavailable`, `invalid`, `returned`, or `error`.
                                type: string
                                enum:
                                  - pending
                                  - valid
                                  - not_found
                                  - cep_unavailable
                                  - invalid
                                  - returned
                                  - error
                                example: valid
                              validation_type:
                                description: 'Modalidad de validación — `direct`: Captura manual de los campos; `ocr`: Lectura de la imagen del comprobante.'
                                x-translations:
                                  en:
                                    description: 'Validation mode — `direct`: Manual entry of the fields; `ocr`: Reading of the receipt image.'
                                type: string
                                enum:
                                  - direct
                                  - ocr
                                example: direct
                              created_at:
                                $ref: '#/components/schemas/TimestampUTC'
      required:
        - type
        - attributes
    RetryPolicyCaps:
      type: object
      description: Límites efectivos de reintentos del plan activo, con los valores globales cuando no hay una sobreescritura.
      x-translations:
        en:
          description: Effective retry limits of the active plan, with global values when there is no override.
      properties:
        max_retries_cap:
          type: integer
          minimum: 0
          description: Cantidad máxima de reintentos por validación.
          x-translations:
            en:
              description: Maximum number of retries per validation.
        min_interval_seconds:
          type: integer
          description: Intervalo mínimo entre reintentos, en segundos.
          x-translations:
            en:
              description: Minimum interval between retries, in seconds.
        max_interval_seconds:
          type: integer
          description: Intervalo máximo entre reintentos, en segundos.
          x-translations:
            en:
              description: Maximum interval between retries, in seconds.
        max_age_seconds:
          type: integer
          description: Antigüedad máxima de una validación reintentable, en segundos.
          x-translations:
            en:
              description: Maximum age of a retryable validation, in seconds.
        max_pending_per_user:
          type: integer
          description: Cantidad máxima de validaciones con reintentos pendientes por cuenta.
          x-translations:
            en:
              description: Maximum number of validations with pending retries per account.
        max_dispatched_per_day_per_user:
          type: integer
          description: Cantidad máxima de reintentos despachados por cuenta y día.
          x-translations:
            en:
              description: Maximum number of dispatched retries per account and day.
        max_reactivations:
          type: integer
          description: Cantidad máxima de reactivaciones por validación.
          x-translations:
            en:
              description: Maximum number of reactivations per validation.
        eligible_outcomes:
          type: array
          description: Veredictos que admiten reintentos automáticos.
          x-translations:
            en:
              description: Outcomes eligible for automatic retries.
          items:
            type: string
    UserRetryPolicyResponse:
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          properties:
            meta:
              type: object
              description: Metadatos de la respuesta y límites efectivos de reintentos.
              x-translations:
                en:
                  description: Response metadata and effective retry limits.
              properties:
                caps:
                  $ref: '#/components/schemas/RetryPolicyCaps'
            data:
              type: object
              description: Cuerpo de la respuesta con la política de reintentos guardada.
              x-translations:
                en:
                  description: Response body with the saved retry policy.
              properties:
                type:
                  type: string
                  enum:
                    - retry_policy
                attributes:
                  $ref: '#/components/schemas/RetryPolicy'
    UpdateUserRetryPolicyRequest:
      type: object
      description: Cuerpo de la petición para actualizar la política de reintentos por defecto del usuario (aplica a futuras validaciones).
      x-translations:
        en:
          description: Request body to update the user's default retry policy (applies to future validations).
      required:
        - retry_policy
      properties:
        retry_policy:
          $ref: '#/components/schemas/RetryPolicy'
    BanxicoPublicStatus:
      type: object
      description: Estado público del servicio de verificación de Banxico, tal como lo devuelve `GET /v1/status/banxico`. Muestra el estado operativo actual y una explicación legible.
      x-translations:
        en:
          description: Public status of the Banxico validation service, as returned by `GET /v1/status/banxico`. It shows the current operational status and a human-readable explanation.
      required:
        - status
        - status_label
        - message
        - last_verified_at
      properties:
        status:
          type: string
          enum:
            - operational
            - degraded
            - down
            - unknown
          description: 'Estado agregado del servicio de verificación de Banxico — `operational`: Todas las pruebas pasan y la latencia es normal; `degraded`: Problemas parciales (latencia elevada o errores intermitentes); `down`: Servicio inaccesible; `unknown`: no hay datos de salud vigentes, porque aún no se ha ejecutado ningún chequeo o porque el último tiene más de tres veces `check_interval_seconds` de antigüedad (el chequeo automático dejó de correr).'
          example: operational
          x-translations:
            en:
              description: 'Aggregated service status — `operational`: all checks pass and latency is normal; `degraded`: partial issues (high latency or intermittent errors); `down`: service unreachable; `unknown`: no current health data, because no check has run yet or because the last one is more than three times `check_interval_seconds` old (the automated check stopped running).'
        status_label:
          type: string
          description: Etiqueta legible del estado actual (resuelta según idioma de la llamada).
          example: Operativo
          x-translations:
            en:
              description: Human-readable status label, resolved to the request locale.
        message:
          type: string
          description: Explicación corta del estado actual del servicio de verificación de Banxico (resuelta según idioma de la llamada).
          example: El servicio de verificación está operando normalmente.
          x-translations:
            en:
              description: Short explanation of the current service status, resolved to the request locale.
        last_verified_at:
          oneOf:
            - $ref: '#/components/schemas/TimestampUTC'
            - type: 'null'
          description: 'Timestamp del último chequeo de salud ejecutado. `null` cuando aún no se ha ejecutado ningún chequeo. Cuando `status` es `unknown` por un chequeo demasiado viejo, conserva la fecha de ese chequeo: dice desde cuándo no hay dato.'
          example: '2026-04-11T15:30:00Z'
          x-translations:
            en:
              description: 'Timestamp of the last health check executed. `null` when no check has been run yet. When `status` is `unknown` because the last check is too old, it keeps that check''s date: it says since when there is no data.'
        estimated_recovery_at:
          oneOf:
            - $ref: '#/components/schemas/TimestampUTC'
            - type: 'null'
          description: Tiempo estimado de recuperación del servicio de verificación de Banxico. Solo se establece para cierto tipo de degradación transitoria con una ventana de recuperación conocida; `null` en cualquier otro caso, incluidos los demás tipos de `degraded`.
          example: null
          x-translations:
            en:
              description: Estimated service recovery time. Only set for a specific kind of transient degradation with a known recovery window; `null` in every other case, including other kinds of `degraded`.
        check_interval_seconds:
          type: integer
          minimum: 1
          description: Cadencia (en segundos) del chequeo de salud automático que alimenta este estado. Se lee en vivo de la tarea programada, así que un cambio surte efecto sin desplegar; cuando no hay ninguna cadencia configurada, son `300`.
          example: 300
          x-translations:
            en:
              description: Cadence (in seconds) of the automated health check that feeds this status. It is read live from the configured schedule, so a change takes effect without a deploy; when no cadence is configured, it is `300`.
    BanxicoPublicStatusResource:
      type: object
      description: Recurso JSON:API con la lectura actual del estado público.
      x-translations:
        en:
          description: JSON:API resource carrying the current public status snapshot.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              enum:
                - banxico_status
              description: Tipo del recurso JSON:API. Siempre `banxico_status`.
              example: banxico_status
              x-translations:
                en:
                  description: JSON:API resource type. Always `banxico_status`.
            id:
              type: string
              enum:
                - current
              description: Identificador fijo. Siempre `current`.
              example: current
              x-translations:
                en:
                  description: Fixed `current` identifier (the current status entry).
            attributes:
              $ref: '#/components/schemas/BanxicoPublicStatus'
    BanxicoPublicStatusResponse:
      type: object
      description: |
        Respuesta de `GET /v1/status/banxico`. Vista pública reducida del estado del servicio de verificación de Banxico, sin detalles internos. Cacheada 60s (por `Cache-Control` en la respuesta) — pensada para health badges en dashboards de clientes.
      x-translations:
        en:
          description: |
            Response from `GET /v1/status/banxico`. Public reduced view of the Banxico validation service status, with no internal details. Cached 60s (via `Cache-Control` header in the response) — designed for health badges in customer dashboards.
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico del estado público del servicio de verificación de Banxico.
          x-translations:
            en:
              description: Schema body specific to the public Banxico validation service status.
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/BanxicoPublicStatusResource'
    BanxicoPublicTimeseries:
      type: object
      description: Serie temporal de métricas de salud del servicio de verificación de Banxico, agrupada por intervalos de tiempo, tal como la devuelve `GET /v1/status/banxico/timeseries`.
      x-translations:
        en:
          description: Time series of Banxico validation service health metrics, bucketed by time interval, as returned by `GET /v1/status/banxico/timeseries`.
      properties:
        metric:
          type: string
          enum:
            - probe_latency
            - verdict
          description: 'Métrica reportada — `probe_latency`: Promedio de latencia de las pruebas (en milisegundos); `verdict`: Veredicto Banxico del grupo (codificado como número).'
          example: probe_latency
          x-translations:
            en:
              description: 'Reported metric — `probe_latency`: average health probe latency in milliseconds; `verdict`: the bucket''s Banxico verdict, encoded as a number.'
        window:
          type: string
          enum:
            - 1h
            - 8h
            - 12h
            - 24h
            - 7d
          description: Ventana de tiempo total cubierta por la serie (`1h`, `8h`, `12h`, `24h` o `7d`).
          example: 24h
          x-translations:
            en:
              description: Total time window covered by the series (`1h`, `8h`, `12h`, `24h`, or `7d`).
        unit:
          type: string
          description: 'Unidad de los valores — `ms`: Milisegundos, cuando `metric` es `probe_latency`; `verdict`: Código categórico y no una magnitud, cuando `metric` es `verdict`.'
          example: ms
          x-translations:
            en:
              description: 'Unit of the values — `ms`: milliseconds, when `metric` is `probe_latency`; `verdict`: a categorical code rather than a magnitude, when `metric` is `verdict`.'
        bucket_size_minutes:
          type: integer
          minimum: 1
          description: 'Tamaño de cada intervalo en minutos — `1`: Para la ventana `1h`; `60`: Para las ventanas `8h`, `12h`, `24h` y `7d`.'
          example: 60
          x-translations:
            en:
              description: 'Size of each interval in minutes — `1`: For the `1h` window; `60`: For the `8h`, `12h`, `24h` and `7d` windows.'
        points:
          type: array
          description: Puntos de datos ordenados cronológicamente. Cada punto contiene `ts`, la marca de tiempo de inicio del intervalo, y `value`, que es un número en milisegundos para `probe_latency` y un código de veredicto para `verdict`.
          x-translations:
            en:
              description: Data points in chronological order. Each point contains `ts` (bucket start timestamp) and `value` (number in ms for `probe_latency`; verdict code for `verdict`).
          items:
            type: object
            description: Punto individual de la serie temporal (par `ts`/`value`).
            x-translations:
              en:
                description: Single timeseries data point (`ts`/`value` pair).
            properties:
              ts:
                $ref: '#/components/schemas/TimestampUTC'
              value:
                description: Valor de la métrica en el grupo — número en milisegundos (promedio del grupo) para `probe_latency`; código de veredicto `0`/`1`/`2` (`2`=up, `1`=degraded, `0`=down/unknown, tomado del chequeo más reciente del grupo) para `verdict`. `null` en grupos sin lecturas de esa métrica.
                x-translations:
                  en:
                    description: Metric value for the bucket — number in milliseconds (bucket average) for `probe_latency`; verdict code `0`/`1`/`2` (`2`=up, `1`=degraded, `0`=down/unknown, taken from the bucket's most recent check) for `verdict`. `null` for buckets with no readings for that metric.
                type:
                  - number
                  - 'null'
                example: 182.5
    BanxicoPublicTimeseriesResource:
      type: object
      description: Recurso JSON:API con la serie temporal del par métrica/ventana solicitado.
      x-translations:
        en:
          description: JSON:API resource carrying the timeseries for the requested metric/window pair.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              enum:
                - banxico_public_timeseries
              description: Tipo del recurso JSON:API. Siempre `banxico_public_timeseries`.
              example: banxico_public_timeseries
              x-translations:
                en:
                  description: JSON:API resource type. Always `banxico_public_timeseries`.
            id:
              type: string
              description: Identificador derivado del par métrica/ventana (`<metric>_<window>`).
              example: probe_latency_24h
              x-translations:
                en:
                  description: Identifier derived from the metric/window pair (`<metric>_<window>`).
            attributes:
              $ref: '#/components/schemas/BanxicoPublicTimeseries'
    BanxicoPublicTimeseriesResponse:
      type: object
      description: |
        Respuesta de `GET /v1/status/banxico/timeseries`. Puntos de datos agrupados por grupo de tiempo para la métrica y ventana solicitadas. Solo expone métricas seguras (`probe_latency`, `verdict`); valores de query inválidos caen silenciosamente a los defaults (`probe_latency`, `24h`).
      x-translations:
        en:
          description: |
            Response from `GET /v1/status/banxico/timeseries`. Data points bucketed by time interval for the requested metric and window. Only exposes safe metrics (`probe_latency`, `verdict`); invalid query values silently fall back to defaults (`probe_latency`, `24h`).
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          description: Cuerpo específico de la serie temporal pública del servicio de verificación de Banxico.
          x-translations:
            en:
              description: Schema body specific to the public Banxico validation service time series.
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/BanxicoPublicTimeseriesResource'
    WebhookEndpoint:
      type: object
      description: |
        Recurso JSON:API de un endpoint webhook, con URL receptora, eventos suscritos, estado y representación del secreto.
      x-translations:
        en:
          description: |
            JSON:API resource for a webhook endpoint, with receiver URL, subscribed events, status, and secret representation.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          properties:
            type:
              description: Tipo de recurso JSON:API. Siempre `webhook_endpoint`.
              x-translations:
                en:
                  description: JSON:API resource type. Always `webhook_endpoint`.
              type: string
              enum:
                - webhook_endpoint
              example: webhook_endpoint
            id:
              type: string
              format: uuid
              description: Identificador único del endpoint webhook (UUID v4).
              x-translations:
                en:
                  description: Unique identifier of the webhook endpoint (UUID v4).
              example: a1b2c3d4-e5f6-7890-abcd-ef0123456789
            attributes:
              type: object
              description: Datos del endpoint webhook (URL receptora, eventos suscritos, estado y secreto).
              x-translations:
                en:
                  description: Webhook endpoint data (receiver URL, subscribed events, status, and secret).
              properties:
                url:
                  type: string
                  format: uri
                  maxLength: 2048
                  description: URL receptora del webhook. En producción se rechaza si no usa HTTPS, si resuelve a una dirección privada o si pasa de 2048 caracteres.
                  x-translations:
                    en:
                      description: |
                        URL that receives the webhook. In production it is rejected if it does not use HTTPS, if it resolves to a private address, or if it is longer than 2048 characters.
                  example: https://example.com/webhooks/entregas
                events:
                  type: array
                  minItems: 1
                  maxItems: 10
                  uniqueItems: true
                  description: Eventos a los que se suscribe el endpoint webhook. Entre 1 y 10.
                  x-translations:
                    en:
                      description: Events the endpoint subscribes to, from 1 to 10.
                  items:
                    type: string
                    enum:
                      - validation.completed
                      - validation.failed
                      - validation.error
                      - validation.retry.scheduled
                      - validation.retry.resolved
                      - validation.retry.exhausted
                      - validation.returned
                      - billing.payment_succeeded
                      - billing.payment_failed
                      - billing.trial_will_end
                      - billing.subscription_canceled
                      - billing.invoice_upcoming
                description:
                  type:
                    - string
                    - 'null'
                  maxLength: 255
                  description: Etiqueta libre para distinguir el endpoint webhook de los demás.
                  x-translations:
                    en:
                      description: Free-form label to tell this endpoint apart from the others.
                  example: Alta de pagos en el ERP
                status:
                  type: string
                  enum:
                    - active
                    - disabled
                    - auto_disabled
                  description: 'Estado actual del webhook — `active`: Recibe entregas; `disabled`: Se desactivó manualmente; `auto_disabled`: Se desactivó automáticamente tras superar el umbral de fallos consecutivos.'
                  x-translations:
                    en:
                      description: |
                        `active` receives deliveries. `disabled` was manually turned off. `auto_disabled` was turned off by the platform after exceeding the consecutive-failure threshold.
                  example: active
                consecutive_failures:
                  type: integer
                  minimum: 0
                  description: Cantidad de fallos consecutivos. Se reinicia al primer éxito.
                  x-translations:
                    en:
                      description: Number of consecutive failures. Resets on the first success.
                  example: 0
                last_delivery_at:
                  oneOf:
                    - $ref: '#/components/schemas/TimestampUTC'
                    - type: 'null'
                  description: |
                    Fecha y hora de la última entrega exitosa, en ISO 8601 UTC. `null` cuando el endpoint aún no completó ninguna entrega.
                  x-translations:
                    en:
                      description: |
                        Date and time of the last successful delivery, in ISO 8601 UTC. `null` when the endpoint has not completed a delivery yet.
                secret:
                  type: string
                  pattern: ^[a-f0-9]{64}$
                  description: |
                    Clave compartida para verificar firmas. **Solo presente** en la respuesta de creación y de rotación del endpoint; se omite en cualquier otra respuesta. Se usa para validar las cabeceras `X-Webhook-Signature` y `X-Webhook-Signature-Timestamped` de cada entrega.
                  x-translations:
                    en:
                      description: |
                        Shared secret for signature verification. **Only present** in the create and rotate-secret responses; omitted from every other response. It is used to validate the `X-Webhook-Signature` and `X-Webhook-Signature-Timestamped` headers on each delivery.
                  example: a0b1c2d3e4f5061728394a5b6c7d8e9f00112233445566778899aabbccddeeff
                secret_hint:
                  type: string
                  pattern: ^\.\.\.[a-f0-9]{4}$
                  description: |
                    Últimos 4 caracteres del secret, con el prefijo `...`. Presente en cualquier respuesta donde el `secret` completo está oculto.
                  example: ...4f2a
                  x-translations:
                    en:
                      description: |
                        Last 4 characters of the secret, prefixed with `...`. Present on any response where the full `secret` is hidden.
                created_at:
                  $ref: '#/components/schemas/TimestampUTC'
                updated_at:
                  $ref: '#/components/schemas/TimestampUTC'
              required:
                - url
                - events
                - description
                - status
                - consecutive_failures
                - last_delivery_at
                - created_at
                - updated_at
              oneOf:
                - required:
                    - secret
                - required:
                    - secret_hint
          required:
            - type
            - id
            - attributes
    CreateWebhookRequest:
      type: object
      description: Cuerpo de `POST /v1/webhooks` con la URL HTTPS receptora, los eventos suscritos y una etiqueta opcional.
      x-translations:
        en:
          description: Body for `POST /v1/webhooks` with the receiving HTTPS URL, subscribed events, and an optional label.
      required:
        - url
        - events
      properties:
        url:
          type: string
          format: uri
          maxLength: 2048
          description: URL receptora del webhook. En producción se rechaza si no usa HTTPS, si resuelve a una dirección privada o si pasa de 2048 caracteres.
          x-translations:
            en:
              description: URL that receives the webhook. In production it is rejected if it does not use HTTPS, if it resolves to a private address, or if it is longer than 2048 characters.
          example: https://example.com/webhooks/entregas
        events:
          type: array
          minItems: 1
          maxItems: 10
          items:
            type: string
            enum:
              - validation.completed
              - validation.failed
              - validation.error
              - validation.retry.scheduled
              - validation.retry.resolved
              - validation.retry.exhausted
              - validation.returned
              - billing.payment_succeeded
              - billing.payment_failed
              - billing.trial_will_end
              - billing.subscription_canceled
              - billing.invoice_upcoming
          description: Eventos a los que se suscribe el endpoint webhook. Entre 1 y 10.
          x-translations:
            en:
              description: Events the endpoint subscribes to, from 1 to 10.
        description:
          type:
            - string
            - 'null'
          maxLength: 255
          description: Etiqueta libre para distinguir el endpoint webhook de los demás. `null` la omite; más de 255 caracteres responde `422 webhook_description_too_long`.
          x-translations:
            en:
              description: Free-form label to tell this endpoint apart from the others. `null` omits it; more than 255 characters responds `422 webhook_description_too_long`.
          example: Alta de pagos en el ERP
    UpdateWebhookRequest:
      type: object
      description: Cuerpo de `PUT /v1/webhooks/{id}` con los campos del endpoint que deben actualizarse.
      properties:
        url:
          type: string
          format: uri
          maxLength: 2048
          description: Nueva URL receptora del webhook. En producción se rechaza si no usa HTTPS, si no resuelve a una dirección pública o si supera 2048 caracteres.
          x-translations:
            en:
              description: New receiver URL. In production it is rejected if it does not use HTTPS, if it does not resolve to a public address, or if it is longer than 2048 characters.
          example: https://example.com/webhooks/entregas
        events:
          type: array
          minItems: 1
          maxItems: 10
          description: Nuevos eventos a los que se suscribe el endpoint webhook. Sustituyen a la lista anterior. Entre 1 y 10.
          x-translations:
            en:
              description: New events the endpoint subscribes to. They replace the previous list. Between 1 and 10.
          items:
            type: string
            enum:
              - validation.completed
              - validation.failed
              - validation.error
              - validation.retry.scheduled
              - validation.retry.resolved
              - validation.retry.exhausted
              - validation.returned
              - billing.payment_succeeded
              - billing.payment_failed
              - billing.trial_will_end
              - billing.subscription_canceled
              - billing.invoice_upcoming
        description:
          type:
            - string
            - 'null'
          maxLength: 255
          description: Nueva etiqueta libre para distinguir el endpoint webhook de los demás. `null` la elimina; más de 255 caracteres responde `422 webhook_description_too_long`.
          x-translations:
            en:
              description: New free-form label to tell this endpoint apart from the others. `null` clears it; more than 255 characters responds `422 webhook_description_too_long`.
          example: Alta de pagos en el ERP
        status:
          type: string
          enum:
            - active
            - disabled
          description: Nuevo estado del webhook. `active` recibe entregas; `disabled` las detiene.
          x-translations:
            en:
              description: Webhook status. `active` receives deliveries; `disabled` stops them.
          example: active
      x-translations:
        en:
          description: Body for `PUT /v1/webhooks/{id}` with the endpoint fields to update.
    SendWebhookTestAttributes:
      type: object
      description: 'Resultado de una entrega de prueba: respuesta del receptor, latencia o error.'
      properties:
        delivered:
          type: boolean
          description: '`true` cuando el endpoint webhook receptor respondió con un estado HTTP `2xx` y dentro del tiempo de espera. `false` cuando falla la validación URL, el DNS no resuelve, se agotó el tiempo de espera o respondió con un código fuera del rango `2xx`.'
          x-translations:
            en:
              description: '`true` when the receiver answered with an HTTP `2xx` status within the timeout. `false` when SSRF re-validation fails, DNS does not resolve, the receiver times out, or it answers with a non-`2xx` status code.'
          example: true
        http_status:
          type: integer
          description: 'Código de estado HTTP con el que respondió el endpoint webhook receptor. `0` cuando no hubo respuesta HTTP: DNS, tiempo agotado, o el fallo de la re-validación SSRF antes de la conexión.'
          x-translations:
            en:
              description: HTTP status code the receiver answered with. `0` when no HTTP response was received (DNS, timeout, or SSRF re-validation failure before the connection).
          example: 200
        response_time_ms:
          type: integer
          minimum: 0
          description: Duración del intento de entrega, en milisegundos. Incluye DNS, TLS y el viaje de ida y vuelta.
          x-translations:
            en:
              description: Duration of the delivery attempt, in milliseconds. Includes DNS, TLS, and the roundtrip.
          example: 184
        error:
          type:
            - string
            - 'null'
          description: Detalle del fallo de transporte o de validación de URL. Puede ser `null` aunque `delivered=false` si el receptor respondió con un estado fuera de `2xx` pero la petición se completó.
          x-translations:
            en:
              description: Transport or URL-validation failure detail. It can be `null` even when `delivered=false` if the receiver returned a non-`2xx` status but the request completed.
          example: Connection timed out after 10s
      required:
        - delivered
        - http_status
        - response_time_ms
        - error
      x-translations:
        en:
          description: 'Test-delivery result: receiver response, latency, or error.'
    WebhookDelivery:
      type: object
      description: |
        Recurso JSON:API de una entrega de webhook, con receptor, intento, respuesta y estado.
      x-translations:
        en:
          description: |
            JSON:API resource for a webhook delivery, with receiver, attempt, response, and status.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          properties:
            type:
              description: Tipo de recurso JSON:API. Siempre `webhook_delivery`.
              x-translations:
                en:
                  description: JSON:API resource type. Always `webhook_delivery`.
              type: string
              enum:
                - webhook_delivery
              example: webhook_delivery
            id:
              type: string
              pattern: ^[0-9]+$
              description: Identificador autoincremental de la entrega.
              x-translations:
                en:
                  description: Auto-increment delivery identifier.
              example: '48211'
            attributes:
              type: object
              description: Datos de la entrega webhook (endpoint receptor, intento, respuesta y estado).
              x-translations:
                en:
                  description: Webhook delivery data (receiver endpoint, attempt, response, and status).
              properties:
                endpoint_id:
                  type: string
                  format: uuid
                  description: Identificador único del endpoint webhook (UUID v4) que originó esta entrega.
                  x-translations:
                    en:
                      description: Unique identifier of the webhook endpoint (UUID v4) that originated this delivery.
                  example: a1b2c3d4-e5f6-7890-abcd-ef0123456789
                endpoint_url:
                  type: string
                  description: URL receptora del webhook (endpoint). Solo presente en el listado global (`GET /v1/webhooks/deliveries`).
                  x-translations:
                    en:
                      description: |
                        Receiver endpoint URL. Only present on the cross-endpoint list (`GET /v1/webhooks/deliveries`).
                  example: https://erp.example.com/hooks/pagos
                event_type:
                  type: string
                  description: Tipo de evento que originó la entrega. Determina la forma del cuerpo firmado con HMAC que recibe el receptor. Las entregas sintéticas (envío de prueba) se marcan como `test`.
                  x-translations:
                    en:
                      description: |
                        Event type that produced the delivery. Determines the shape of the HMAC-signed body the receiver gets. `test` marks the synthetic deliveries from the test send.
                  enum:
                    - validation.completed
                    - validation.failed
                    - validation.error
                    - validation.retry.scheduled
                    - validation.retry.resolved
                    - validation.retry.exhausted
                    - validation.returned
                    - billing.payment_succeeded
                    - billing.payment_failed
                    - billing.trial_will_end
                    - billing.subscription_canceled
                    - billing.invoice_upcoming
                    - test
                  example: validation.completed
                validation_id:
                  type:
                    - string
                    - 'null'
                  format: uuid
                  description: Identificador único de la validación (UUID v4) asociada cuando el evento es `validation.*`. `null` cuando el evento es otro.
                  x-translations:
                    en:
                      description: |
                        Unique identifier of the associated validation (UUID v4) when the event is `validation.*`. `null` for any other event.
                  example: a1b2c3d4-e5f6-7890-abcd-ef0123456789
                response_status:
                  type:
                    - integer
                    - 'null'
                  minimum: 100
                  maximum: 599
                  description: Código de estado HTTP con el que respondió el endpoint webhook receptor. `null` cuando la entrega no llegó a establecer respuesta (tiempo de espera agotado o bloqueo).
                  x-translations:
                    en:
                      description: |
                        HTTP status code the receiver answered with. `null` when the delivery never completed a request (timeout or SSRF block).
                  example: 200
                response_body:
                  type:
                    - string
                    - 'null'
                  maxLength: 500
                  description: Cuerpo de la respuesta del endpoint webhook receptor, truncado a 500 caracteres.
                  x-translations:
                    en:
                      description: Receiver response body, truncated to 500 characters.
                  example: '{"ok":true}'
                response_time_ms:
                  type:
                    - integer
                    - 'null'
                  minimum: 0
                  description: Duración de la petición de entrega (en milisegundos).
                  x-translations:
                    en:
                      description: Delivery request duration, in milliseconds.
                  example: 184
                attempt:
                  type: integer
                  minimum: 1
                  description: Número del intento. `1` es el primer envío; los mayores, reintentos.
                  x-translations:
                    en:
                      description: Attempt number. `1` is the first send; higher values are retries.
                  example: 1
                status:
                  type: string
                  enum:
                    - pending
                    - retrying
                    - success
                    - failed
                  description: 'Estado actual de la entrega — `pending`: Aún sin entregar; `retrying`: Se pospuso por el límite de salida y Messenger la reintentará; `success`: Entregada; `failed`: El último intento falló; Messenger puede programar otro si el fallo es recuperable.'
                  x-translations:
                    en:
                      description: 'Current delivery status. `pending`: Not delivered yet; `retrying`: Deferred by the outbound limit and Messenger will retry it; `success`: Delivered; `failed`: The latest attempt failed; Messenger can schedule another one for a recoverable failure.'
                  example: pending
                next_retry_at:
                  oneOf:
                    - $ref: '#/components/schemas/TimestampUTC'
                    - type: 'null'
                  description: |
                    Fecha y hora del próximo reintento persistido, en ISO 8601 UTC. `null` cuando no hay una fecha almacenada; los reintentos que programa Messenger no persisten esta fecha en la entrega.
                  x-translations:
                    en:
                      description: |
                        Date and time of the next persisted retry, in ISO 8601 UTC. `null` when no date is stored; retries scheduled by Messenger do not persist this date on the delivery.
                error_message:
                  type:
                    - string
                    - 'null'
                  description: Detalle de error del transporte o del bloqueo de la entrega. `null` cuando no hay un detalle de error, incluso si el receptor respondió con un estado fuera de `2xx`.
                  x-translations:
                    en:
                      description: Transport or delivery-block error detail. `null` when no error detail is available, including when the receiver answered with a non-`2xx` status.
                  example: Connection timed out after 10s
                created_at:
                  $ref: '#/components/schemas/TimestampUTC'
              required:
                - endpoint_id
                - event_type
                - validation_id
                - response_status
                - response_body
                - response_time_ms
                - attempt
                - status
                - next_retry_at
                - error_message
                - created_at
          required:
            - type
            - id
            - attributes
    PlanFeature:
      type: object
      required:
        - text_es
        - text_en
        - icon
        - sort_order
        - is_active
      description: Característica bilingüe de un plan, con indicador visual, orden y estado.
      x-translations:
        en:
          description: Bilingual plan feature with a visual indicator, order, and status.
      properties:
        text_es:
          type: string
          maxLength: 255
          description: Texto en español que la interfaz muestra para la característica.
          example: 500 validaciones al mes
          x-translations:
            en:
              description: Spanish text shown by the interface for the feature.
        text_en:
          type: string
          maxLength: 255
          description: Texto en inglés que la interfaz muestra para la característica.
          example: 500 validations per month
          x-translations:
            en:
              description: English text shown by the interface for the feature.
        icon:
          type: string
          enum:
            - check
            - cross
          description: 'Indicador visual — `check`: Incluido en el plan, con una marca de éxito; `cross`: No incluido o no disponible, con una marca atenuada.'
          example: check
          x-translations:
            en:
              description: 'Visual indicator — `check`: Included in the plan, with a success mark; `cross`: Not included or unavailable, with a muted mark.'
        sort_order:
          type: integer
          minimum: 0
          description: Posición de la característica dentro del plan. El servidor normaliza el valor a múltiplos de `10` al guardarla desde administración.
          example: 10
          x-translations:
            en:
              description: Feature position within the plan. The server normalizes the value to multiples of `10` when it is saved through administration.
        is_active:
          type: boolean
          description: '`true` cuando la característica está activa. Los catálogos para usuarios sólo incluyen las activas; administración devuelve todas.'
          example: true
          x-translations:
            en:
              description: '`true` when the feature is active. User-facing catalogs include only active features; administration returns all of them.'
    PlanPriceAttributes:
      type: object
      required:
        - currency
        - billing_interval
        - kind
        - unit_amount
        - tax_behavior
      description: 'Atributos públicos de un precio de plan: moneda, intervalo, tipo, importe unitario y tratamiento fiscal.'
      x-translations:
        en:
          description: 'Public plan-price attributes: currency, interval, kind, unit amount, and tax treatment.'
      properties:
        currency:
          type: string
          enum:
            - MXN
            - USD
          description: 'Moneda del precio — `MXN`: Pesos mexicanos; `USD`: Dólares estadounidenses.'
          example: MXN
          x-translations:
            en:
              description: 'Price currency — `MXN`: Mexican pesos; `USD`: US dollars.'
        billing_interval:
          type: string
          enum:
            - month
            - year
          description: 'Cadencia de facturación — `month`: Cada mes; `year`: Cada año.'
          example: month
          x-translations:
            en:
              description: 'Billing cadence — `month`: Monthly; `year`: Yearly.'
        kind:
          type: string
          enum:
            - base
            - metered_overage
          description: 'Tipo de precio — `base`: Cuota fija del ciclo; `metered_overage`: Cargo por unidad consumida por encima de las incluidas en el plan.'
          example: base
          x-translations:
            en:
              description: 'Price kind — `base`: Flat cycle fee; `metered_overage`: Per-unit charge above the units included in the plan.'
        unit_amount:
          type:
            - integer
            - 'null'
          minimum: 0
          description: Importe en la unidad mínima de la moneda. `null` cuando Stripe calcula un precio escalonado al facturar y no puede mostrarse como una sola cifra.
          example: 49900
          x-translations:
            en:
              description: Amount in the currency's minor unit. `null` when Stripe calculates a tiered price at billing time and it cannot be shown as a single amount.
        tax_behavior:
          type: string
          enum:
            - inclusive
            - exclusive
          description: 'Tratamiento del impuesto en `unit_amount` — `inclusive`: Ya incluido; `exclusive`: Se agrega al cobro.'
          example: inclusive
          x-translations:
            en:
              description: 'Tax treatment in `unit_amount` — `inclusive`: Already included; `exclusive`: Added to the charge.'
    PublicPlanResource:
      type: object
      description: Plan del catálogo público con sus precios y características activas.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              description: Tipo de recurso JSON:API. Siempre `plan`.
              x-translations:
                en:
                  description: JSON:API resource type. Always `plan`.
              type: string
              enum:
                - plan
              example: plan
            id:
              type: string
              description: Identificador único y legible del plan.
              x-translations:
                en:
                  description: Unique, human-readable plan identifier.
              example: pro
            attributes:
              description: Datos del recurso, separados de su identidad.
              x-translations:
                en:
                  description: The resource's own data, kept apart from its identity.
              type: object
              required:
                - slug
                - name
                - billing_model
                - monthly_validation_limit
                - included_validations
                - trial_days
                - is_default
                - sort_order
                - prices
                - features
              properties:
                slug:
                  type: string
                  description: Identificador único y legible del plan. Tiene el mismo valor que `id`.
                  x-translations:
                    en:
                      description: Unique, human-readable plan identifier. It has the same value as `id`.
                  example: pro
                name:
                  type: string
                  description: Nombre del plan que muestran las interfaces.
                  x-translations:
                    en:
                      description: Plan name shown in the interfaces.
                  example: Pro
                billing_model:
                  type: string
                  description: 'Modelo de facturación del plan — `free`: Sin cobro; `tiered`: Cuota fija; `metered`: Cobro por consumo; `hybrid`: Cuota fija más consumo.'
                  x-translations:
                    en:
                      description: 'Plan billing model — `free`: No charge; `tiered`: Flat fee; `metered`: Usage-based charge; `hybrid`: Flat fee plus usage.'
                  enum:
                    - free
                    - tiered
                    - metered
                    - hybrid
                  example: free
                monthly_validation_limit:
                  type: integer
                  description: Tope de validaciones por ciclo de la suscripción. `0` no permite validaciones.
                  x-translations:
                    en:
                      description: Validation cap per subscription cycle. `0` allows no validations.
                  minimum: 0
                  example: 5000
                included_validations:
                  type:
                    - integer
                    - 'null'
                  description: Validaciones incluidas en la cuota base antes del cobro por excedentes. `null` cuando el plan no tiene cuota base.
                  x-translations:
                    en:
                      description: Validations included in the base quota before overage charges. `null` when the plan has no base quota.
                  minimum: 0
                  example: 5000
                trial_days:
                  type:
                    - integer
                    - 'null'
                  description: Días de prueba gratuita. `null` o `0` cuando el plan no ofrece prueba gratuita.
                  x-translations:
                    en:
                      description: Free trial days. `null` or `0` when the plan offers no free trial.
                  minimum: 0
                  example: 14
                is_default:
                  type: boolean
                  description: '`true` cuando es el plan asignado por defecto a las cuentas nuevas.'
                  x-translations:
                    en:
                      description: '`true` when this is the default plan assigned to new accounts.'
                  example: true
                sort_order:
                  type: integer
                  description: Posición de ordenamiento para listar el plan dentro del catálogo (ascendente).
                  x-translations:
                    en:
                      description: Sort position used to order the plan within the catalog (ascending).
                  example: 20
                prices:
                  type: array
                  description: Precios activos del plan, uno por combinación de moneda, intervalo de facturación y modalidad.
                  x-translations:
                    en:
                      description: Active plan prices, one per currency, billing interval, and pricing-kind combination.
                  items:
                    $ref: '#/components/schemas/PlanPriceAttributes'
                features:
                  type: array
                  description: Características bilingües activas del plan, ordenadas por `sort_order`.
                  x-translations:
                    en:
                      description: Active bilingual plan features, ordered by `sort_order`.
                  items:
                    $ref: '#/components/schemas/PlanFeature'
      x-translations:
        en:
          description: Public-catalog plan with its active prices and features.
    ListPublicPlansResponse:
      type: object
      description: |
        Respuesta de `GET /v1/plans/public` con los planes activos del catálogo público, sus precios y características.
      x-translations:
        en:
          description: |
            Response from `GET /v1/plans/public` with the active plans in the public catalog, their prices, and features.
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          required:
            - data
          properties:
            data:
              type: array
              description: Lista de planes públicos.
              x-translations:
                en:
                  description: List of public plans.
              items:
                $ref: '#/components/schemas/PublicPlanResource'
    PlanComparisonResource:
      type: object
      description: Recurso JSON:API de la comparativa, con planes por columna y características por fila.
      required:
        - type
        - attributes
      properties:
        type:
          description: Tipo de recurso JSON:API. Siempre `plan_comparison`.
          x-translations:
            en:
              description: JSON:API resource type. Always `plan_comparison`.
          type: string
          enum:
            - plan_comparison
          example: plan_comparison
        attributes:
          description: Datos del recurso, separados de su identidad.
          x-translations:
            en:
              description: The resource's own data, kept apart from its identity.
          type: object
          required:
            - plans
            - rows
          properties:
            plans:
              type: array
              description: Columnas, en el orden del catálogo público.
              x-translations:
                en:
                  description: Columns, in public-catalog order.
              items:
                type: object
                required:
                  - slug
                  - name
                  - is_default
                properties:
                  slug:
                    description: Identificador del plan en la URL y en el resto de la interfaz.
                    x-translations:
                      en:
                        description: Plan identifier used in the URL and across the interface.
                    type: string
                    example: basic
                  name:
                    description: Nombre del plan que encabeza su columna.
                    x-translations:
                      en:
                        description: Plan name shown as the column heading.
                    type: string
                    example: Basic
                  is_default:
                    description: '`true` cuando es el plan asignado por defecto a las cuentas nuevas.'
                    x-translations:
                      en:
                        description: '`true` when this is the plan assigned by default to new accounts.'
                    type: boolean
                    example: true
            rows:
              type: array
              description: Dimensiones de la comparativa, en el orden configurado.
              x-translations:
                en:
                  description: Comparison dimensions, in their configured order.
              items:
                type: object
                required:
                  - key
                  - label_es
                  - label_en
                  - cells
                properties:
                  key:
                    description: Identificador de la fila de la comparativa, estable entre idiomas.
                    x-translations:
                      en:
                        description: Identifier of the comparison row, stable across languages.
                    type: string
                    example: validations_included
                  label_es:
                    description: Etiqueta en español que nombra la dimensión de la fila.
                    x-translations:
                      en:
                        description: Spanish label naming the row dimension.
                    type: string
                    example: Validaciones incluidas por mes
                  label_en:
                    description: Etiqueta en inglés que nombra la dimensión de la fila.
                    x-translations:
                      en:
                        description: English label naming the row dimension.
                    type: string
                    example: Validations included per month
                  cells:
                    type: object
                    description: |
                      Celda por slug de plan. `type: bool` usa `value` (incluido / no incluido); `type: text` usa `text_es`/`text_en`.
                    x-translations:
                      en:
                        description: |
                          One cell per plan slug. `type: bool` uses `value` (included / not included); `type: text` uses `text_es`/`text_en`.
                    additionalProperties:
                      type: object
                      required:
                        - type
                        - value
                        - text_es
                        - text_en
                      properties:
                        type:
                          description: Tipo de celda. `bool` para una celda de incluido/no incluido; `text` para una celda de texto bilingüe.
                          x-translations:
                            en:
                              description: Cell type. `bool` for an included/not-included cell; `text` for a bilingual text cell.
                          type: string
                          enum:
                            - bool
                            - text
                          example: bool
                        value:
                          type:
                            - boolean
                            - 'null'
                        text_es:
                          type:
                            - string
                            - 'null'
                        text_en:
                          type:
                            - string
                            - 'null'
      x-translations:
        en:
          description: JSON:API comparison resource with plans as columns and features as rows.
    PlanComparisonResponse:
      description: |
        Respuesta de `GET /v1/plans/public/comparison` con la matriz de planes públicos.
      x-translations:
        en:
          description: |
            Response from `GET /v1/plans/public/comparison` with the public plan matrix.
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/PlanComparisonResource'
    UsageSummary:
      type: object
      description: Cuota de validaciones de la cuenta para el ciclo de suscripción vigente.
      x-translations:
        en:
          description: Account validation quota for the current subscription cycle.
      properties:
        plan_slug:
          type: string
          description: Identificador único y legible del plan. Corresponde al plan activo del usuario.
          example: free
          x-translations:
            en:
              description: Unique, human-readable plan identifier. It corresponds to the user's active plan.
        plan_name:
          type: string
          description: Nombre del plan que muestran las interfaces.
          x-translations:
            en:
              description: Plan name shown in the interfaces.
          example: Pro
        limit:
          type: integer
          minimum: 0
          description: Límite efectivo de validaciones para el ciclo actual.
          x-translations:
            en:
              description: Effective validation limit for the current cycle.
          example: 5000
        used:
          type: integer
          minimum: 0
          description: Validaciones consumidas en el ciclo actual.
          x-translations:
            en:
              description: Validations consumed in the current cycle.
          example: 1284
        remaining:
          type: integer
          minimum: 0
          description: Validaciones disponibles en el ciclo actual (`max(0, limit - used)`).
          x-translations:
            en:
              description: Validations available in the current cycle (`max(0, limit - used)`).
          example: 3716
        used_percent:
          type: number
          minimum: 0
          maximum: 100
          description: Porcentaje del límite consumido en el ciclo actual, redondeado a 2 decimales.
          example: 70.5
          x-translations:
            en:
              description: Percentage of the limit consumed in the current cycle, rounded to 2 decimals.
        tone:
          type: string
          enum:
            - ok
            - warn
            - danger
          description: 'Nivel de alerta según el consumo: `ok` (<70 %), `warn` (70–89 %), `danger` (≥90 % o cuota agotada).'
          x-translations:
            en:
              description: |
                `ok` (<70%), `warn` (70-89%), `danger` (≥90% or exhausted quota).
          example: ok
        resets_at:
          $ref: '#/components/schemas/TimestampUTC'
        next_reset_at:
          $ref: '#/components/schemas/TimestampUTC'
        quota_kind:
          type: string
          enum:
            - cycle
            - trial
          description: 'Origen de esta cuota: `cycle` (ciclo de suscripción de siempre) o `trial` (asignación de prueba inicial de una cuenta sin reclamar el plan). Aditivo.'
          example: cycle
          x-translations:
            en:
              description: 'Where this quota comes from: `cycle` (the usual subscription cycle) or `trial` (an unclaimed account''s initial trial allowance). Additive.'
        renews:
          type: boolean
          description: '`true` cuando esta cuota se repone al iniciar el siguiente ciclo. `false` para `quota_kind=trial`.'
          example: true
          x-translations:
            en:
              description: '`true` when this quota replenishes at the next cycle. `false` for `quota_kind=trial`.'
      required:
        - plan_slug
        - limit
        - used
        - remaining
        - used_percent
        - tone
        - resets_at
        - next_reset_at
    GetUsageHistoryAttributes:
      type: object
      description: Historial mensual de consumo para el número de meses solicitado.
      x-translations:
        en:
          description: Monthly usage history for the requested number of months.
      properties:
        data:
          description: Cuerpo de la respuesta, en la envoltura JSON:API.
          x-translations:
            en:
              description: Response body, in the JSON:API envelope.
          type: object
          properties:
            type:
              description: Tipo de recurso JSON:API. Siempre `usage_history`.
              x-translations:
                en:
                  description: JSON:API resource type. Always `usage_history`.
              type: string
              enum:
                - usage_history
              example: usage_history
            attributes:
              description: 'Datos del historial: Número de meses y lo consumo por mes.'
              x-translations:
                en:
                  description: 'History data: the month count and the per-month usage.'
              type: object
              properties:
                months:
                  description: Cantidad de meses que cubre el historial devuelto.
                  x-translations:
                    en:
                      description: Number of months the returned history covers.
                  type: integer
                  example: 6
                history:
                  description: Un elemento por mes, del más reciente al más antiguo.
                  x-translations:
                    en:
                      description: One entry per month, most recent first.
                  type: array
                  items:
                    type: object
                    properties:
                      period:
                        description: Mes al que corresponde la fila, en formato `YYYY-MM`.
                        x-translations:
                          en:
                            description: Month the row belongs to, in `YYYY-MM` format.
                        type: string
                        example: 2026-04
                      used:
                        description: Cantidad de validaciones consumidas en ese mes.
                        x-translations:
                          en:
                            description: Number of validations consumed that month.
                        type: integer
                        example: 420
                      limit_at_time:
                        description: |-
                          Límite mensual que se muestra junto al consumo de esa fila.

                          **No es el límite que regía aquel mes**: es el límite vigente hoy, repetido en todas las filas. Si el plan cambió por el camino, los meses anteriores se ven contra un tope que entonces no era el suyo. La bandera de la fila lo advierte.
                        x-translations:
                          en:
                            description: |-
                              Monthly limit shown alongside that row's usage.

                              **It is not the limit that applied that month**: it is today's limit, repeated on every row. If the plan changed along the way, earlier months are shown against a cap that was not theirs at the time. The row flag says so.
                        type: integer
                        example: 1000
                      limit_is_current_snapshot:
                        description: '`true` cuando el límite mostrado en la fila es el de hoy y no el de aquel mes. Vale siempre `true`, porque el histórico de límites no se guarda.'
                        x-translations:
                          en:
                            description: '`true` when the limit shown on the row is today''s and not that month''s. Always `true`, because limit history is not stored.'
                        type: boolean
                        example: true
    GetUsageBreakdownAttributes:
      type: object
      description: Consumo de la cuenta por tipo de operación durante el periodo consultado.
      x-translations:
        en:
          description: Account usage by operation type during the requested period.
      properties:
        data:
          description: Cuerpo de la respuesta, en la envoltura JSON:API.
          x-translations:
            en:
              description: Response body, in the JSON:API envelope.
          type: object
          properties:
            type:
              description: Tipo de recurso JSON:API. Siempre `usage_breakdown`.
              x-translations:
                en:
                  description: JSON:API resource type. Always `usage_breakdown`.
              type: string
              enum:
                - usage_breakdown
              example: usage_breakdown
            attributes:
              description: 'Datos del desglose: el periodo y el consumo por operación.'
              x-translations:
                en:
                  description: 'Breakdown data: the period and the per-operation usage.'
              type: object
              properties:
                period:
                  description: Mes al que corresponde el desglose, en formato `YYYY-MM`.
                  x-translations:
                    en:
                      description: Month the breakdown belongs to, in `YYYY-MM` format.
                  type: string
                  example: 2026-04
                operations:
                  description: Una entrada por tipo de operación, con lo que consumió cada una.
                  x-translations:
                    en:
                      description: One entry per operation type, with what each consumed.
                  type: array
                  items:
                    type: object
                    properties:
                      operation:
                        description: Operación a la que corresponde el consumo.
                        x-translations:
                          en:
                            description: Operation the usage belongs to.
                        type: string
                        example: validation
                      used:
                        description: Cantidad de validaciones consumidas por esa operación en el periodo.
                        x-translations:
                          en:
                            description: Number of validations that operation consumed over the period.
                        type: integer
                        example: 380
                      limit:
                        description: Límite mensual de la operación. `0` cuando se aplica el límite del plan.
                        x-translations:
                          en:
                            description: Monthly limit for the operation. `0` when the plan limit applies.
                        type: integer
                        example: 0
    GetUsageLimitsAttributes:
      type: object
      description: Límites de tasa aplicables a la cuenta.
      x-translations:
        en:
          description: Rate limits that apply to the account.
      properties:
        data:
          description: Cuerpo de la respuesta, en la envoltura JSON:API.
          x-translations:
            en:
              description: Response body, in the JSON:API envelope.
          type: object
          properties:
            type:
              description: Tipo de recurso JSON:API. Siempre `usage_limits`.
              x-translations:
                en:
                  description: JSON:API resource type. Always `usage_limits`.
              type: string
              enum:
                - usage_limits
              example: usage_limits
            attributes:
              description: 'Datos de los límites: los límites por contexto y sus notas.'
              x-translations:
                en:
                  description: 'Limits data: the per-context limits and their notes.'
              type: object
              properties:
                rate_limits:
                  description: 'Límites de tasa por contexto. Cada entrada trae sólo los que ese contexto aplica: `api` lleva `key_per_minute`, `ip_ceiling_per_minute` e `ip_per_minute`; `webhooks`, `host_per_minute` y `host_global_per_minute`; `login`, los cuatro de IP y correo electrónico. `key_per_minute` y `host_per_minute` los fija el plan de la cuenta.'
                  x-translations:
                    en:
                      description: 'Rate limits by context. Each entry carries only the ones that context applies: `api` carries `key_per_minute`, `ip_ceiling_per_minute` and `ip_per_minute`; `webhooks`, `host_per_minute` and `host_global_per_minute`; `login`, the four IP and email ones. `key_per_minute` and `host_per_minute` are set by the account plan.'
                  type: object
                  additionalProperties:
                    type: object
                    properties:
                      key_per_minute:
                        type: integer
                        description: Peticiones por minuto de una clave de la cuenta en la API, según su plan. Rige a las peticiones autenticadas.
                        example: 240
                        x-translations:
                          en:
                            description: Requests per minute for one key of the account on the API, per its plan. It governs authenticated requests.
                      ip_ceiling_per_minute:
                        type: integer
                        description: Techo de peticiones por minuto por dirección IP para las peticiones autenticadas, todas las claves de un mismo origen juntas.
                        example: 1200
                        x-translations:
                          en:
                            description: Ceiling of requests per minute per IP address for authenticated requests, all keys from one origin together.
                      host_per_minute:
                        type: integer
                        description: Entregas de webhook por minuto que la cuenta puede enviar hacia un mismo host destino, según su plan.
                        example: 60
                        x-translations:
                          en:
                            description: Webhook deliveries per minute the account may send to a single destination host, per its plan.
                      host_global_per_minute:
                        type: integer
                        description: Tope de entregas de webhook por minuto hacia un mismo host destino, todos los clientes juntos.
                        example: 600
                        x-translations:
                          en:
                            description: Cap on webhook deliveries per minute to a single destination host, all customers together.
                      ip_per_minute:
                        type: integer
                        description: Cantidad máxima de peticiones por dirección IP por minuto. En `api` sólo rige a las peticiones anónimas, sin clave.
                        x-translations:
                          en:
                            description: Maximum requests per IP address per minute. On `api` it only governs anonymous requests, without a key.
                      ip_per_hour:
                        type: integer
                        description: Cantidad máxima de peticiones por dirección IP por hora.
                        x-translations:
                          en:
                            description: Maximum requests per IP address per hour.
                      email_per_minute:
                        type: integer
                        description: Cantidad máxima de peticiones por correo electrónico por minuto.
                        x-translations:
                          en:
                            description: Maximum requests per email per minute.
                      email_per_hour:
                        type: integer
                        description: Cantidad máxima de peticiones por correo electrónico por hora.
                        x-translations:
                          en:
                            description: Maximum requests per email per hour.
                notes:
                  type: array
                  description: Notas legibles sobre los límites, resueltas al idioma de la petición.
                  x-translations:
                    en:
                      description: Human-readable notes about the limits, resolved to the request locale.
                  items:
                    type: string
    UsageHeatmapResource:
      type: object
      description: Recurso JSON:API con el mapa de calor de validaciones.
      x-translations:
        en:
          description: JSON:API resource carrying the validation heatmap.
      properties:
        type:
          type: string
          enum:
            - usage_heatmap
          description: Tipo de recurso JSON:API. Siempre `usage_heatmap`.
          x-translations:
            en:
              description: JSON:API resource type. Always `usage_heatmap`.
          example: usage_heatmap
        attributes:
          type: object
          description: 'Datos del mapa de calor: la ventana en días, el valor máximo y las celdas.'
          x-translations:
            en:
              description: 'Heatmap data: the window in days, the maximum value, and the cells.'
          properties:
            days:
              type: integer
              description: Ventana del mapa de calor en días (típicamente 30 o 90).
              x-translations:
                en:
                  description: Heatmap window in days (typically 30 or 90).
              example: 30
            max_count:
              type: integer
              description: Valor máximo de `count` entre todas las celdas; sirve para escalar la intensidad del color al representarlo.
              x-translations:
                en:
                  description: Maximum `count` value across all cells; useful for scaling color intensity when rendering.
              example: 48
            buckets:
              type: array
              description: Lista de celdas del mapa; cada entrada es una combinación de `day_of_week` y `hour_of_day` con su conteo.
              x-translations:
                en:
                  description: Map cells; each entry is a (`day_of_week`, `hour_of_day`) combination with its count.
              items:
                type: object
                description: Celda individual del mapa de calor.
                x-translations:
                  en:
                    description: Single heatmap cell.
                properties:
                  day_of_week:
                    type: integer
                    minimum: 0
                    maximum: 6
                    description: Día de la semana (0 = domingo … 6 = sábado).
                    x-translations:
                      en:
                        description: Day of week (0 = Sunday … 6 = Saturday).
                    example: 0
                  hour_of_day:
                    type: integer
                    minimum: 0
                    maximum: 23
                    description: Hora del día (0–23), hora del servidor.
                    x-translations:
                      en:
                        description: Hour of day (0-23), server time.
                    example: 0
                  count:
                    type: integer
                    description: Cantidad de validaciones en esa celda durante la ventana.
                    x-translations:
                      en:
                        description: Number of validations in this cell during the window.
                    example: 12
    GetUsageHeatmapResponse:
      type: object
      description: |
        Respuesta de `GET /v1/usage/heatmap` con la densidad de validaciones por día de la semana y hora.
      x-translations:
        en:
          description: |
            Response from `GET /v1/usage/heatmap` with validation density by weekday and hour.
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/UsageHeatmapResource'
    InsightsOverviewAttributes:
      type: object
      description: |
        Indicadores agregados de las validaciones de la cuenta.
      x-translations:
        en:
          description: |
            Aggregate indicators for the account's validations.
      properties:
        total:
          type: integer
          minimum: 0
          description: Cantidad de validaciones en el alcance, sin acotar por fecha.
          example: 1240
          x-translations:
            en:
              description: Number of validations in the scope, with no date bound.
        today:
          type: integer
          minimum: 0
          description: Cantidad de validaciones creadas en el día calendario actual (UTC).
          example: 37
          x-translations:
            en:
              description: Number of validations created on the current calendar day (UTC).
        yesterday:
          type: integer
          minimum: 0
          description: Cantidad de validaciones creadas en el día calendario anterior (UTC).
          example: 42
          x-translations:
            en:
              description: Number of validations created on the previous calendar day (UTC).
        success_rate:
          type: number
          minimum: 0
          maximum: 100
          description: |
            Porcentaje de validaciones con veredicto `valid` sobre el total, expresado de 0 a 100.
          example: 94.2
          x-translations:
            en:
              description: |
                Percentage of validations with a `valid` verdict out of the total, expressed from 0 to 100.
        avg_latency_ms:
          type: integer
          minimum: 0
          description: Tiempo promedio de procesamiento (en milisegundos) de las últimas 24 horas.
          example: 810
          x-translations:
            en:
              description: |
                Average processing time over the last 24 hours, in milliseconds.
        by_banxico_7d:
          type: object
          description: |
            Distribución de veredictos de Banxico de los últimos 7 días. Las claves son valores de `banxico_status` (`valid`, `not_found`, `cep_unavailable`, `invalid`, `returned`, `error` o `pending`); los valores ausentes implican cero.
          x-translations:
            en:
              description: |
                Banxico verdict distribution for the last 7 days. Keys are `banxico_status` values (`valid`, `not_found`, `cep_unavailable`, `invalid`, `returned`, `error`, or `pending`); missing values imply zero.
          additionalProperties:
            type: integer
          example:
            valid: 210
            invalid: 8
            not_found: 3
            cep_unavailable: 2
            returned: 1
        scope:
          type: string
          enum:
            - self
            - global
          description: 'Alcance de los datos — `self`: Solo las validaciones del usuario autenticado; `global`: Todas las validaciones de la plataforma (solo para administradores).'
          example: self
          x-translations:
            en:
              description: |
                Data scope — `self`: Only the authenticated user's validations; `global`: All platform validations (administrators only).
    InsightsTrendsAttributes:
      type: object
      description: |
        Series temporales de validaciones cuyos puntos contienen métricas de volumen o latencia según `metric`.
      x-translations:
        en:
          description: |
            Validation time series whose points contain volume or latency metrics according to `metric`.
      properties:
        range:
          type: string
          enum:
            - 24h
            - 7d
            - 30d
            - 90d
          description: |
            Ventana de tiempo de la serie: `24h` (últimas 24 horas), `7d` (últimos 7 días), `30d` (últimos 30 días) o `90d` (últimos 90 días).
          example: 7d
          x-translations:
            en:
              description: |
                Time window of the series: `24h` (last 24 hours), `7d` (last 7 days), `30d` (last 30 days), or `90d` (last 90 days).
        metric:
          type: string
          enum:
            - volume
            - latency
          description: 'Métrica representada — `volume`: conteos de validaciones apilados por tipo; `latency`: tiempos de procesamiento (en percentiles).'
          example: volume
          x-translations:
            en:
              description: |
                Metric represented — `volume`: validation counts stacked by type; `latency`: processing times in percentiles.
        scope:
          type: string
          enum:
            - self
            - global
          description: 'Alcance de los datos — `self`: Solo las validaciones del usuario autenticado; `global`: Todas las validaciones de la plataforma (solo para administradores).'
          example: self
          x-translations:
            en:
              description: |
                Data scope — `self`: Only the authenticated user's validations; `global`: All platform validations (administrators only).
        series:
          type: array
          description: |
            Puntos de datos ordenados cronológicamente. La forma de cada punto depende de `metric`: para `volume` incluye `bucket`, `total`, `direct`, `ocr`, `valid`, `errored`; para `latency` incluye `bucket`, `p50`, `p95`, `p99`, `avg`.
          x-translations:
            en:
              description: |
                Data points in chronological order. The shape of each point depends on `metric`: for `volume` it includes `bucket`, `total`, `direct`, `ocr`, `valid`, `errored`; for `latency` it includes `bucket`, `p50`, `p95`, `p99`, `avg`.
          items:
            type: object
            description: Punto de la serie temporal (la forma exacta depende de `metric`).
            x-translations:
              en:
                description: Time-series data point (the exact shape depends on `metric`).
            additionalProperties: true
    InsightsTopBanksAttributes:
      type: object
      description: |
        Ranking de bancos de la cuenta según la métrica solicitada.
      x-translations:
        en:
          description: |
            Ranking of the account's banks by the requested metric.
      properties:
        metric:
          type: string
          enum:
            - volume
            - errors
          description: 'Métrica de clasificación — `volume`: bancos ordenados por cantidad de validaciones; `errors`: bancos ordenados por tasa de error.'
          example: volume
          x-translations:
            en:
              description: |
                Ranking metric — `volume`: banks ordered by validation count; `errors`: banks ordered by error rate.
        scope:
          type: string
          enum:
            - self
            - global
          description: 'Alcance de los datos — `self`: Solo las validaciones del usuario autenticado; `global`: Todas las validaciones de la plataforma (solo para administradores).'
          example: self
          x-translations:
            en:
              description: |
                Data scope — `self`: Only the authenticated user's validations; `global`: All platform validations (administrators only).
        banks:
          type: array
          description: Lista de bancos ordenada según la métrica seleccionada.
          x-translations:
            en:
              description: List of banks ordered by the selected metric.
          items:
            type: object
            description: Entrada del ranking de bancos (código, nombre y métricas del periodo).
            x-translations:
              en:
                description: Bank ranking entry (code, name, and period metrics).
            properties:
              bank_code:
                type: string
                pattern: ^\d{5}$
                minLength: 5
                maxLength: 5
                description: Código SPEI (5 dígitos) del banco resuelto.
                example: '40012'
                x-translations:
                  en:
                    description: SPEI code (5 digits) of the resolved bank.
              bank_name:
                type: string
                description: Nombre oficial del banco, resuelto a partir de su `bank_code`.
                example: BBVA
                x-translations:
                  en:
                    description: Official name of the bank, resolved from its `bank_code`.
              total:
                type: integer
                minimum: 0
                description: Cantidad de validaciones del banco en el periodo.
                example: 180
                x-translations:
                  en:
                    description: Number of validations for the bank in the period.
              valid:
                type: integer
                minimum: 0
                description: Cantidad de validaciones con veredicto `valid` en el periodo.
                example: 175
                x-translations:
                  en:
                    description: Number of validations with a `valid` verdict in the period.
              error_rate:
                type: number
                minimum: 0
                maximum: 100
                description: |
                  Tasa de error del banco: porcentaje de validaciones con estado `failed` o `error` sobre el total, de 0 a 100.
                example: 2.78
                x-translations:
                  en:
                    description: |
                      Bank error rate: percentage of validations with `failed` or `error` status out of the total, from 0 to 100.
    InsightsTopBeneficiariesAttributes:
      type: object
      description: |
        Ranking de beneficiarios de la cuenta por validaciones durante los últimos 90 días.
      x-translations:
        en:
          description: |
            Ranking of the account's beneficiaries by validations during the last 90 days.
      properties:
        beneficiaries:
          type: array
          description: Ranking de beneficiarios, ordenado de mayor a menor por `count` (validaciones hacia ese beneficiario en los últimos 90 días).
          x-translations:
            en:
              description: Beneficiary ranking, ordered from highest to lowest by `count` (validations to that beneficiary in the last 90 days).
          items:
            type: object
            description: 'Entrada del ranking: beneficiario con cuenta enmascarada y su número de validaciones en el periodo.'
            x-translations:
              en:
                description: 'Ranking entry: a beneficiary with a masked account and its validation count in the period.'
            properties:
              label:
                type: string
                description: |
                  Etiqueta guardada del beneficiario. Cuando no hay una etiqueta guardada, el nombre normalizado del banco receptor.
                x-translations:
                  en:
                    description: |
                      Saved beneficiary label. When there is no saved label, the normalized name of the receiving bank.
                example: Proveedor Norte
              bank_name:
                type: string
                description: Nombre del banco receptor, tomado del beneficiario guardado o capturado por OCR.
                x-translations:
                  en:
                    description: Receiving bank name, taken from the saved beneficiary or captured by OCR.
                example: BBVA MEXICO
              account_last4:
                type: string
                pattern: ^\d{4}$
                minLength: 4
                maxLength: 4
                description: Últimos 4 dígitos del número de cuenta; el resto se enmascara por privacidad.
                example: '1234'
                x-translations:
                  en:
                    description: Last 4 digits of the account number; the rest is masked for privacy.
              count:
                type: integer
                minimum: 1
                description: Cantidad de validaciones hacia este beneficiario en el periodo.
                x-translations:
                  en:
                    description: Number of validations to this beneficiary in the period.
                example: 48
            required:
              - label
              - bank_name
              - account_last4
              - count
      required:
        - beneficiaries
    FinanceTopBank:
      type: object
      description: Entrada del ranking de bancos por volumen verificado.
      x-translations:
        en:
          description: Entry in the bank ranking by verified volume.
      properties:
        bank_code:
          type: string
          pattern: ^\d{5}$
          minLength: 5
          maxLength: 5
          description: Código SPEI (5 dígitos) del banco resuelto.
          example: '40012'
          x-translations:
            en:
              description: SPEI code (5 digits) of the resolved bank.
        bank_name:
          type: string
          description: Nombre oficial del banco, resuelto a partir de su `bank_code`.
          example: BBVA México
          x-translations:
            en:
              description: Official name of the bank, resolved from its `bank_code`.
        count:
          type: integer
          minimum: 0
          description: Cantidad de transferencias del banco en el periodo.
          example: 71
          x-translations:
            en:
              description: Number of transfers for this bank in the period.
        valid:
          type: integer
          minimum: 0
          description: Transferencias con resultado `valid` (verificadas con CEP).
          example: 64
          x-translations:
            en:
              description: Transfers with `valid` result (verified in CEP).
        verified_volume:
          type: number
          description: Volumen total (en MXN) de las transferencias verificadas.
          example: 412000
          x-translations:
            en:
              description: Total volume in MXN of verified transfers.
    FinanceSummaryAttributes:
      type: object
      description: Indicadores financieros de la cuenta para un mes calendario, con agregados, desgloses y comparación con el mes anterior.
      x-translations:
        en:
          description: Account financial indicators for a calendar month, with aggregates, breakdowns, and comparison with the previous month.
      properties:
        period:
          type: object
          description: Rango temporal del resumen (mes calendario completo).
          x-translations:
            en:
              description: Time range of the summary (full calendar month).
          properties:
            month:
              type: string
              pattern: ^\d{4}-\d{2}$
              description: Mes en formato `YYYY-MM`.
              example: 2026-04
              x-translations:
                en:
                  description: Month in `YYYY-MM` format.
            from_utc:
              type: string
              format: date-time
              description: Inicio del mes en UTC (ISO 8601).
              example: '2026-04-01T06:00:00Z'
              x-translations:
                en:
                  description: Start of the month in UTC (ISO 8601).
            to_utc:
              type: string
              format: date-time
              description: Fin del mes en UTC (ISO 8601, exclusivo).
              example: '2026-05-01T06:00:00Z'
              x-translations:
                en:
                  description: End of the month in UTC (ISO 8601, exclusive).
        counts:
          type: object
          description: Conteos de validaciones por veredicto, más retiros mediante borrado lógico del periodo (informativo).
          x-translations:
            en:
              description: Validation counts by verdict, plus records withdrawn through soft deletion during the period (informational).
          properties:
            total:
              type: integer
              minimum: 0
              description: Cantidad de validaciones completadas en el periodo (excluyendo las validaciones con estado `pending`).
              example: 287
              x-translations:
                en:
                  description: Number of validations completed in the period (excluding validations with `pending` status).
            valid:
              type: integer
              minimum: 0
              description: Validaciones con veredicto `valid` en el periodo.
              example: 228
              x-translations:
                en:
                  description: Validations with `valid` verdict in the period.
            invalid:
              type: integer
              minimum: 0
              description: Validaciones con veredicto `invalid` en el periodo.
              example: 4
              x-translations:
                en:
                  description: Validations with `invalid` verdict in the period.
            not_found:
              type: integer
              minimum: 0
              description: Validaciones con veredicto `not_found` en el periodo.
              example: 32
              x-translations:
                en:
                  description: Validations with `not_found` verdict in the period.
            cep_unavailable:
              type: integer
              minimum: 0
              description: Validaciones con veredicto `cep_unavailable` en el periodo.
              example: 18
              x-translations:
                en:
                  description: Validations with `cep_unavailable` verdict in the period.
            returned:
              type: integer
              minimum: 0
              description: Validaciones cuya operación se liquidó y después se devolvió en el periodo.
              example: 4
              x-translations:
                en:
                  description: Validations whose operation was settled and later returned in the period.
            error:
              type: integer
              minimum: 0
              description: Validaciones que terminaron en error técnico.
              example: 3
              x-translations:
                en:
                  description: Validations that ended in a technical error.
            pending:
              type: integer
              minimum: 0
              description: Validaciones aún en proceso que no han recibido estado terminal al cierre del periodo.
              example: 2
              x-translations:
                en:
                  description: Validations still in progress that had not reached a terminal status at the end of the period.
            deleted:
              type: integer
              minimum: 0
              description: Validaciones retiradas mediante borrado lógico en el periodo.
              example: 6
              x-translations:
                en:
                  description: Validations withdrawn through soft deletion during the period.
        amounts:
          type: object
          description: |
            Agregados monetarios del periodo. `verified_volume` suma únicamente validaciones con resultado `valid`.
          x-translations:
            en:
              description: |
                Monetary aggregates for the period. `verified_volume` sums only validations with `valid` result.
          properties:
            total_volume:
              type: number
              description: Importe sumado en MXN de todas las validaciones del periodo.
              example: 1234567.89
              x-translations:
                en:
                  description: Summed amount in MXN of every validation in the period.
            verified_volume:
              type: number
              description: Volumen en MXN de validaciones con resultado `valid`.
              example: 980123.45
              x-translations:
                en:
                  description: Volume in MXN of validations with `valid` result.
            unverified_volume:
              type: number
              description: Diferencia entre volumen total y verificado.
              example: 254444.44
              x-translations:
                en:
                  description: Difference between total and verified volume.
            avg_ticket:
              type: number
              description: Monto promedio por validación en MXN.
              example: 4302.32
              x-translations:
                en:
                  description: Average amount per validation in MXN.
            median_ticket:
              type: number
              description: Monto mediano por validación en MXN.
              example: 3100
              x-translations:
                en:
                  description: Median amount per validation in MXN.
            max_ticket:
              type: number
              description: Mayor monto individual del mes en MXN.
              example: 250000
              x-translations:
                en:
                  description: Largest individual amount in the month in MXN.
            min_ticket:
              type: number
              description: Menor monto individual del mes en MXN.
              example: 50
              x-translations:
                en:
                  description: Smallest individual amount in the month in MXN.
            verified_share_pct:
              type: number
              minimum: 0
              maximum: 100
              description: |
                Porcentaje del volumen total correspondiente a validaciones verificadas, expresado de 0 a 100.
              example: 79.4
              x-translations:
                en:
                  description: |
                    Percentage of total volume from verified validations, expressed from 0 to 100.
        success_rate:
          type: number
          minimum: 0
          maximum: 100
          description: |
            Tasa de éxito: porcentaje de validaciones con resultado `valid` sobre el total completado, expresado de 0 a 100.
          example: 79.4
          x-translations:
            en:
              description: |
                Success rate: percentage of validations with `valid` result out of all completed, expressed from 0 to 100.
        avg_processing_ms:
          type: integer
          minimum: 0
          description: Tiempo promedio de procesamiento en milisegundos del periodo.
          example: 412
          x-translations:
            en:
              description: Average processing time in milliseconds for the period.
        daily_breakdown:
          type: array
          description: |
            Desglose diario con actividad. Los días sin validaciones se omiten.
          x-translations:
            en:
              description: |
                Daily breakdown for days with activity. Days with no validations are omitted.
          items:
            type: object
            description: Entrada diaria del desglose financiero (fecha, conteos y volúmenes).
            x-translations:
              en:
                description: Daily breakdown entry for finance (date, counts, and volumes).
            properties:
              date:
                description: Día al que corresponde la fila.
                x-translations:
                  en:
                    description: Day the row belongs to.
                type: string
                format: date
                example: '2026-04-15'
              count:
                description: Validaciones hechas ese día.
                x-translations:
                  en:
                    description: Validations made that day.
                type: integer
                example: 14
              valid:
                description: Cuántas de ellas encontraron el comprobante y cuadran.
                x-translations:
                  en:
                    description: How many of them found the receipt and match.
                type: integer
                example: 11
              verified_volume:
                description: Importe sumado de las transferencias cuyo comprobante se encontró y cuadra.
                x-translations:
                  en:
                    description: Summed amount of transfers whose receipt was found and matches.
                type: number
                example: 42100
              unverified_volume:
                description: 'Importe sumado del resto: las que no se pudieron verificar, por no encontrarse el comprobante o por no cuadrar.'
                x-translations:
                  en:
                    description: 'Summed amount of the rest: those that could not be verified, either because the receipt was not found or because it does not match.'
                type: number
                example: 8500
        top_counterparties:
          type: array
          description: |
            Top 5 cuentas beneficiarias del usuario por volumen verificado en el mes.
          x-translations:
            en:
              description: |
                Top 5 user beneficiary accounts by verified volume in the month.
          items:
            type: object
            description: Resumen de una cuenta contraparte con volumen verificado del periodo.
            x-translations:
              en:
                description: Counterparty account summary with verified volume for the period.
            properties:
              cuenta:
                type: string
                description: Cuenta beneficiaria (CLABE, tarjeta o celular).
                example: '012345678901234567'
                x-translations:
                  en:
                    description: Beneficiary account (CLABE, card, or phone number).
              banco:
                type: string
                description: Nombre del banco receptor, tal como quedó en la validación.
                example: BBVA México
                x-translations:
                  en:
                    description: Name of the receiving bank, as recorded in the validation.
              count:
                type: integer
                description: Cantidad de transferencias a esta cuenta en el mes.
                example: 24
                x-translations:
                  en:
                    description: Number of transfers to this account in the month.
              valid:
                type: integer
                description: Cuántas de ellas encontraron el comprobante y cuadran.
                example: 22
                x-translations:
                  en:
                    description: How many of them found the receipt and match.
              verified_volume:
                type: number
                description: Importe sumado en MXN de las transferencias que cuadran.
                example: 145000
                x-translations:
                  en:
                    description: Summed amount in MXN of the transfers that match.
        top_banks_receptor:
          type: array
          description: Top 5 bancos receptores por volumen verificado.
          x-translations:
            en:
              description: Top 5 receiving banks by verified volume.
          items:
            $ref: '#/components/schemas/FinanceTopBank'
        top_banks_emisor:
          type: array
          description: Top 5 bancos emisores por volumen verificado.
          x-translations:
            en:
              description: Top 5 sending banks by verified volume.
          items:
            $ref: '#/components/schemas/FinanceTopBank'
        verdict_distribution:
          type: object
          description: Conteo de validaciones por valor de veredicto. Los valores ausentes implican cero.
          x-translations:
            en:
              description: Validation count by verdict value. Missing values imply zero.
          additionalProperties:
            type: integer
          example:
            valid: 228
            not_found: 32
            cep_unavailable: 18
            invalid: 4
            returned: 4
            error: 3
        comparison_prev_month:
          type: object
          description: |
            Comparativa contra el mes anterior. Los deltas son `null` cuando el mes anterior tuvo base cero (crecimiento no definido).
          x-translations:
            en:
              description: |
                Comparison against the previous month. Deltas are `null` when the previous month had a zero base (growth not defined).
          properties:
            prev_month:
              type: string
              pattern: ^\d{4}-\d{2}$
              description: Mes anterior en formato `YYYY-MM`.
              example: 2026-03
              x-translations:
                en:
                  description: Previous month in `YYYY-MM` format.
            total:
              type: integer
              description: Cantidad de validaciones del mes anterior.
              example: 251
              x-translations:
                en:
                  description: Number of validations in the previous month.
            verified_volume:
              type: number
              description: Volumen verificado del mes anterior en MXN.
              example: 880000
              x-translations:
                en:
                  description: Verified volume in the previous month in MXN.
            total_delta_pct:
              type:
                - number
                - 'null'
              description: |
                Cambio porcentual del conteo total respecto al mes anterior. `null` cuando el mes anterior tuvo cero validaciones.
              example: 14.3
              x-translations:
                en:
                  description: |
                    Percentage change in total count vs. the previous month. `null` when the previous month had zero validations.
            volume_delta_pct:
              type:
                - number
                - 'null'
              description: |
                Cambio porcentual del volumen verificado respecto al mes anterior. `null` cuando el mes anterior tuvo cero volumen verificado.
              example: 11.4
              x-translations:
                en:
                  description: |
                    Percentage change in verified volume vs. the previous month. `null` when the previous month had zero verified volume.
        folio:
          type: string
          description: |
            Folio determinístico del estado de cuenta del mes, idéntico al devuelto en la cabecera `X-Finance-Folio` del endpoint de exportación.
          example: A3F12B9C0D4E
          x-translations:
            en:
              description: |
                Deterministic statement folio for the month, identical to the value returned in the `X-Finance-Folio` header of the export endpoint.
    FinanceSummaryResource:
      type: object
      description: Recurso JSON:API del resumen financiero.
      x-translations:
        en:
          description: JSON:API resource for the financial summary.
      properties:
        type:
          type: string
          description: Tipo del recurso JSON:API. Siempre `finance_summary`.
          example: finance_summary
          x-translations:
            en:
              description: JSON:API resource type. Always `finance_summary`.
        attributes:
          $ref: '#/components/schemas/FinanceSummaryAttributes'
    FinanceSummaryResponse:
      type: object
      description: |
        Respuesta de `GET /v1/finance/summary` con los indicadores del mes solicitado en `data.attributes`.
      x-translations:
        en:
          description: |
            Response from `GET /v1/finance/summary` with the requested month's indicators in `data.attributes`.
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/FinanceSummaryResource'
    FinancePreviewResource:
      type: object
      description: Recurso JSON:API de la vista previa de un reporte financiero.
      x-translations:
        en:
          description: JSON:API resource for a financial report preview.
      properties:
        type:
          type: string
          description: Tipo del recurso JSON:API. Siempre `finance_preview`.
          example: finance_preview
          x-translations:
            en:
              description: JSON:API resource type. Always `finance_preview`.
        attributes:
          type: object
          description: Datos de la vista previa del reporte.
          x-translations:
            en:
              description: Report preview fields.
          properties:
            report:
              type: string
              enum:
                - monthly
                - counterparties
                - by-bank
                - accounting
              description: 'Tipo de reporte — `monthly`: estado de cuenta mensual; `counterparties`: top contrapartes; `by-bank`: desglose por banco; `accounting`: libro diario contable.'
              example: monthly
              x-translations:
                en:
                  description: |
                    Report type — `monthly`: Monthly statement; `counterparties`: Top counterparties; `by-bank`: Bank breakdown; `accounting`: Accounting ledger.
            month:
              type: string
              pattern: ^\d{4}-\d{2}$
              description: Mes del reporte en formato `YYYY-MM`.
              example: 2026-04
              x-translations:
                en:
                  description: Report month in `YYYY-MM` format.
            headers:
              type: array
              description: Nombres de las columnas para formato CSV (en el mismo orden que las filas en `rows`).
              x-translations:
                en:
                  description: |
                    CSV column names, in the same order as the values in `rows`.
              items:
                type: string
              example:
                - Fecha
                - Monto
                - Clave de rastreo
                - Estado
            rows:
              type: array
              maxItems: 20
              description: Primeras 20 filas (máximo) de datos del reporte.
              x-translations:
                en:
                  description: First 20 data rows of the report (maximum).
              items:
                type: array
                items:
                  type: string
              example:
                - - '2026-04-15'
                  - '15000.50'
                  - MXBA20260415001234
                  - valid
    FinancePreviewResponse:
      type: object
      description: |
        Respuesta de vista previa de un reporte financiero con las cabeceras y hasta 20 filas del CSV.
      x-translations:
        en:
          description: |
            Financial report preview response with the CSV headers and up to 20 rows.
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/FinancePreviewResource'
    BillingSubscriptionAttributes:
      type: object
      description: Suscripción activa del usuario, con su origen, estado, ciclo y límites. Cada usuario tiene exactamente una. Los campos `trial_*`, `cancel_*` y `stripe_subscription_id` son nulos cuando no aplican.
      x-translations:
        en:
          description: The user's active subscription, including its source, state, cycle, and limits. Each user has exactly one. The `trial_*`, `cancel_*`, and `stripe_subscription_id` fields are null when not applicable.
      properties:
        plan_slug:
          type: string
          description: Identificador único y legible del plan. Corresponde al plan activo del usuario.
          example: pro
          x-translations:
            en:
              description: Unique, human-readable plan identifier. It corresponds to the user's active plan.
        plan_name:
          type: string
          description: Nombre del plan que muestran las interfaces.
          example: Pro
          x-translations:
            en:
              description: Plan name shown in the interfaces.
        billing_model:
          type: string
          enum:
            - free
            - tiered
            - metered
            - hybrid
          description: 'Modelo de facturación del plan — `free`: Sin cobro; `tiered`: Cuota fija; `metered`: Cobro por consumo; `hybrid`: Cuota fija más consumo.'
          example: hybrid
          x-translations:
            en:
              description: 'Plan billing model. `free`: No charge; `tiered`: Flat fee; `metered`: Charged by usage; `hybrid`: Flat fee plus usage.'
        source:
          type: string
          enum:
            - stripe
            - admin_granted
            - system_default
          description: 'Origen de la suscripción — `stripe`: Pago mediante Stripe; `admin_granted`: Concesión administrativa; `system_default`: Plan asignado por el sistema.'
          example: stripe
          x-translations:
            en:
              description: 'Subscription source — `stripe`: Payment through Stripe; `admin_granted`: Admin grant; `system_default`: Plan assigned by the system.'
        status:
          type: string
          enum:
            - active
            - trialing
            - past_due
            - canceled
            - unpaid
            - incomplete
            - incomplete_expired
            - paused
          description: 'Estado del ciclo de vida de la suscripción — `active`: Activa; `trialing`: En periodo de prueba; `past_due`: Con un cobro vencido y acceso todavía habilitado; `canceled`: Terminada; `unpaid`: Con cobros fallidos y sin acceso; `incomplete`: Primer pago pendiente; `incomplete_expired`: Primer pago vencido; `paused`: En pausa.'
          example: active
          x-translations:
            en:
              description: 'Subscription lifecycle state — `active`: Active; `trialing`: In a trial period; `past_due`: Payment overdue with access still enabled; `canceled`: Ended; `unpaid`: Failed payments with no access; `incomplete`: First payment pending; `incomplete_expired`: First payment expired; `paused`: Paused.'
        current_period_start:
          $ref: '#/components/schemas/TimestampUTC'
        current_period_end:
          $ref: '#/components/schemas/TimestampUTC'
        currency:
          type: string
          enum:
            - MXN
            - USD
          description: 'Moneda del precio — `MXN`: Pesos mexicanos; `USD`: Dólares estadounidenses.'
          example: MXN
          x-translations:
            en:
              description: Currency of the active cycle. `MXN` is Mexican pesos and `USD`, US dollars.
        billing_interval:
          type: string
          enum:
            - month
            - year
            - once
          description: 'Cadencia de facturación — `once`: Ciclo único, sin renovación automática; `month`: Cada mes; `year`: Cada año.'
          example: month
          x-translations:
            en:
              description: 'Billing cadence — `once`: One cycle with no automatic renewal; `month`: Monthly; `year`: Yearly.'
        overage_enabled:
          type: boolean
          description: |
            `true` cuando el usuario aceptó cobro por excedente en un plan `hybrid`. En ese caso, `effective_limit` salta de `included_validations` a `monthly_validation_limit`.
          example: false
          x-translations:
            en:
              description: |
                `true` when the user has opted into overage billing on a `hybrid` plan. When so, `effective_limit` jumps from `included_validations` to `monthly_validation_limit`.
        included_validations:
          type:
            - integer
            - 'null'
          minimum: 0
          description: Validaciones incluidas en la cuota base antes del cobro por excedentes. `null` cuando el plan no tiene cuota base.
          example: 500
          x-translations:
            en:
              description: Validations included in the base quota before overage charges. `null` when the plan has no base quota.
        monthly_validation_limit:
          type: integer
          minimum: 0
          description: Tope de validaciones por ciclo de la suscripción. `0` no permite validaciones. En planes `hybrid`, es el tope duro cuando `overage_enabled` es `true`; cuando es `false`, `effective_limit` usa `included_validations`. En los demás modelos, es el límite efectivo salvo que exista un ajuste administrativo.
          example: 10000
          x-translations:
            en:
              description: Validation cap per subscription cycle. `0` allows no validations. On `hybrid` plans, it is the hard cap when `overage_enabled` is `true`; when it is `false`, `effective_limit` uses `included_validations`. On other models, it is the effective limit unless an admin override applies.
        beneficiaries_max:
          type: integer
          minimum: -1
          description: Tope de beneficiarios registrados por cuenta. `-1` significa sin tope; desde `0`, el valor se aplica como límite.
          example: -1
          x-translations:
            en:
              description: Cap on registered beneficiaries per account. `-1` means uncapped; from `0`, the value is enforced as the limit.
        bulk_validations_max:
          type: integer
          minimum: 0
          description: Cantidad máxima de validaciones por trabajo de importación. `0` no permite importar validaciones.
          x-translations:
            en:
              description: Maximum number of validations per import job. `0` allows no validation imports.
        effective_limit:
          type: integer
          minimum: 0
          description: Límite efectivo de validaciones para el ciclo actual. Incorpora los ajustes administrativos y, en planes `hybrid`, la regla de `overage_enabled`. Es el valor contra el que se decide si una validación se atiende o se rechaza.
          example: 500
          x-translations:
            en:
              description: Effective validation limit for the current cycle. It includes admin overrides and, on `hybrid` plans, the `overage_enabled` rule. This is the value used to decide whether a validation is served or rejected.
        is_stripe:
          type: boolean
          description: '`true` cuando `source` es `stripe`.'
          example: true
          x-translations:
            en:
              description: '`true` when `source` is `stripe`.'
        stripe_subscription_id:
          type:
            - string
            - 'null'
          description: Identificador de la suscripción en Stripe (`sub_...`). `null` cuando `source` no es `stripe`.
          example: sub_1OaBcDeFgHiJk2
          x-translations:
            en:
              description: Stripe subscription identifier (`sub_...`). `null` when `source` is not `stripe`.
        grant_reason:
          type:
            - string
            - 'null'
          description: |
            Motivo registrado cuando la suscripción fue otorgada por un
                administrador. `null` cuando `source` no es `admin_granted`.
          example: cortesía soporte
          x-translations:
            en:
              description: |
                Reason recorded when the subscription was granted by an admin. `null` when `source` is not `admin_granted`.
        cancel_at_period_end:
          type: boolean
          description: '`true` cuando la suscripción está programada para cancelarse al cierre del ciclo actual.'
          example: false
          x-translations:
            en:
              description: '`true` when the subscription is scheduled to cancel at the end of the current cycle.'
        cancel_at:
          type:
            - string
            - 'null'
          format: date-time
          description: |
            Fecha y hora programada para la cancelación, en ISO 8601 UTC. `null` cuando no hay una cancelación programada mediante `cancel_at`.
          x-translations:
            en:
              description: |
                Date and time the cancellation is scheduled for, in ISO 8601 UTC. `null` when no cancellation is scheduled through `cancel_at`.
          example: '2026-04-30T10:15:00Z'
        canceled_at:
          type:
            - string
            - 'null'
          format: date-time
          description: |
            Fecha y hora en que la cancelación se consumó, en ISO 8601 UTC. `null` mientras la suscripción está activa.
          x-translations:
            en:
              description: |
                Date and time the cancellation took effect, in ISO 8601 UTC. `null` while the subscription is active.
          example: '2026-04-30T10:15:00Z'
        trial_start:
          type:
            - string
            - 'null'
          format: date-time
          description: Fecha y hora de inicio del periodo de prueba, en ISO 8601 UTC. `null` cuando no hay periodo de prueba.
          x-translations:
            en:
              description: Date and time the trial period starts, in ISO 8601 UTC. `null` when there is no trial period.
          example: '2026-04-30T10:15:00Z'
        trial_end:
          type:
            - string
            - 'null'
          format: date-time
          description: Fecha y hora de fin del periodo de prueba, en ISO 8601 UTC. `null` cuando no hay periodo de prueba.
          x-translations:
            en:
              description: Date and time the trial period ends, in ISO 8601 UTC. `null` when there is no trial period.
          example: '2026-04-30T10:15:00Z'
    BillingSubscriptionResource:
      type: object
      description: Recurso JSON:API con la suscripción activa del usuario.
      x-translations:
        en:
          description: JSON:API resource containing the user's active subscription.
      allOf:
        - $ref: '#/components/schemas/JsonApiResourceBase'
        - type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              description: Tipo de recurso JSON:API. Siempre `billing_subscription`.
              x-translations:
                en:
                  description: JSON:API resource type. Always `billing_subscription`.
              type: string
              enum:
                - billing_subscription
              example: billing_subscription
            id:
              type: string
              description: Identificador local de la suscripción, expresado como cadena.
              example: '4218'
              x-translations:
                en:
                  description: Local subscription identifier, represented as a string.
            attributes:
              $ref: '#/components/schemas/BillingSubscriptionAttributes'
    BillingSubscriptionQuotaMeta:
      type: object
      description: |
        Resumen de cuota del ciclo activo. Devuelto en `meta.quota` por `GET /v1/billing/subscription`.
      x-translations:
        en:
          description: |
            Quota summary for the active cycle. Returned at `meta.quota` by `GET /v1/billing/subscription`.
      properties:
        plan:
          type: string
          description: Identificador único y legible del plan. Coincide con `data.attributes.plan_slug`.
          example: pro
          x-translations:
            en:
              description: Unique, human-readable plan identifier. It matches `data.attributes.plan_slug`.
        used:
          type: integer
          minimum: 0
          description: Validaciones consumidas en el ciclo actual.
          example: 420
          x-translations:
            en:
              description: Validations consumed in the current cycle.
        limit:
          type: integer
          minimum: 0
          description: Límite efectivo de validaciones para el ciclo actual. Tiene el mismo valor que `data.attributes.effective_limit`.
          example: 500
          x-translations:
            en:
              description: Effective validation limit for the current cycle. Same value as `data.attributes.effective_limit`.
        remaining:
          type: integer
          description: Validaciones disponibles en el ciclo actual (`max(0, limit - used)`).
          example: 80
          x-translations:
            en:
              description: Validations available in the current cycle (`max(0, limit - used)`).
        resets_at:
          $ref: '#/components/schemas/TimestampUTC'
        quota_kind:
          type: string
          enum:
            - cycle
            - trial
          description: 'Origen de esta cuota: `cycle` (ciclo de suscripción de siempre) o `trial` (asignación de prueba inicial de una cuenta sin reclamar el plan). Aditivo — un cliente que no lo lea sigue funcionando igual.'
          example: cycle
          x-translations:
            en:
              description: 'Where this quota comes from: `cycle` (the usual subscription cycle) or `trial` (an unclaimed account''s initial trial allowance). Additive — a client that ignores it keeps working the same way.'
        renews:
          type: boolean
          description: '`true` cuando esta cuota se repone al iniciar el siguiente ciclo. `false` para `quota_kind=trial`: `resets_at` es el fin del periodo técnico, no una promesa de más unidades.'
          example: true
          x-translations:
            en:
              description: '`true` when this quota replenishes at the next cycle. `false` for `quota_kind=trial`: `resets_at` is the technical period''s end, not a promise of more units.'
    BillingSubscriptionResponse:
      type: object
      description: Suscripción activa y resumen de cuota devueltos por `GET /v1/billing/subscription`.
      x-translations:
        en:
          description: Active subscription and quota summary returned by `GET /v1/billing/subscription`.
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/BillingSubscriptionResource'
            meta:
              type: object
              description: Plan efectivo, cuota del ciclo y metadatos de la petición.
              x-translations:
                en:
                  description: Effective plan, current-cycle quota, and request metadata.
              properties:
                effective_plan_slug:
                  type: string
                  description: Identificador único y legible del plan. Coincide con `data.attributes.plan_slug`.
                  example: pro
                  x-translations:
                    en:
                      description: Unique, human-readable plan identifier. It matches `data.attributes.plan_slug`.
                has_subscription:
                  type: boolean
                  description: '`true` porque, si no hay una suscripción activa, el servidor asigna el plan predeterminado antes de responder.'
                  example: true
                  x-translations:
                    en:
                      description: '`true` because, if there is no active subscription, the server assigns the default plan before responding.'
                quota:
                  $ref: '#/components/schemas/BillingSubscriptionQuotaMeta'
  responses:
    PayloadTooLarge:
      description: El cuerpo de la petición supera el tamaño máximo admitido (`body_too_large`).
      x-translations:
        en:
          description: The request body exceeds the maximum accepted size (`body_too_large`).
          example:
            errors:
              - detail: The request body is too large.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - status: '413'
                code: body_too_large
                detail: El cuerpo de la petición es demasiado grande.
            meta:
              version: 1.51.0
              request_id: 1a2b3c4d5e6f
    BodyEmpty:
      description: El cuerpo de la petición llegó vacío cuando la operación necesita datos (`body_empty`).
      x-translations:
        en:
          description: The request body arrived empty when the operation requires data (`body_empty`).
          example:
            errors:
              - detail: The request body is empty.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - status: '400'
                code: body_empty
                detail: El cuerpo de la petición está vacío.
            meta:
              version: 1.51.0
              request_id: 2b3c4d5e6f7a
    Unauthorized:
      description: Se requiere autenticación o las credenciales son inválidas
      x-translations:
        en:
          description: Authentication is required or the provided credentials are invalid.
          example:
            errors:
              - detail: Invalid or missing authentication credentials.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - status: '401'
                code: unauthorized
                detail: Credenciales de autenticación ausentes o inválidas.
            meta:
              version: 1.51.0
              request_id: c4d5e6f7a8b9
    RateLimited:
      description: Límite de tasa excedido
      x-translations:
        en:
          description: Rate limit exceeded
          example:
            errors:
              - detail: Rate limit exceeded. Try again in 45 seconds.
      headers:
        Retry-After:
          schema:
            type: integer
            example: 45
          description: Segundos a esperar antes de reintentar. Coincide con la ventana de rate-limit del endpoint (típicamente 60s para listas, 1-5s para operaciones idempotentes en vuelo).
          x-translations:
            en:
              description: 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:
          schema:
            type: integer
          description: Límite de solicitudes configurado para este bucket. Se emite en cada 429 y, en la API autenticada, también en las respuestas correctas, donde es el tope por clave del plan de la cuenta.
          x-translations:
            en:
              description: Configured request cap for this bucket. It is emitted on every 429 and, on the authenticated API, also on successful responses, where it is the per-key cap of the account plan.
        X-RateLimit-Remaining:
          schema:
            type: integer
          description: Solicitudes restantes en la ventana actual. Siempre 0 en el momento del 429; en una respuesta correcta de la API autenticada es lo que queda del tope por clave.
          x-translations:
            en:
              description: Requests remaining in the current window. Always 0 at the moment of the 429; on a successful response of the authenticated API it is what is left of the per-key cap.
        X-RateLimit-Reset:
          schema:
            type: integer
          description: Unix epoch absoluto (segundos) en que se reinicia la ventana. Se emite en cada 429, junto con Retry-After, y en las respuestas correctas de la API autenticada. Puede existir sobreescritura por endpoint (p. ej. `rate_limited_login`).
          x-translations:
            en:
              description: Absolute Unix epoch (seconds) when the window resets. Emitted on every 429, alongside Retry-After, and on successful responses of the authenticated API. Per-endpoint overrides exist (e.g. `rate_limited_login`).
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ErrorResponse'
              - type: object
                properties:
                  meta:
                    type: object
                    properties:
                      retry_after:
                        type: integer
                        description: Segundos a esperar antes de reintentar. Es el mismo valor que la cabecera `Retry-After`, para clientes que no leen cabeceras HTTP.
                        x-translations:
                          en:
                            description: Seconds to wait before retrying. It is the same value as the `Retry-After` header, for clients that do not read HTTP headers.
                        example: 45
          example:
            errors:
              - status: '429'
                code: rate_limit_exceeded
                detail: Límite de peticiones excedido. Inténtalo de nuevo en 45 segundos.
            meta:
              version: 1.51.0
              request_id: f7a8b9c0d1e2
              retry_after: 45
    ValidationQueuedAccepted:
      description: Validación asíncrona aceptada. Se debe sondear `GET /v1/validations/{id}` hasta uno de los estados terminales (`valid`, `not_found`, `cep_unavailable`, `invalid`, `returned`, `failed`, `error`). `meta.next_poll_after_seconds` indica el intervalo recomendado para el primer sondeo.
      x-translations:
        en:
          description: |
            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.
      headers:
        ETag:
          schema:
            type: string
            example: W/"0-queued"
          description: Weak ETag inicial (formato `W/"{etag_version}-{status}"`). Versiones suben con cada transición; envíalo en `If-None-Match` durante el polling para que el server responda 304 cuando nada cambió.
          x-translations:
            en:
              description: Initial weak ETag (format `W/"{etag_version}-{status}"`). The version increments on every state transition; send it in `If-None-Match` during polling so the server can answer 304 when nothing changed.
        Location:
          schema:
            type: string
            example: /v1/validations/64c0e9b8-4a3a-4c1b-9c5d-6e7f8a9b0c1d
          description: Path absoluto (relativo al host de la API) de la validación encolada. Equivale a `/v1/validations/{data.id}`.
          x-translations:
            en:
              description: Absolute path (host-relative) of the queued validation. Equivalent to `/v1/validations/{data.id}`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationQueued'
    InvalidIdempotencyKey:
      description: |
        El valor de la cabecera `Idempotency-Key` no cumple el formato permitido (alfanumérico + `_` + `-`, 1–255 caracteres).
      x-translations:
        en:
          description: |
            The `Idempotency-Key` header value does not meet the allowed format (alphanumeric + `_` + `-`, 1–255 characters).
          example:
            errors:
              - detail: 'Invalid Idempotency-Key format. Allowed: A-Z a-z 0-9 _ - (1-255 chars).'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - status: '400'
                code: invalid_idempotency_key
                detail: Formato de Idempotency-Key inválido. Se admiten A-Z a-z 0-9 _ - (de 1 a 255 caracteres).
            meta:
              version: 1.51.0
              request_id: 3c4d5e6f7a8b
    IdempotencyKeyInProgress:
      description: Una petición con la misma `Idempotency-Key` aún se está procesando. Se debe reintentar después de los segundos que indica la cabecera `Retry-After`.
      x-translations:
        en:
          description: |
            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.
          example:
            errors:
              - detail: A request with this Idempotency-Key is still being processed.
      headers:
        Retry-After:
          schema:
            type: integer
            example: 2
          description: Segundos a esperar antes de reintentar
          x-translations:
            en:
              description: Seconds to wait before retrying
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - status: '409'
                code: idempotency_key_in_progress
                detail: Una petición con esta Idempotency-Key sigue en proceso.
            meta:
              version: 1.51.0
              request_id: 2b3c4d5e6f7a
    ValidationNotModified:
      description: |
        El `etag_version` y `status` no cambiaron desde el `If-None-Match` enviado. La respuesta no tiene cuerpo.
      x-translations:
        en:
          description: |
            The `etag_version` and `status` have not changed since the `If-None-Match` sent. The response has no body.
      headers:
        ETag:
          schema:
            type: string
          description: ETag actual de la fila (igual al `If-None-Match` recibido).
          x-translations:
            en:
              description: Current ETag of the row (same as the `If-None-Match` received).
    Forbidden:
      description: Permisos insuficientes
      x-translations:
        en:
          description: Insufficient permissions.
          example:
            errors:
              - detail: You do not have permission to access this resource.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - status: '403'
                code: forbidden
                detail: No tienes permiso para acceder a este recurso.
            meta:
              version: 1.51.0
              request_id: d5e6f7a8b9c0
    ValidationPurged:
      description: La validación se purgó y este recurso ya no existe (`validation_purged`).
      x-translations:
        en:
          description: The validation was purged and this resource no longer exists (`validation_purged`).
          example:
            errors:
              - detail: The validation was purged and this resource no longer exists.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - status: '410'
                code: validation_purged
                detail: La validación se purgó y este recurso ya no existe.
            meta:
              version: 1.61.0
              request_id: 1a2b3c4d5e6f
    NotFound:
      description: El recurso no existe o no es visible para el cliente
      x-translations:
        en:
          description: The resource does not exist or is not visible to the client
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ValidationError:
      description: Falló la validación de la petición
      x-translations:
        en:
          description: Request validation failed.
          example:
            errors:
              - detail: The fecha field is required.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - status: '422'
                code: validation_error
                detail: El campo fecha es obligatorio.
                source:
                  pointer: /data/attributes/fecha
            meta:
              version: 1.51.0
              request_id: e6f7a8b9c0d1
    InvalidMonth:
      description: Parámetro `month` inválido o ausente
      x-translations:
        en:
          description: Invalid or missing `month` parameter
          example:
            errors:
              - detail: '''month'' is required and must be YYYY-MM.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            errors:
              - status: '400'
                code: invalid_month
                detail: '''month'' es obligatorio y debe tener el formato YYYY-MM.'
            meta:
              version: 1.51.0
              request_id: d5e6f7a8b9c1
