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

# Insertar contactos en un lote

> Añade uno o varios contactos a un lote existente del marcador.

Añade hasta 1000 contactos a un lote existente y deja la operación en cola para su procesamiento. El mismo endpoint admite dos formatos de body:

* **Un contacto:** envía sus propiedades directamente en la raíz del body.
* **Varios contactos:** envía un objeto con el array `contacts`. Cada elemento del array es un contacto completo.

No envíes un contacto individual dentro de `contacts` salvo que quieras utilizar el formato de carga múltiple.

<Info>
  `identifier` es opcional, aunque se recomienda enviarlo para relacionar el resultado con el registro del sistema de origen. Si se omite, el servicio genera uno automáticamente.
</Info>

Si omites `scheduledAt`, el contacto entra en la selección del siguiente ciclo y se marca a la brevedad posible. Si lo incluyes, debe ser una fecha futura expresada en UTC y fija cuándo se realizará el primer intento.

Los campos `contactPriority` y `customFields` son opcionales. `contactPriority` ordena el contacto frente a los demás contactos del lote: cuanto menor sea su valor, antes se seleccionará. `customFields` permite guardar datos propios que viajarán con el contacto.

<Warning>
  Envía los teléfonos con el código de país y únicamente dígitos, sin el signo `+`. Un número que contenga `+` se rechaza con una respuesta `400`.
</Warning>

## Tratamiento de contactos duplicados

`onDuplicate` y `resetAttempts` son campos opcionales de cada contacto:

* Si el contacto ya existe y omites `onDuplicate`, sus datos se actualizan, se trata como duplicado y no se vuelve a poner en marcación.
* Envía `"onDuplicate": "recontact"` cuando quieras volver a poner en marcación un contacto cuyo `identifier` ya existe en el lote.
* Añade `"resetAttempts": true` si, además de recontactarlo, quieres reiniciar sus contadores de intentos.

En una carga múltiple, estos campos se incluyen dentro del objeto del contacto al que deben aplicarse, no en la raíz del body. Por tanto, una misma petición puede solicitar la reactivación de algunos contactos y aplicar el tratamiento habitual a otros.

<Tip>
  Usa `resetAttempts` junto con `onDuplicate: "recontact"`. Si no necesitas reiniciar los intentos, puedes enviar solo `onDuplicate`.
</Tip>

## Ejemplos de solicitud

<CodeGroup>
  ```bash Un contacto theme={null}
  curl --request POST \
    --url 'https://api.backend.inconcertcc.com/autocontact/api/v1/batches/e8b63614-f5db-4fd4-84ab-258b7070262e/contacts' \
    --header 'Content-Type: application/json' \
    --header 'apikey: TU_API_KEY' \
    --data '{
      "identifier": "CLI-001",
      "name": "Alex Romero",
      "addresses": [
        {
          "address": "34910000001",
          "type": "phone",
          "priority": 1
        }
      ],
      "contactPriority": 0,
      "customFields": {
        "campaign": "renovaciones",
        "source": "crm"
      },
      "onDuplicate": "recontact",
      "resetAttempts": true
    }'
  ```

  ```bash Varios contactos theme={null}
  curl --request POST \
    --url 'https://api.backend.inconcertcc.com/autocontact/api/v1/batches/e8b63614-f5db-4fd4-84ab-258b7070262e/contacts' \
    --header 'Content-Type: application/json' \
    --header 'apikey: TU_API_KEY' \
    --data '{
      "contacts": [
        {
          "identifier": "CLI-002",
          "name": "Andrea Torres",
          "addresses": [
            {
              "address": "34910000002",
              "type": "phone",
              "priority": 1
            }
          ],
          "contactPriority": 0,
          "customFields": {
            "campaign": "renovaciones",
            "source": "crm"
          },
          "onDuplicate": "recontact",
          "resetAttempts": true
        },
        {
          "identifier": "CLI-003",
          "name": "Daniel Vega",
          "addresses": [
            {
              "address": "34910000003",
              "type": "phone",
              "priority": 1
            },
            {
              "address": "daniel.vega@example.com",
              "type": "email",
              "priority": 2
            }
          ],
          "contactPriority": 1,
          "customFields": {
            "campaign": "renovaciones",
            "source": "crm"
          }
        }
      ]
    }'
  ```
