> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# API de Document AI: extracción personalizada de datos

> POST /v3/document-ai/ de Didit extrae campos que definas de PDF o imágenes, con cotejo de nombres y detección de manipulación. $0.20 por documento.

Envía un documento y la lista de campos que quieres leer de él. Didit ejecuta su modelo de extracción predeterminado sobre el archivo, devuelve cada campo con su tipo (`text`, `number` o `date` como `YYYY-MM-DD`), detecta y decodifica los códigos QR y de barras compatibles, comprueba si el archivo ha sido manipulado y, de forma opcional, compara el nombre que figura en el documento con la persona o empresa que esperas. Úsalo para pruebas de fondos, nóminas, extractos bancarios, documentos fiscales, facturas o cualquier documento que no se ajuste a una plantilla fija.

Esta es la versión servidor a servidor del paso alojado de [Document AI](https://docs.didit.me/es/core-technology/document-ai/overview.md). La solicitud es `multipart/form-data`: el archivo va en `document` y las definiciones de campos van en `fields` como una única cadena con un array codificado en JSON.

## Define los campos que se van a extraer

`fields` es un array JSON de 1 a 30 elementos, enviado como una sola cadena. Cada elemento tiene una `key` (letras minúsculas, dígitos y guiones bajos, única dentro de la solicitud), un `name` legible para las personas, una `instruction` que indica al modelo qué buscar, un `type` (`text`, `number` o `date`) y dos indicadores: `required` y `is_full_name`.

```json theme={null}
[
  {
    "key": "account_holder",
    "name": "Account holder",
    "instruction": "Full name of the person or company the account belongs to",
    "type": "text",
    "required": true,
    "is_full_name": true
  },
  {
    "key": "balance",
    "name": "Closing balance",
    "instruction": "Closing balance shown on the statement, as a number without the currency symbol",
    "type": "number",
    "required": true
  },
  {
    "key": "currency",
    "name": "Currency",
    "instruction": "Three-letter currency code of the balance",
    "type": "text"
  },
  {
    "key": "statement_date",
    "name": "Statement date",
    "instruction": "Date the statement was issued",
    "type": "date",
    "required": true
  }
]
```

En una solicitud multipart, el array completo es un único valor del formulario:

```bash theme={null}
curl -X POST https://verification.didit.me/v3/document-ai/ \
  -H "x-api-key: $DIDIT_API_KEY" \
  -F "document=@./bank-statement.pdf" \
  -F 'fields=[{"key":"account_holder","name":"Account holder","instruction":"Full name of the account holder","type":"text","required":true,"is_full_name":true},{"key":"balance","name":"Closing balance","instruction":"Closing balance as a number","type":"number","required":true}]' \
  -F "expected_first_name=Jane" \
  -F "expected_last_name=Doe"
```

La respuesta devuelve los `fields` normalizados y `extracted_data` indexado por tus valores de `key`, con `null` para todo lo que el modelo no haya podido encontrar. `name`, `instruction` y `type` son opcionales en cada elemento (`type` es `text` de forma predeterminada); `key` es obligatorio. Si envías `fields` como claves de formulario con corchetes (`fields[0][key]=...`) o en cualquier formato que no sea un array JSON, se devuelve `400` antes de ejecutar ninguna extracción, por lo que no se factura nada.

## Códigos QR y códigos de barras

Toda respuesta exitosa incluye `document_ai.detected_codes`. Cada elemento contiene:

| Campo | Descripción |
| - | - |
| `type` | Simbología detectada, como `QR_CODE`, `CODE_128`, `EAN_13`, `DATA_MATRIX` o `PDF_417` |
| `page_number` | Número de página, empezando en 1 |
| `position` | Polígono detectado en píxeles de la página renderizada, o `null` cuando no hay una ubicación fiable |
| `x`, `y`, `width`, `height` | Rectángulo delimitador en píxeles de la página renderizada, o `null` cuando no hay una ubicación fiable |
| `page_width`, `page_height` | Dimensiones de la página renderizada utilizadas por las coordenadas |
| `parsed_payload` | Cadena decodificada o valor estructurado, o `null` cuando solo se puede ubicar el código |
| `raw_payload` | Payload sin procesar del decodificador como cadena base64 cuando está disponible; en caso contrario, una cadena vacía |

El array abarca todas las páginas procesadas de un PDF y está vacío cuando no se encuentra ningún código admitido. Didit nunca adivina un payload: los códigos QR dañados pueden devolverse con su ubicación y `parsed_payload: null`, y los fallos en la detección de códigos no hacen fallar la solicitud de Document AI.

## Coincidencia de nombres

Marca como máximo un campo con `is_full_name: true` y envía el nombre de la persona o empresa a la que esperas que pertenezca el documento:

| Si verificas | Envía | El nombre se compara con |
| - | - | - |
| Una persona | `expected_first_name` + `expected_last_name` | el nombre completo de la persona |
| Una empresa | `expected_company_name` (+ `expected_company_country` opcional, un código ISO de 2 letras) | el nombre de la empresa |

Envía un grupo o el otro, no ambos. Cuando se extrae un nombre y hay un nombre esperado, la respuesta incluye `name_match_score` (de 0 a 100) y `name_on_document`. Una puntuación inferior a `document_ai_name_match_score_threshold` (`80` de forma predeterminada) genera la advertencia `DOCUMENT_AI_NAME_MISMATCH` y aplica `document_ai_name_mismatch_action`. Si ningún campo está marcado con `is_full_name`, Didit recurre al primer campo de texto cuya clave parezca un nombre (`name`, `holder`, `full_name`, `account_holder`); marca el campo de forma explícita cuando quieras controlar qué valor se compara. Cuando no se encuentra ningún nombre en el documento, o no se envía ningún nombre esperado, la comprobación del nombre se omite y `name_match_score` es `null`.

## Riesgos y acciones

Cada riesgo de la siguiente tabla corresponde a un parámetro de acción que puedes definir en cada solicitud: `DECLINE`, `REVIEW` o `NO_ACTION`. La acción más estricta que se active determina `document_ai.status` (`Declined` > `In Review` > `Approved`), y cada riesgo activado aparece en `document_ai.warnings`. Los valores predeterminados coinciden con los del paso del flujo alojado; consulta [Reglas y advertencias de Document AI](https://docs.didit.me/es/core-technology/document-ai/warnings-document-ai.md) para saber cómo se detecta cada riesgo.

| Riesgo | Parámetro | Valor predeterminado |
| - | - | - |
| `DOCUMENT_AI_UNREADABLE_DOCUMENT` | `document_ai_unreadable_document_action` | `REVIEW` |
| `DOCUMENT_AI_MISSING_REQUIRED_FIELDS` | `document_ai_missing_required_fields_action` | `REVIEW` |
| `DOCUMENT_AI_SUSPECTED_MANIPULATION` | `document_ai_document_tampering_action` | `DECLINE` |
| `DOCUMENT_AI_NAME_MISMATCH` | `document_ai_name_mismatch_action` | `NO_ACTION` |
| `DOCUMENT_AI_UNSUPPORTED_FILE` | `document_ai_unsupported_file_action` | `REVIEW` |

## Archivos, guardado y facturación

* **Archivos aceptados:** PDF (se admiten varias páginas), JPG, JPEG, PNG, TIFF, WebP o un ZIP que contenga el documento, de hasta 30 MB. Los PDF cifrados devuelven `400`; sube un PDF descifrado o una foto en su lugar.
* **`save_api_request`** (`true` de forma predeterminada): la solicitud, los campos extraídos y los códigos detectados aparecen en la Business Console, en **Manual Checks**, donde tu equipo puede revisarlos. Establécelo en `false` para no guardarlos.
* **`vendor_data`** y **`metadata`** se devuelven sin cambios en la respuesta para que puedas vincular el resultado con tus propios registros.
* **Precio:** \$0.20 por documento, que se factura cuando el documento se procesa y se devuelve un estado. Se aplica el descuento de tu plan. Consulta [Precios](https://docs.didit.me/es/getting-started/pricing.md#precios-de-las-api-independientes-servidor-a-servidor).

> [!NOTE]
>
> ¿Necesitas que el usuario suba el documento por sí mismo, que se recopilen hasta 3 documentos en un mismo flujo, que se traduzca automáticamente la pantalla de carga o que se crucen los campos extraídos con los datos del documento de identidad o del cuestionario? Usa en su lugar el [paso del flujo de Document AI](https://docs.didit.me/es/core-technology/document-ai/overview.md) alojado.


## OpenAPI

````yaml es/openapi-25.json POST /v3/document-ai/
openapi: 3.0.0
info:
  version: 3.0.0
  title: Didit Verification API
  description: API de verificación de identidad. Autentícate con el encabezado x-api-key.
servers:
  - url: https://verification.didit.me
security: []
tags: []
paths:
  /v3/document-ai/:
    post:
      tags:
        - Standalone APIs
      summary: >-
        Document AI (extracción de campos personalizados + comprobaciones de
        manipulación)
      description: >-
        Extrae los campos que **tú** defines de cualquier documento de respaldo
        (nóminas, pruebas de fondos, cartas de origen del patrimonio,
        declaraciones fiscales, contratos, facturas) en una sola llamada. Envía
        el archivo junto con una definición de `fields` (clave, nombre,
        instrucción de extracción, tipo) y recibe valores tipados (`text`,
        `number`, `date` como `YYYY-MM-DD`), una puntuación de coincidencia de
        nombre opcional frente a la persona o empresa esperada, señales de
        manipulación de PDF/EXIF, un `status` `Approved`/`Declined`/`In Review`
        y una lista de `warnings` que explica cada problema detectado. Es el
        equivalente de servidor a servidor del paso de Document AI del flujo de
        verificación alojado (consulta `/core-technology/document-ai/overview`);
        usa el modelo de extracción predeterminado de Didit y no permite elegir
        el proveedor.


        **Latencia.** La extracción se basa en un LLM: las llamadas típicas
        tardan **5–15 segundos** y los PDF de varias páginas, hasta **~30
        segundos**. Configura un tiempo de espera del cliente de **al menos 45
        segundos** y no reintentes antes de que la llamada termine.


        **Formato de la solicitud.** Solo `multipart/form-data`. `fields` es
        **una única cadena con un array codificado en JSON** (1–30 elementos, 16
        KB como máximo); los arrays de formulario con corchetes se rechazan con
        `400`. Cada elemento es `{key, name, instruction, type, required,
        is_full_name}`: `key` es la clave JSON que recibes (`^[a-z0-9_]{1,64}$`,
        única), `instruction` indica al modelo qué debe leer, `type` es `text`
        (predeterminado) | `number` | `date`, `required` genera
        `DOCUMENT_AI_MISSING_REQUIRED_FIELDS` cuando no se puede leer el valor,
        y como máximo un campo puede establecer `is_full_name: true` para marcar
        el valor que se compara con el nombre esperado.


        **Coincidencia de nombre.** Envía `expected_first_name` +
        `expected_last_name` (persona) **o** `expected_company_name` (+
        `expected_company_country` opcional), pero no ambos. El valor del campo
        `is_full_name` (o, si no hay ninguno marcado, el del primer campo de
        texto cuya clave parezca un nombre) se compara de forma aproximada;
        `name_match_score` (0–100) y `name_on_document` se devuelven siempre que
        existen ambos lados, y una puntuación inferior a
        `document_ai_name_match_score_threshold` (80 por defecto) añade
        `DOCUMENT_AI_NAME_MISMATCH`, que se rige por
        `document_ai_name_mismatch_action`.


        **Lógica de decisión.** Cada riesgo se asocia a una acción configurable:
        `DECLINE` → `status: "Declined"`, `REVIEW` → `status: "In Review"`,
        `NO_ACTION` → solo informativo. Los valores predeterminados son los
        mismos que en el paso del flujo: documento ilegible `REVIEW`, campos
        obligatorios ausentes `REVIEW`, sospecha de manipulación `DECLINE`,
        discrepancia de nombre `NO_ACTION`, archivo no admitido `REVIEW`. Un
        documento que no se puede procesar en absoluto devuelve `400`; un
        documento legible pero problemático devuelve `200` con `Declined`/`In
        Review`. Revisa siempre `document_ai.status` y `document_ai.warnings`,
        no solo el código HTTP.


        **Facturación.** Cada respuesta `200` consume un crédito de API de
        Document AI (**$0.20 por documento** según la tarifa de lista), que se
        paga con el crédito de tu plan o con tu saldo recargado. Cuando el saldo
        de la organización no cubre la llamada, el endpoint devuelve `403` antes
        de cualquier procesamiento. Las claves de API de sandbox devuelven una
        respuesta simulada determinista y no generan cargos.


        **Persistencia.** Con `save_api_request=true` (predeterminado), la
        llamada se guarda como una sesión de tipo API: se conservan el
        documento, los valores extraídos, las definiciones de campos y la
        instantánea de la coincidencia de nombre, la sesión aparece en la
        Business Console y `request_id` funciona con `GET
        /v3/session/{sessionId}/decision/`.
      operationId: post_v3document-ai
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - document
                - fields
              properties:
                document:
                  type: string
                  format: binary
                  description: >-
                    El documento que se va a leer. Extensiones permitidas:
                    `tiff`, `jpg`, `jpeg`, `png`, `pdf`, `webp`, `zip`. Tamaño
                    máximo de carga: **30 MB** (los archivos más grandes se
                    rechazan con `400`). Se admiten PDF de varias páginas (con
                    una latencia mayor); los PDF cifrados se rechazan con `400`.
                    Las imágenes se comprimen automáticamente antes del
                    procesamiento.
                fields:
                  type: string
                  description: >-
                    Una cadena con un array codificado en JSON con **1–30**
                    definiciones de campos (máximo 16 KB). Cada elemento: `key`
                    (obligatorio, `^[a-z0-9_]{1,64}$`, único: la clave que se
                    devuelve en `extracted_data`), `name` (≤100 caracteres, se
                    muestra en los mensajes), `instruction` (≤500 caracteres, lo
                    que el modelo debe leer), `type` (`text` por defecto |
                    `number` | `date`), `required` (booleano, por defecto
                    `false`), `is_full_name` (booleano, por defecto `false`,
                    como máximo uno por solicitud). Los arrays de formulario con
                    corchetes, el JSON no válido, las claves duplicadas o más de
                    un `is_full_name` devuelven `400` antes de cualquier
                    procesamiento o facturación.
                  example: >-
                    [{"key": "employer_name", "name": "Employer name",
                    "instruction": "The employer or company issuing the
                    payslip.", "type": "text", "required": true, "is_full_name":
                    false}, {"key": "employee_name", "name": "Employee name",
                    "instruction": "The full name of the employee as printed on
                    the payslip.", "type": "text", "required": true,
                    "is_full_name": true}, {"key": "net_pay", "name": "Net pay",
                    "instruction": "The net amount paid to the employee for the
                    period.", "type": "number", "required": true,
                    "is_full_name": false}, {"key": "pay_date", "name": "Pay
                    date", "instruction": "The payment date shown on the
                    payslip.", "type": "date", "required": false,
                    "is_full_name": false}]
                expected_first_name:
                  type: string
                  description: >-
                    Nombre que se verifica contra el campo de nombre del
                    documento (coincidencia aproximada, con soporte de
                    transliteración). Úsalo junto con `expected_last_name`; no
                    se puede combinar con `expected_company_name`.
                  example: Sophia
                expected_last_name:
                  type: string
                  description: >-
                    Apellido que se verifica contra el campo de nombre del
                    documento, combinado con `expected_first_name` para
                    `name_match_score`.
                  example: Martinez
                expected_company_name:
                  type: string
                  description: >-
                    Nombre de la empresa que se comparará con el campo de nombre
                    del documento (para documentos de empresa). No se puede
                    combinar con `expected_first_name` / `expected_last_name`
                    (no tiene valor de ejemplo a propósito: el ejemplo de
                    solicitud generado usa los campos de persona; sustitúyelos
                    por este al verificar un documento de empresa).
                expected_company_country:
                  type: string
                  description: >-
                    Código de país ISO 3166-1 alfa-2 opcional (dos letras ASCII,
                    p. ej., `FR`) que ajusta la normalización del nombre de la
                    empresa para `expected_company_name`. Solo se usa con
                    `expected_company_name`.
                document_ai_name_match_score_threshold:
                  type: integer
                  minimum: 0
                  maximum: 100
                  default: 80
                  description: >-
                    `name_match_score` mínimo (0–100) para considerar que el
                    nombre coincide. Una puntuación inferior añade
                    `DOCUMENT_AI_NAME_MISMATCH`, que se rige por
                    `document_ai_name_mismatch_action`.
                  example: 80
                document_ai_unreadable_document_action:
                  type: string
                  enum:
                    - DECLINE
                    - REVIEW
                    - NO_ACTION
                  default: REVIEW
                  description: >-
                    Qué hacer cuando no se pudo leer ningún campo del documento
                    (`DOCUMENT_AI_UNREADABLE_DOCUMENT`).
                document_ai_missing_required_fields_action:
                  type: string
                  enum:
                    - DECLINE
                    - REVIEW
                    - NO_ACTION
                  default: REVIEW
                  description: >-
                    Qué hacer cuando no se pudo leer al menos un campo
                    `required` (`DOCUMENT_AI_MISSING_REQUIRED_FIELDS`;
                    `additional_data.missing_fields` los enumera).
                document_ai_document_tampering_action:
                  type: string
                  enum:
                    - DECLINE
                    - REVIEW
                    - NO_ACTION
                  default: DECLINE
                  description: >-
                    Qué hacer cuando el análisis forense de PDF/EXIF sospecha
                    una manipulación: modificación posterior a la firma digital,
                    editor de PDF conocido, manipulación con texto superpuesto,
                    reexportación sospechosa o anomalías en la imagen
                    (`DOCUMENT_AI_SUSPECTED_MANIPULATION`).
                document_ai_name_mismatch_action:
                  type: string
                  enum:
                    - DECLINE
                    - REVIEW
                    - NO_ACTION
                  default: NO_ACTION
                  description: >-
                    Qué hacer cuando `name_match_score` está por debajo del
                    umbral (`DOCUMENT_AI_NAME_MISMATCH`).
                document_ai_unsupported_file_action:
                  type: string
                  enum:
                    - DECLINE
                    - REVIEW
                    - NO_ACTION
                  default: REVIEW
                  description: >-
                    Qué hacer cuando el archivo subido está vacío o no se puede
                    procesar (`DOCUMENT_AI_UNSUPPORTED_FILE`).
                save_api_request:
                  type: boolean
                  default: true
                  description: >-
                    Cuando es `true` (valor predeterminado), guarda la llamada
                    como una sesión de tipo API: visible en la Business Console,
                    recuperable mediante `GET /v3/session/{sessionId}/decision/`
                    con el `request_id` devuelto, y con el documento subido, los
                    valores extraídos, las definiciones de campos y la
                    instantánea de la coincidencia de nombre almacenados. Cuando
                    es `false`, no se almacena nada y `request_id` es un UUID
                    temporal que solo sirve para correlacionar la respuesta; la
                    llamada se factura igualmente.
                  example: true
                vendor_data:
                  type: string
                  description: >-
                    Cadena opaca opcional (tu ID de usuario interno, correo
                    electrónico, UUID…) que se guarda en la sesión persistida y
                    se devuelve en la respuesta. Úsala para relacionar las
                    llamadas a la API con tus propios registros y para filtrar
                    sesiones más adelante.
                  example: user-123
                metadata:
                  type: object
                  additionalProperties: true
                  description: >-
                    Objeto JSON opcional que se almacena con la sesión (cuando
                    `save_api_request=true`) y se devuelve en la respuesta. En
                    solicitudes multipart, envíalo como un campo de cadena
                    codificado en JSON (p. ej.,
                    `metadata={"flow":"onboarding"}`): se convierte en un
                    objeto.
                  example:
                    flow: onboarding
      responses:
        '200':
          description: >-
            Documento procesado. `document_ai.status` es `Approved`, `Declined`
            o `In Review`; cada problema detectado se detalla en
            `document_ai.warnings`. Un documento con problemas también devuelve
            `200`: revisa el cuerpo, no solo el código HTTP. Cuando
            `save_api_request=true`, `request_id` es el ID de la sesión
            guardada.
          content:
            application/json:
              examples:
                Approved:
                  summary: Nómina leída correctamente, nombre coincidente
                  value:
                    request_id: 9d6a3b1e-2f0c-4a8e-9b7d-1c2e3f4a5b6c
                    document_ai:
                      status: Approved
                      extracted_data:
                        employer_name: Acme Payments Ltd
                        employee_name: Sophia Martinez
                        net_pay: 3120.5
                        pay_date: '2026-07-31'
                      detected_codes:
                        - type: CODE_128
                          page_number: 1
                          position:
                            - - 96
                              - 456
                            - - 96
                              - 616
                            - - 1012
                              - 611
                            - - 1015
                              - 456
                          x: 96
                          'y': 456
                          width: 919
                          height: 160
                          page_width: 1200
                          page_height: 1700
                          raw_payload: SU5WLTIwMjYtMDA0Mg==
                          parsed_payload: INV-2026-0042
                      fields:
                        - key: employer_name
                          name: Employer name
                          instruction: The employer or company issuing the payslip.
                          type: text
                          required: true
                          is_full_name: false
                        - key: employee_name
                          name: Employee name
                          instruction: >-
                            The full name of the employee as printed on the
                            payslip.
                          type: text
                          required: true
                          is_full_name: true
                        - key: net_pay
                          name: Net pay
                          instruction: The net amount paid to the employee for the period.
                          type: number
                          required: true
                          is_full_name: false
                        - key: pay_date
                          name: Pay date
                          instruction: The payment date shown on the payslip.
                          type: date
                          required: false
                          is_full_name: false
                      name_match_score: 100
                      name_on_document: Sophia Martinez
                      warnings: []
                    vendor_data: user-123
                    metadata:
                      flow: onboarding
                    created_at: '2026-08-19T16:40:12.318Z'
                In Review — missing required field:
                  summary: 'No se pudo leer `net_pay`; acción predeterminada: REVIEW'
                  value:
                    request_id: 3f2a9c7b-5d4e-4b1a-8c6f-0e9d8c7b6a5f
                    document_ai:
                      status: In Review
                      extracted_data:
                        employer_name: Acme Payments Ltd
                        employee_name: Sophia Martinez
                        net_pay: null
                        pay_date: '2026-07-31'
                      detected_codes: []
                      fields:
                        - key: employer_name
                          name: Employer name
                          instruction: The employer or company issuing the payslip.
                          type: text
                          required: true
                          is_full_name: false
                        - key: employee_name
                          name: Employee name
                          instruction: >-
                            The full name of the employee as printed on the
                            payslip.
                          type: text
                          required: true
                          is_full_name: true
                        - key: net_pay
                          name: Net pay
                          instruction: The net amount paid to the employee for the period.
                          type: number
                          required: true
                          is_full_name: false
                        - key: pay_date
                          name: Pay date
                          instruction: The payment date shown on the payslip.
                          type: date
                          required: false
                          is_full_name: false
                      name_match_score: 100
                      name_on_document: Sophia Martinez
                      warnings:
                        - risk: DOCUMENT_AI_MISSING_REQUIRED_FIELDS
                          feature: DOCUMENT_AI
                          additional_data:
                            missing_fields:
                              - Net pay
                          log_type: warning
                          short_description: Required fields could not be extracted
                          long_description: >-
                            One or more fields configured as required could not
                            be extracted from the uploaded document.
                    vendor_data: null
                    metadata: null
                    created_at: '2026-08-19T16:41:05.002Z'
                Declined — suspected manipulation:
                  summary: >-
                    El análisis forense del PDF detectó un editor conocido;
                    acción predeterminada DECLINE
                  value:
                    request_id: 7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f
                    document_ai:
                      status: Declined
                      extracted_data:
                        employer_name: Acme Payments Ltd
                        employee_name: Sophia Martinez
                        net_pay: 3120.5
                        pay_date: '2026-07-31'
                      detected_codes: []
                      fields:
                        - key: employer_name
                          name: Employer name
                          instruction: The employer or company issuing the payslip.
                          type: text
                          required: true
                          is_full_name: false
                        - key: employee_name
                          name: Employee name
                          instruction: >-
                            The full name of the employee as printed on the
                            payslip.
                          type: text
                          required: true
                          is_full_name: true
                        - key: net_pay
                          name: Net pay
                          instruction: The net amount paid to the employee for the period.
                          type: number
                          required: true
                          is_full_name: false
                        - key: pay_date
                          name: Pay date
                          instruction: The payment date shown on the payslip.
                          type: date
                          required: false
                          is_full_name: false
                      name_match_score: null
                      name_on_document: Sophia Martinez
                      warnings:
                        - risk: DOCUMENT_AI_SUSPECTED_MANIPULATION
                          feature: DOCUMENT_AI
                          additional_data:
                            detection_method: known_editor
                            processed_by_known_editor: ilovepdf
                          log_type: error
                          short_description: Suspected document manipulation
                          long_description: >-
                            The system detected signs of potential manipulation
                            or editing in the uploaded document.
                    vendor_data: null
                    metadata: null
                    created_at: '2026-08-19T16:42:40.771Z'
              schema:
                type: object
                properties:
                  request_id:
                    type: string
                    format: uuid
                    description: >-
                      ID de la sesión guardada cuando `save_api_request=true`
                      (se puede usar con `GET
                      /v3/session/{sessionId}/decision/`); en caso contrario, un
                      UUID de correlación temporal.
                  document_ai:
                    type: object
                    properties:
                      status:
                        type: string
                        enum:
                          - Approved
                          - Declined
                          - In Review
                        description: >-
                          `Approved` cuando ninguna advertencia se resuelve en
                          un rechazo o una revisión; `Declined` cuando al menos
                          un riesgo se asigna a `DECLINE`; en caso contrario,
                          `In Review` cuando al menos uno se asigna a `REVIEW`.
                          La lista `warnings` explica el motivo.
                      extracted_data:
                        type: object
                        additionalProperties: true
                        description: >-
                          Una entrada por cada clave de campo solicitada. Los
                          valores tienen el tipo que indica el `type` del campo:
                          cadenas para `text`, números para `number`, cadenas
                          `YYYY-MM-DD` para `date`, o `null` cuando no se pudo
                          leer el valor.
                        example:
                          employer_name: Acme Payments Ltd
                          employee_name: Sophia Martinez
                          net_pay: 3120.5
                          pay_date: '2026-07-31'
                      detected_codes:
                        type: array
                        description: >-
                          Códigos QR y códigos de barras detectados en todas las
                          páginas procesadas. Vacío cuando no se encuentra
                          ninguno. Un código QR localizado pero que no se puede
                          decodificar tiene `parsed_payload: null`.
                        items:
                          $ref: '#/components/schemas/DocumentAIDetectedCode'
                      fields:
                        type: array
                        description: >-
                          Las definiciones de campo normalizadas que se usaron
                          para esta extracción, devueltas tal cual (también se
                          guardan con la sesión cuando `save_api_request=true`).
                        items:
                          type: object
                          properties:
                            key:
                              type: string
                            name:
                              type: string
                            instruction:
                              type: string
                            type:
                              type: string
                              enum:
                                - text
                                - number
                                - date
                            required:
                              type: boolean
                            is_full_name:
                              type: boolean
                      name_match_score:
                        type: number
                        nullable: true
                        minimum: 0
                        maximum: 100
                        description: >-
                          Similitud difusa (0–100) entre `name_on_document` y el
                          nombre esperado de la persona o empresa. Se devuelve
                          siempre que existan tanto un valor de nombre como un
                          nombre esperado, en coincidencias **y** en
                          discrepancias; `null` en caso contrario.
                        example: 96
                      name_on_document:
                        type: string
                        nullable: true
                        description: >-
                          El valor de nombre extraído del documento (el campo
                          `is_full_name`, o el primer campo de texto con aspecto
                          de nombre) que se comparó con el nombre esperado.
                          `null` cuando no había ningún valor de nombre
                          disponible.
                        example: Sophia Martinez
                      warnings:
                        type: array
                        description: >-
                          Vacío en una aprobación sin incidencias. Cada entrada
                          explica un riesgo detectado; las entradas con
                          `log_type: "error"` establecen `status` en `Declined`,
                          y las entradas con `log_type: "warning"` lo establecen
                          en `In Review`.
                        items:
                          type: object
                          properties:
                            risk:
                              type: string
                              enum:
                                - DOCUMENT_AI_UNREADABLE_DOCUMENT
                                - DOCUMENT_AI_MISSING_REQUIRED_FIELDS
                                - DOCUMENT_AI_SUSPECTED_MANIPULATION
                                - DOCUMENT_AI_NAME_MISMATCH
                                - DOCUMENT_AI_UNSUPPORTED_FILE
                              description: >-
                                Código de riesgo legible por máquina. Cada uno
                                se controla con la opción de solicitud
                                `document_ai_*_action` correspondiente.
                              example: DOCUMENT_AI_MISSING_REQUIRED_FIELDS
                            feature:
                              type: string
                              enum:
                                - DOCUMENT_AI
                              description: >-
                                Función que emitió la advertencia. En este
                                endpoint siempre es `DOCUMENT_AI`.
                            additional_data:
                              type: object
                              nullable: true
                              additionalProperties: true
                              description: >-
                                Contexto adicional del riesgo:
                                `{"missing_fields": ["Net pay"]}` para campos
                                obligatorios ausentes, `{"name_on_document",
                                "score", "threshold"}` para una discrepancia de
                                nombre, y el hallazgo forense
                                (`detection_method`, datos del productor o
                                editor) para una sospecha de manipulación.
                                `null` cuando no hay nada que añadir.
                              example:
                                missing_fields:
                                  - Net pay
                            log_type:
                              type: string
                              enum:
                                - error
                                - warning
                                - information
                              description: >-
                                Gravedad derivada de la acción configurada:
                                `error` (DECLINE), `warning` (REVIEW),
                                `information` (NO_ACTION).
                            short_description:
                              type: string
                              description: Descripción legible del riesgo en una línea.
                              example: Required fields could not be extracted
                            long_description:
                              type: string
                              description: >-
                                Explicación legible, completada con
                                `additional_data` cuando corresponde.
                  vendor_data:
                    type: string
                    nullable: true
                    description: Devuelve el `vendor_data` que enviaste, o `null`.
                  metadata:
                    type: object
                    nullable: true
                    additionalProperties: true
                    description: Devuelve el objeto `metadata` que enviaste, o `null`.
                  created_at:
                    type: string
                    format: date-time
                    description: >-
                      Fecha y hora en que se procesó la solicitud (UTC, ISO
                      8601).
        '400':
          description: >-
            Error de validación o de procesamiento. Los problemas a nivel de
            campo devuelven el formato estándar de DRF (un array de mensajes por
            cada campo afectado). Los documentos cifrados o ilegibles y los
            fallos de extracción devuelven `{"error": ...}`.
          content:
            application/json:
              examples:
                Missing document:
                  summary: '`document` no se incluyó en los datos del formulario'
                  value:
                    document:
                      - No file was submitted.
                Invalid fields payload:
                  summary: '`fields` no es una única cadena de array codificada en JSON'
                  value:
                    fields:
                      - fields must be a valid JSON array string.
                Duplicate field keys:
                  summary: Dos elementos comparten la misma `key`
                  value:
                    fields:
                      - Every fields item must have a unique key.
                Conflicting expectations:
                  summary: >-
                    Datos esperados de la persona y de la empresa enviados
                    juntos
                  value:
                    non_field_errors:
                      - >-
                        Provide either person expected-name fields or
                        expected_company_name, not both.
                Invalid metadata:
                  summary: '`metadata` no es un objeto JSON (o contiene un carácter NUL)'
                  value:
                    metadata:
                      - Metadata must be a JSON object.
                Oversized vendor_data:
                  summary: '`vendor_data` supera el límite de 2000 bytes'
                  value:
                    vendor_data:
                      - vendor_data is too long (maximum 2000 bytes).
                Encrypted PDF:
                  summary: PDF protegido con contraseña
                  value:
                    error: >-
                      The PDF is encrypted. Please upload a decrypted PDF or a
                      photo instead.
                Extraction failed:
                  summary: El proceso de extracción no pudo procesar el documento
                  value:
                    error:
                      - Error extracting Document AI information
              schema:
                type: object
                additionalProperties: true
        '403':
          description: >-
            Permiso denegado. Se devuelve cuando falta el encabezado `x-api-key`
            y también cuando el saldo de la organización que realiza la llamada
            no la cubre; una clave incorrecta, revocada o expirada devuelve
            `401`. Si falta la clave, se devuelve `403` con `{"detail": ...}`;
            una clave incorrecta, revocada o expirada devuelve `401`. La falta
            de créditos (y las organizaciones con facturación por contrato cuya
            tabla de tarifas no incluye una entrada para la API de Document AI)
            devuelve `403` con `{"error": ...}` antes de que se procese ningún
            documento.
          content:
            application/json:
              examples:
                Missing or invalid API key:
                  summary: >-
                    Falta el encabezado `x-api-key`, o la clave no es válida o
                    está revocada
                  value:
                    detail: You do not have permission to perform this action.
                Not enough credits:
                  summary: El saldo de la organización no cubre la llamada
                  value:
                    error: >-
                      You don't have enough credits to perform this request.
                      Please top up at https://business.didit.me
                Contract rate missing:
                  summary: >-
                    Organización con facturación por contrato cuya tabla de
                    tarifas no tiene una entrada para la API de Document AI
                  value:
                    error: >-
                      Document AI API is not included in your contract rate
                      card. Contact support@didit.me to enable it.
              schema:
                type: object
                properties:
                  detail:
                    type: string
                  error:
                    type: string
        '429':
          description: >-
            Límite de solicitudes superado. Todos los endpoints
            POST/PATCH/DELETE comparten un presupuesto de 300 solicitudes de
            escritura por minuto por clave de API. La respuesta incluye los
            encabezados `X-RateLimit-Limit`, `X-RateLimit-Remaining`,
            `X-RateLimit-Reset` y `Retry-After`.
          content:
            application/json:
              examples:
                Write rate limit exceeded:
                  summary: Más de 300 solicitudes de escritura en el minuto actual
                  value:
                    detail: >-
                      Write request rate limit exceeded. You can make up to 300
                      requests per minute.
              schema:
                type: object
                properties:
                  detail:
                    type: string
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: python
          label: Python
          source: |-
            from didit import Didit

            # Reads DIDIT_API_KEY.
            client = Didit()

            result = client.document_ai.analyze(
                document=open("document.pdf", "rb"),
                fields="[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]",
            )
            print(result)
        - lang: javascript
          label: Node.js
          source: |-
            import Didit, { fileFromPath } from "@didit-protocol/sdk-node";

            // Reads DIDIT_API_KEY.
            const didit = new Didit();

            const result = await didit.documentAi.analyze({
              document: await fileFromPath("document.pdf"),
              fields: "[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]",
            });
            console.log(result);
        - lang: go
          label: Go
          source: "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.DocumentAi.Analyze(ctx, didit.DocumentAiAnalyzeParams{\n\tDocument: didit.FileFromPath(\"document.pdf\"),\n\tFields:   \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)"
        - lang: java
          label: Java
          source: >-
            import java.nio.file.Path;

            import me.didit.Didit;

            import me.didit.types.DocumentAiAnalyzeParams;


            // Reads DIDIT_API_KEY.

            Didit client = new Didit();


            var result =
            client.documentAi().analyze(DocumentAiAnalyzeParams.builder()
                    .document(Path.of("document.pdf"))
                    .fields("[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]")
                    .build());
            System.out.println(result);
        - lang: php
          label: PHP
          source: |-
            <?php

            require 'vendor/autoload.php';

            use Didit\DiditClient;
            use Didit\File;

            // Reads DIDIT_API_KEY.
            $didit = new DiditClient();

            $result = $didit->documentAi->analyze([
                'document' => File::fromPath('document.pdf'),
                'fields' => '[{"key": "employer_name", "name": "Employer name", "instruction": "The employer or company issuing the payslip.", "type": "text", "required": true, "is_full_name": false}, {"key": "employee_name", "name": "Employee name", "instruction": "The full name of the employee as printed on the payslip.", "type": "text", "required": true, "is_full_name": true}, {"key": "net_pay", "name": "Net pay", "instruction": "The net amount paid to the employee for the period.", "type": "number", "required": true, "is_full_name": false}, {"key": "pay_date", "name": "Pay date", "instruction": "The payment date shown on the payslip.", "type": "date", "required": false, "is_full_name": false}]',
            ]);
            print_r($result->toArray());
        - lang: ruby
          label: Ruby
          source: |-
            require "didit"

            # Reads DIDIT_API_KEY.
            didit = Didit::Client.new

            result = didit.document_ai.analyze(
              document: Pathname("document.pdf"),
              fields: "[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]"
            )
            puts result.to_h
        - lang: csharp
          label: .NET
          source: >-
            using System;

            using System.Text.Json;

            using Didit;


            // Reads DIDIT_API_KEY.

            using var client = new DiditClient();


            var result = await client.DocumentAi.AnalyzeAsync(new
            DocumentAiAnalyzeParams

            {
                Document = FileUpload.FromPath("document.pdf"),
                Fields = "[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]",
            });

            Console.WriteLine(JsonSerializer.Serialize(result));
components:
  schemas:
    DocumentAIDetectedCode:
      type: object
      required:
        - type
        - page_number
        - position
        - x
        - 'y'
        - width
        - height
        - page_width
        - page_height
        - raw_payload
        - parsed_payload
      properties:
        type:
          type: string
          description: >-
            Simbología detectada, por ejemplo `QR_CODE`, `CODE_128`, `EAN_13`,
            `DATA_MATRIX` o `PDF_417`.
        page_number:
          type: integer
          minimum: 1
          description: Número de página, empezando en 1.
        position:
          type: array
          nullable: true
          description: >-
            Polígono detectado en coordenadas de píxeles de la página
            renderizada. Es `null` cuando no hay una ubicación fiable
            disponible.
          items:
            type: array
            minItems: 2
            maxItems: 2
            items:
              type: integer
        x:
          type: integer
          nullable: true
          description: >-
            Borde izquierdo del cuadro delimitador, en píxeles de la página
            renderizada.
        'y':
          type: integer
          nullable: true
          description: >-
            Borde superior del cuadro delimitador, en píxeles de la página
            renderizada.
        width:
          type: integer
          nullable: true
          description: Ancho del cuadro delimitador en píxeles de la página renderizada.
        height:
          type: integer
          nullable: true
          description: Altura del cuadro delimitador en píxeles de la página renderizada.
        page_width:
          type: integer
          description: Ancho de la página renderizada en píxeles.
        page_height:
          type: integer
          description: Altura de la página renderizada en píxeles.
        raw_payload:
          type: string
          description: >-
            Payload del decodificador sin procesar como cadena base64 cuando
            está disponible; en caso contrario, una cadena vacía.
        parsed_payload:
          nullable: true
          description: >-
            Cadena decodificada o valor estructurado. `null` cuando el código se
            localizó pero no se pudo decodificar.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        La clave de API de tu aplicación, que encontrarás en Developers -> API
        keys en la Business Console. La clave principal tiene acceso completo.
        Una clave con nombre puede tener un alcance limitado: ninguno, lectura o
        escritura por recurso, limitada a algunos flujos o a sesiones aprobadas,
        a una lista de direcciones IP y a una fecha de caducidad. 401 significa
        que la clave es incorrecta, se revocó o expiró; 403 significa que no se
        envió ninguna clave, que la clave no tiene acceso a este recurso o
        acción, o que la solicitud provino de una dirección fuera de su lista de
        IP; 404 en una ruta de sesión significa que la sesión está fuera de los
        flujos o estados de la clave. Una clave sin acceso a contenido
        multimedia recibe las URL de imágenes, videos y PDF como null, y una
        clave sin permiso de escritura en sesiones recibe los enlaces y tokens
        de sesión como null. Consulta https://docs.didit.me/console/api-keys.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.