</CodeGroup>

En el ejemplo múltiple, el primer contacto solicita recontacto y reinicio de intentos. El segundo omite ambos campos, por lo que conserva el tratamiento de duplicados habitual del lote.

<ResponseExample>
  ```json Response theme={null}
  {
    "operationId": "626343ed-dbff-4644-a8ce-cee2471b2106",
    "operationUrl": "/autocontact/api/v1/batches/operations/626343ed-dbff-4644-a8ce-cee2471b2106",
    "status": "queued",
    "contactsReceived": 1,
    "chunksPublished": 1,
    "message": "Contacts queued for processing"
  }
  ```
</ResponseExample>

<Info>
  Una respuesta con `status: "queued"` confirma que los contactos quedaron en cola. Usa `operationId` para identificar la operación devuelta por la API.
</Info>


## OpenAPI

````yaml api-reference/dialer/openapi.yaml POST /batches/{batchId}/contacts
openapi: 3.1.0
info:
  title: AutoContact API
  version: final
  summary: Gestión de lotes de marcación automática y carga de contactos.
  description: >
    API de AutoContact: gestión de lotes de marcación automática y de los
    contactos que los integran.


    Todos los recursos se publican bajo `/autocontact/api/v1/`.


    ## Autenticación


    Las peticiones se autentican con el header `apikey`. La misma credencial
    permite operar los lotes

    de la cuenta a los que tenga acceso. El servicio deriva la cuenta de esa
    credencial y verifica que

    el recurso solicitado le pertenezca.


    Un lote que no pertenece a la cuenta responde `404`, igual que un lote
    inexistente.


    ## Operaciones asíncronas


    Algunas operaciones responden `202` en lugar de `200`: la petición se acepta
    y el trabajo continúa

    en segundo plano. Es el caso de la carga de contactos, el arranque y la
    eliminación de un lote.


    Esas respuestas incluyen un `operationId` que se consulta en

    `GET /batches/operations/{operationId}` para conocer el resultado.


    `POST /batches` no genera una operación: devuelve directamente el `batchId`,
    el estado `created` y

    el origen del lote, sin `operationId`.


    Tiempo de retención de las operaciones:


    | Tipo de operación | Disponible durante |

    |---|---|

    | Carga de contactos | 6 horas |

    | Ciclo de vida del lote (arranque, archivado, eliminación) | 1 hora, y 10
    minutos desde que finaliza |


    ## Reintentos seguros con `Idempotency-Key`


    Las operaciones de escritura aceptan el header opcional `Idempotency-Key`.
    Resuelve el caso en que

    se produce un error de red después de que la petición llegó al servicio: sin
    este header no es

    posible determinar si el trabajo se procesó, y reintentar podría duplicar
    llamadas telefónicas.


    Con el header, un reintento que use la misma clave y el mismo cuerpo
    devuelve la respuesta original

    sin procesar nada de nuevo. Las claves se conservan 24 horas.


    Recomendación de uso: generar un identificador único (por ejemplo un UUID)
    antes del primer intento

    y reutilizarlo solo en los reintentos de esa misma operación.


    Requisitos de la clave: entre 1 y 255 caracteres, solo ASCII imprimible. Una
    clave que no cumpla

    estos requisitos se rechaza con `400 INVALID_IDEMPOTENCY_KEY` en lugar de
    ignorarse, para evitar

    procesar la petición sin la protección que el cliente espera tener.


    ### Headers de respuesta


    | Header | Cuándo aparece | Significado |

    |---|---|---|

    | `Idempotency-Replayed: true` | En un reintento | La respuesta corresponde
    a la petición original; no se procesó de nuevo |

    | `Retry-After` | Junto a `409 IDEMPOTENCY_KEY_IN_FLIGHT` | Segundos a
    esperar antes de reintentar |

    | `Idempotency-Status: bypassed` | Excepcionalmente | La petición se procesó
    **sin** protección de idempotencia |


    El header `Idempotency-Status: bypassed` indica que el servicio de
    idempotencia no estaba

    disponible y la petición se procesó igualmente, para no interrumpir la
    operación. La respuesta es

    válida, pero esa petición concreta no quedó protegida: si no se recibe
    respuesta, se recomienda

    verificar el estado antes de reintentar.


    Las respuestas con error `5xx` no se almacenan, de modo que un reintento con
    la misma clave vuelve a

    procesar la petición.
servers:
  - url: https://api.backend.inconcertcc.com/autocontact/api/v1
    description: API de Inagent
security:
  - ApiKeyAuth: []
tags:
  - name: Lotes
    description: |
      Creación, consulta y control de lotes de marcación.
  - name: Contactos
    description: |
      Carga de contactos en un lote, reagendamiento y anulación.
  - name: Direcciones bloqueadas
    description: |
      Direcciones excluidas de la marcación en toda la cuenta.
  - name: Operaciones
    description: |
      Seguimiento de las operaciones asíncronas que devuelven `202`.
paths:
  /batches/{batchId}/contacts:
    parameters:
      - $ref: '#/components/parameters/BatchId'
    post:
      tags:
        - Contactos
      summary: Carga uno o varios contactos en un lote
      description: >
        Agrega contactos a un lote existente. Responde `202`: la inserción se
        realiza en segundo plano.


        Se recomienda enviar el header `Idempotency-Key`. Esta operación encola
        llamadas telefónicas, por

        lo que un reintento sin protección puede contactar dos veces a las
        mismas personas.


        ## Formato del cuerpo


        Admite dos formas equivalentes: un contacto individual en la raíz del
        cuerpo, o varios contactos

        en el campo `contacts`.


        ## Límites


        * Máximo 1000 contactos por petición.

        * El total de contactos activos de la cuenta tiene un límite propio. Al
        superarlo, la respuesta
          es `400` con `code: CONTACT_LIMIT_EXCEEDED` e indica cuántos contactos pueden añadirse.

        ## El campo `identifier`


        Es el identificador del contacto en el sistema de origen y permite
        conciliar los resultados. Si

        se omite, el servicio genera uno automáticamente, pero en ese caso no
        habrá forma de relacionar

        el contacto con el registro original.


        ## Resultado de la carga


        La respuesta `202` confirma que los contactos se aceptaron para
        procesamiento, no que se hayan

        insertado. Una carga puede completarse parcialmente: un contacto puede
        descartarse por duplicado

        según la política de deduplicación configurada en el lote.


        Para conocer el resultado, consulte

        `GET /batches/operations/{operationId}` con el identificador que
        devuelve esta

        respuesta. La URL completa también viene en el header `Location`.


        Las validaciones de formato (campos obligatorios, formato de teléfono y
        correo, prioridades) se

        aplican antes de encolar. Un solo contacto inválido rechaza la petición
        completa con `400`, de

        modo que se pueda corregir el envío antes de procesarlo.


        La respuesta `400` de validación incluye todos los errores detectados,
        cada uno con su campo, un

        código estable y datos adicionales del problema.


        ## Agendar la primera llamada


        El campo `scheduledAt` es opcional y determina cuándo se intentará
        contactar por primera vez. Si

        se omite, el contacto entra en el siguiente ciclo de marcación.


        La fecha debe ser futura, expresarse en UTC y estar dentro de los
        próximos 30 días.


        ## Contactos duplicados


        Si el `identifier` ya existe y se omite `onDuplicate`, los datos se
        actualizan, el contacto se

        trata como duplicado y no vuelve a ponerse en marcación. Con
        `onDuplicate: recontact`, el mismo

        request permite insertar el contacto cuando no existe o volver a
        llamarlo cuando ya estaba en

        el lote.


        ## Formato del teléfono


        Los números de teléfono deben incluir el código de país y contener
        únicamente dígitos, sin el

        signo `+`. Un número que contenga `+` se rechaza con `400`.
      operationId: insertContacts
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/ContactInput'
                - type: object
                  required:
                    - contacts
                  properties:
                    contacts:
                      type: array
                      minItems: 1
                      maxItems: 1000
                      items:
                        $ref: '#/components/schemas/ContactInput'
            examples:
              unContacto:
                summary: Un contacto individual, en la raíz del cuerpo
                value:
                  identifier: CLI-001
                  name: Juan Pérez
                  addresses:
                    - address: '5491155551234'
                      type: phone
                      priority: 1
                    - address: juan@example.com
                      type: email
                      priority: 2
                  contactPriority: 0
                  customFields:
                    campaign: verano2025
                    source: crm
              variosContactos:
                summary: Varios contactos, en el campo `contacts`
                value:
                  contacts:
                    - identifier: CLI-001
                      name: Juan Pérez
                      addresses:
                        - address: '5491155551234'
                          type: phone
                          priority: 1
                      contactPriority: 0
                    - identifier: CLI-002
                      name: María López
                      addresses:
                        - address: '5491155555678'
                          type: phone
                          priority: 1
                      contactPriority: 1
              conAgendamiento:
                summary: Con la primera llamada programada
                value:
                  identifier: CLI-003
                  name: Carlos Gómez
                  addresses:
                    - address: '5491155559999'
                      type: phone
                      priority: 1
                  scheduledAt: '2026-09-23T08:41:43.522Z'
                  contactPriority: 0
              recontactarDuplicado:
                summary: Volver a llamar a un contacto duplicado
                value:
                  identifier: CLI-003
                  name: Juan Pérez 3
                  addresses:
                    - address: '5491155551233'
                      type: phone
                      priority: 1
                  customFields:
                    campaign: verano2025
                    source: crm
                    source2: crm2
                  contactPriority: 0
                  onDuplicate: recontact
                  resetAttempts: true
      responses:
        '202':
          description: |
            Contactos aceptados. La inserción se realiza en segundo plano.
          headers:
            Location:
              description: >
                URL de la operación asociada a esta carga, donde se consulta su
                resultado.
              schema:
                type: string
              example: >-
                /autocontact/api/v1/batches/operations/9f1c7e2a-4b3d-4a1e-9c8f-2d5b6e7a8c90
            Idempotency-Replayed:
              $ref: '#/components/headers/IdempotencyReplayed'
            Idempotency-Status:
              $ref: '#/components/headers/IdempotencyStatus'
          content:
            application/json:
              schema:
                type: object
                required:
                  - operationId
                  - operationUrl
                  - status
                  - contactsReceived
                  - chunksPublished
                properties:
                  operationId:
                    type: string
                    format: uuid
                    description: |
                      Identifica esta carga. Se resuelve en
                      `GET /batches/operations/{operationId}`.
                    example: 9f1c7e2a-4b3d-4a1e-9c8f-2d5b6e7a8c90
                  operationUrl:
                    type: string
                    description: >
                      URL del recurso de operación, la misma que viene en el
                      header `Location`.
                    example: >-
                      /autocontact/api/v1/batches/operations/9f1c7e2a-4b3d-4a1e-9c8f-2d5b6e7a8c90
                  status:
                    type: string
                    enum:
                      - queued
                  contactsReceived:
                    type: integer
                    description: Cuántos contactos se recibieron y validaron.
                    example: 150
                  chunksPublished:
                    type: integer
                    description: >
                      Número de bloques en los que se dividió la carga. La
                      operación se considera

                      finalizada cuando todos se han procesado.
                    example: 3
                  message:
                    type: string
              example:
                operationId: 9f1c7e2a-4b3d-4a1e-9c8f-2d5b6e7a8c90
                operationUrl: >-
                  /autocontact/api/v1/batches/operations/9f1c7e2a-4b3d-4a1e-9c8f-2d5b6e7a8c90
                status: queued
                contactsReceived: 150
                chunksPublished: 3
                message: Contacts queued for processing
        '400':
          description: >
            Cuerpo vacío, más de 1000 contactos, un contacto que no pasó la
            validación de formato,

            límite de contactos de la cuenta excedido, o `Idempotency-Key` con
            formato incorrecto.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ValidationError'
                  - $ref: '#/components/schemas/ContactLimitError'
                  - $ref: '#/components/schemas/IdempotencyKeyError'
              examples:
                sinContactos:
                  summary: Ningún contacto en el cuerpo
                  value:
                    message: Se requiere al menos un contacto
                    details: []
                demasiados:
                  summary: Más de 1000 contactos
                  value:
                    message: El máximo de contactos por request es 1000
                    details:
                      - Se recibieron 1500 contactos
                validacion:
                  summary: Uno o más contactos inválidos
                  value:
                    code: VALIDATION_FAILED
                    message: Validación fallida en uno o más contactos
                    details:
                      - index: 3
                        field: contacts[3].addresses[0].address
                        code: INVALID_PHONE_FORMAT
                        reason: >-
                          addresses[0]: formato inválido para tipo 'phone':
                          '123' (debe contener solo dígitos, 7-15 caracteres)
                        meta:
                          received: '123'
                          expected: digits only, 7-15 characters
                        error: >-
                          addresses[0]: formato inválido para tipo 'phone':
                          '123' (debe contener solo dígitos, 7-15 caracteres)
                      - index: 7
                        field: contacts[7].name
                        code: REQUIRED
                        reason: '''name'' es obligatorio y no puede estar vacío'
                        error: '''name'' es obligatorio y no puede estar vacío'
                limite:
                  summary: Límite de contactos de la cuenta excedido
                  value:
                    code: CONTACT_LIMIT_EXCEEDED
                    message: >-
                      Contact limit exceeded. You are trying to add 500
                      contacts, but you can only add 120 more.
                    maxContacts: 10000
                    currentActive: 9880
                    attempting: 500
                    exceededBy: 380
                claveInvalida:
                  summary: '`Idempotency-Key` mal formada'
                  value:
                    code: INVALID_IDEMPOTENCY_KEY
                    message: >-
                      Invalid Idempotency-Key header: it must be at most 255
                      characters
                    details:
                      - field: Idempotency-Key
                        code: TOO_LONG
                        reason: must be at most 255 characters
                        meta:
                          maxLength: 255
                          receivedLength: 300
                    meta:
                      rejection: TOO_LONG
                      maxLength: 255
                      receivedLength: 300
        '404':
          description: |
            El lote no existe o no pertenece a la cuenta.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleError'
              example:
                message: Batch not found
        '409':
          description: |
            El lote está archivado, o existe un conflicto de idempotencia.
          headers:
            Retry-After:
              description: >
                Segundos a esperar antes de reintentar. Solo con
                `IDEMPOTENCY_KEY_IN_FLIGHT`.
              schema:
                type: integer
              example: 2
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/SimpleError'
                  - $ref: '#/components/schemas/IdempotencyConflictError'
              examples:
                archivado:
                  summary: Lote archivado
                  value:
                    message: Cannot modify an archived batch
                enVuelo:
                  summary: Petición idéntica todavía en proceso
                  value:
                    code: IDEMPOTENCY_KEY_IN_FLIGHT
                    message: >-
                      A request with this Idempotency-Key is still being
                      processed. Retry shortly.
                    meta:
                      retryAfterSeconds: 2
                claveReusada:
                  summary: La clave ya se utilizó con otro cuerpo
                  value:
                    code: IDEMPOTENCY_KEY_REUSED
                    message: >-
                      This Idempotency-Key was already used with a different
                      request body.
        '500':
          $ref: '#/components/responses/InternalError'
components:
  parameters:
    BatchId:
      name: batchId
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Id del lote.
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 255
        pattern: ^[\x20-\x7E]+$
      description: >
        Clave para hacer el reintento seguro. Opcional.


        Un UUID por operación lógica, reusado en los reintentos de ese mismo
        envío. Ver "Reintentos

        seguros" en la descripción de la API.
      example: 3f8a1b2c-5d6e-4f70-8a9b-1c2d3e4f5a6b
  schemas:
    ContactInput:
      type: object
      required:
        - name
        - addresses
      description: >
        Contacto que se va a cargar en el lote.


        El campo `identifier` no es obligatorio: si se omite, el servicio genera
        uno automáticamente. Se

        recomienda indicarlo siempre que exista, ya que es lo que permite
        relacionar el contacto con el

        registro del sistema de origen.
      properties:
        identifier:
          type: string
          description: >
            Identificador del contacto en el sistema de origen. Si se omite, el
            servicio genera uno

            automáticamente.
          example: CLI-001
        name:
          type: string
          description: Nombre del contacto. Obligatorio y no vacío.
          example: Juan Pérez
        addresses:
          type: array
          minItems: 1
          description: >
            Direcciones por las que intentar el contacto, con su orden de
            marcación.
          items:
            $ref: '#/components/schemas/ContactAddress'
        customFields:
          type: object
          additionalProperties: true
          description: >
            Datos adicionales que se almacenan con el contacto. No intervienen
            en la marcación y quedan

            disponibles para el asistente y para la consulta posterior del
            contacto.
          example:
            campaign: verano2025
            source: crm
        contactPriority:
          type: integer
          description: >
            Prioridad del contacto frente a los demás contactos del lote. Cuanto
            menor sea el valor,

            antes se seleccionará para la marcación.
          example: 0
        scheduledAt:
          type: string
          format: date-time
          description: >
            Cuándo intentar la PRIMERA marcación. Debe ser una fecha futura en
            UTC y estar dentro de

            los próximos 30 días.


            Sin este campo el contacto entra a la selección del siguiente ciclo.
          example: '2026-09-23T08:41:43.522Z'
        onDuplicate:
          type: string
          enum:
            - recontact
          description: >
            Usa `recontact` para volver a llamar al contacto aunque su
            `identifier` ya exista en el

            lote.
        resetAttempts:
          type: boolean
          description: >
            Con `true`, reinicia los contadores de intentos cuando el contacto
            duplicado se vuelve a

            poner en marcación.
    ValidationError:
      type: object
      required:
        - message
      properties:
        code:
          type: string
          enum:
            - VALIDATION_FAILED
          description: >
            Presente cuando el rechazo corresponde a la validación de contactos.
            No aparece en los

            rechazos por límites de la petición, como un cuerpo vacío o más de
            1000 contactos.
        message:
          type: string
        details:
          description: >
            Detalle del rechazo. La estructura varía según el caso: una lista de
            textos para los límites

            de la petición, o una lista de `FieldRejection` cuando el rechazo es
            de validación de

            contactos.
          oneOf:
            - type: array
              items:
                type: string
            - type: array
              items:
                $ref: '#/components/schemas/FieldRejection'
    ContactLimitError:
      type: object
      required:
        - code
        - message
      description: >
        Límite de contactos activos de la cuenta excedido. Es el único error de
        estos endpoints que

        trae un `code` estable.
      properties:
        code:
          type: string
          enum:
            - CONTACT_LIMIT_EXCEEDED
        message:
          type: string
        maxContacts:
          type: integer
          description: Tope de contactos activos de la cuenta.
        currentActive:
          type: integer
          description: Cuántos hay activos ahora.
        attempting:
          type: integer
          description: Cuántos intentabas agregar.
        exceededBy:
          type: integer
          description: Por cuántos se excede el tope.
    IdempotencyKeyError:
      type: object
      required:
        - code
        - message
      description: >
        La clave de idempotencia recibida no cumple los requisitos de formato.


        Se rechaza en lugar de ignorarse, para no procesar la petición sin la
        protección que el cliente

        espera tener.
      properties:
        code:
          type: string
          enum:
            - INVALID_IDEMPOTENCY_KEY
        message:
          type: string
        details:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
                enum:
                  - Idempotency-Key
              code:
                type: string
                enum:
                  - EMPTY
                  - TOO_LONG
                  - INVALID_CHARACTERS
              reason:
                type: string
              meta:
                type: object
                additionalProperties: true
        meta:
          type: object
          description: >
            `rejection` es el dato estable para ramificar; el texto de `reason`
            es informativo.
          properties:
            rejection:
              type: string
              enum:
                - EMPTY
                - TOO_LONG
                - INVALID_CHARACTERS
          additionalProperties: true
    SimpleError:
      type: object
      required:
        - message
      description: >
        Error con mensaje descriptivo.


        Algunos traen `code` estable (los más nuevos); otros solo `message`.


        Los mensajes pueden aparecer en español o en inglés según el caso, y
        algunos errores todavía no

        incluyen `code`. Cuando esté presente, utilice `code` para tratar el
        error de forma programática.
      properties:
        code:
          type: string
          description: >
            Presente en los errores ya normalizados (`MISSING_PARAMETER`,
            `OPERATION_NOT_FOUND`,

            `VALIDATION_FAILED`, `INVALID_IDEMPOTENCY_KEY`, y los de
            idempotencia).
        message:
          type: string
    IdempotencyConflictError:
      type: object
      required:
        - code
        - message
      description: >
        Conflicto de idempotencia. Los dos casos se resuelven distinto:


        * `IDEMPOTENCY_KEY_IN_FLIGHT`: hay una petición idéntica en proceso.
        Espere el tiempo indicado en
          `Retry-After` y reintente con la misma clave.
        * `IDEMPOTENCY_KEY_REUSED`: la clave ya se utilizó con un cuerpo
        distinto. Utilice una clave nueva
          para la nueva petición.
      properties:
        code:
          type: string
          enum:
            - IDEMPOTENCY_KEY_IN_FLIGHT
            - IDEMPOTENCY_KEY_REUSED
        message:
          type: string
        meta:
          type: object
          additionalProperties: true
          properties:
            retryAfterSeconds:
              type: integer
    ContactAddress:
      type: object
      required:
        - address
        - type
        - priority
      properties:
        address:
          type: string
          description: >
            El teléfono o email. El formato se valida según `type`:


            * `phone` y `whatsapp`: únicamente dígitos, con código de país y sin
            el signo `+`, entre 7
              y 15 caracteres.
            * `email`: formato de email.
          example: '5491155551234'
        type:
          type: string
          enum:
            - phone
            - email
            - whatsapp
        priority:
          type: integer
          minimum: 1
          description: |
            Orden de marcación: **1 se intenta primero**. Entero positivo.
          example: 1
    FieldRejection:
      type: object
      required:
        - index
        - field
        - code
        - reason
      description: >
        Campo rechazado de un contacto concreto.


        Se incluyen todos los rechazos, no solo el primero de cada contacto: un
        contacto con tres campos

        incorrectos genera tres entradas.


        Utilice `code`, que es estable, para tratar el error de forma
        programática. El campo `reason` es

        un texto descriptivo de apoyo y puede cambiar sin aviso.
      properties:
        index:
          type: integer
          description: Posición del contacto en el array enviado, base 0.
          example: 3
        field:
          type: string
          description: >
            Ruta del campo rechazado, precedida por la posición del contacto.
            Permite localizarlo en la

            petición enviada.
          example: contacts[3].addresses[0].address
        code:
          type: string
          enum:
            - REQUIRED
            - INVALID_TYPE
            - INVALID_PHONE_FORMAT
            - INVALID_EMAIL_FORMAT
            - INVALID_ADDRESS_TYPE
            - INVALID_PRIORITY
            - EMPTY_ARRAY
            - INVALID_DATE
            - DATE_NOT_FUTURE
            - DATE_OUT_OF_RANGE
            - DUPLICATED_IN_REQUEST
          description: >
            Código estable del motivo del rechazo. Es el valor recomendado para
            tratar el error de forma

            programática y para traducir el mensaje al idioma del usuario final.
        reason:
          type: string
          description: >
            Descripción del rechazo, en español. Informativa: puede cambiar sin
            aviso.
        meta:
          type: object
          additionalProperties: true
          description: >
            Datos del rechazo: el valor recibido, el esperado o el límite
            aplicado. Permiten construir un

            mensaje propio sin necesidad de interpretar el texto de `reason`.
          example:
            received: '123'
            expected: digits only, 7-15 characters
        error:
          type: string
          deprecated: true
          description: >
            Contiene el mismo valor que `reason`. Se mantiene por compatibilidad
            con versiones anteriores;

            utilice `reason`.
  headers:
    IdempotencyReplayed:
      description: >
        Presente con valor `true` cuando la respuesta es una repetición de un
        request ya procesado con

        la misma clave. No se ejecutó nada nuevo.
      schema:
        type: string
        enum:
          - 'true'
    IdempotencyStatus:
      description: >
        Aparece con valor `bypassed` cuando se envió una clave válida pero la
        petición se procesó sin

        protección de idempotencia, porque el servicio que la gestiona no estaba
        disponible.


        La respuesta es válida, pero esa petición concreta no cuenta con la
        garantía de reintento seguro:

        si no se recibe respuesta, verifique el estado antes de reintentar.
      schema:
        type: string
        enum:
          - bypassed
  responses:
    InternalError:
      description: Error interno.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SimpleError'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: apikey
      description: API key obtenida desde la interfaz de Inagent, dentro del marcador.

````