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

# Crear un documento PDF con su flujo de firmas

> Es `multipart/form-data` con dos campos: `file` (el PDF, binario) y `payload` (texto con un JSON con `creator`, `document` y `participants`; su estructura es `IntegrationDocumentPayloadDto`).

La organización sale de la API key. El creador se identifica por su correo: si es miembro de la organización se usa su cuenta; si existe sin membresía, se le crea una con el rol `INTEGRATION_DOCUMENT_CREATOR`; si no existe, se crea como usuario provisional (sin contraseña). Sus datos nunca se sobrescriben.

Cada documento creado consume un crédito de la organización. Los participantes se invitan por correo.

**Orden de firma.** `document.isSequential` decide si `participants[].order` es obligatorio:

| `document.isSequential` | `SIGNER` | `WITNESS` |
| --- | --- | --- |
| `true` o se omite | `order` obligatorio: firman uno a la vez | No requiere `order` ni participa en él |
| `false` | `order` no es obligatorio: firman en cualquier momento | No requiere `order` ni participa en él |

En un documento secuencial, `order` es un entero que inicia en `1` y debe ser único y consecutivo entre firmantes (`1`, `2`, `3`…). Es este valor, no la posición en `participants`, lo que define el orden. Si no se cumple, la respuesta es `422` con `INVALID_SIGNING_ORDER` o `DUPLICATE_SIGNING_ORDER`.

**Ejemplo de `payload`: flujo secuencial** (dos firmantes y un testigo):

```json
{
  "creator": {
    "email": "ana.lopez@cliente.example",
    "firstName": "Ana",
    "lastName": "López"
  },
  "document": {
    "signatureType": "SIMPLE",
    "isSequential": true,
    "isIndexable": true
  },
  "participants": [
    {
      "role": "SIGNER",
      "firstName": "Luis",
      "lastName": "Martínez",
      "email": "luis.martinez@correo.example",
      "order": 1,
      "signaturePositions": [
        {
          "page": 1,
          "xRatio": 0.1,
          "yRatio": 0.8,
          "widthRatio": 0.25,
          "heightRatio": 0.08
        }
      ]
    },
    {
      "role": "SIGNER",
      "firstName": "Marta",
      "lastName": "Gómez",
      "email": "marta.gomez@correo.example",
      "order": 2,
      "signaturePositions": [
        {
          "page": 1,
          "xRatio": 0.55,
          "yRatio": 0.8,
          "widthRatio": 0.25,
          "heightRatio": 0.08
        }
      ]
    },
    {
      "role": "WITNESS",
      "firstName": "Eva",
      "lastName": "Ruiz",
      "email": "eva.ruiz@correo.example"
    }
  ]
}
```

**Ejemplo de `payload`: flujo no secuencial** (sin `order`):

```json
{
  "creator": {
    "email": "ana.lopez@cliente.example",
    "firstName": "Ana",
    "lastName": "López"
  },
  "document": {
    "signatureType": "SIMPLE",
    "isSequential": false,
    "isIndexable": true
  },
  "participants": [
    {
      "role": "SIGNER",
      "firstName": "Luis",
      "lastName": "Martínez",
      "email": "luis.martinez@correo.example",
      "signaturePositions": [
        {
          "page": 1,
          "xRatio": 0.1,
          "yRatio": 0.8,
          "widthRatio": 0.25,
          "heightRatio": 0.08
        }
      ]
    },
    {
      "role": "SIGNER",
      "firstName": "Marta",
      "lastName": "Gómez",
      "email": "marta.gomez@correo.example",
      "signaturePositions": [
        {
          "page": 1,
          "xRatio": 0.55,
          "yRatio": 0.8,
          "widthRatio": 0.25,
          "heightRatio": 0.08
        }
      ]
    },
    {
      "role": "WITNESS",
      "firstName": "Eva",
      "lastName": "Ruiz",
      "email": "eva.ruiz@correo.example"
    }
  ]
}
```

Ejemplo con curl:

```bash
curl -X POST https://<host>/api/v1/integration/documents \
  -H "X-API-Key: $FIRMALO_API_KEY" \
  -H "Idempotency-Key: erp-contrato-2026-000184" \
  -F "file=@contrato.pdf;type=application/pdf" \
  -F 'payload={"creator":{"email":"ana.lopez@cliente.example","firstName":"Ana","lastName":"López"},"document":{"signatureType":"SIMPLE","isSequential":true},"participants":[{"role":"SIGNER","firstName":"Luis","lastName":"Martínez","email":"luis.martinez@correo.example","order":1,"signaturePositions":[{"page":1,"xRatio":0.1,"yRatio":0.8,"widthRatio":0.25,"heightRatio":0.08}]}]}'
```



## OpenAPI

````yaml https://signature.api.staging.data-lo.online/api/v1/integration/docs-json post /api/v1/integration/documents
openapi: 3.0.0
info:
  title: Firmalo Integration API
  description: >-
    API para que tu sistema cree documentos PDF con su flujo de firmas, consulte
    su estado y gestione el acceso de los firmantes en Firmalo.


    **Autenticación.** Todas las rutas usan la API key de tu organización en el
    header `X-API-Key`, con formato `fma_live_<publicId>_<secret>`. Pégala en
    «Authorize» para probar desde esta página. Ejemplo (no es una llave real):


    `X-API-Key:
    fma_live_0123456789abcdef_EjemploNoRealEjemploNoRealEjemploNoReal1234`


    **Idempotencia.** `POST /integration/documents` exige `Idempotency-Key`:
    repetir la petición con la misma clave y el mismo contenido devuelve el
    documento original, sin crear ni cobrar otro. Ejemplo:


    `Idempotency-Key: erp-contrato-2026-000184`


    **Errores.** Todos tienen la forma `{ "success": false, "error": { "code",
    "message", "details" } }`, con un `code` estable para decidir qué hacer sin
    depender del texto.
  version: '1.0'
servers: []
security:
  - integration-api-key: []
tags:
  - name: Integration Documents
    description: Documentos, firmantes y accesos de firma de tu organización
paths:
  /api/v1/integration/documents:
    post:
      tags:
        - Integration Documents
      summary: Crear un documento PDF con su flujo de firmas
      description: >-
        Es `multipart/form-data` con dos campos: `file` (el PDF, binario) y
        `payload` (texto con un JSON con `creator`, `document` y `participants`;
        su estructura es `IntegrationDocumentPayloadDto`).


        La organización sale de la API key. El creador se identifica por su
        correo: si es miembro de la organización se usa su cuenta; si existe sin
        membresía, se le crea una con el rol `INTEGRATION_DOCUMENT_CREATOR`; si
        no existe, se crea como usuario provisional (sin contraseña). Sus datos
        nunca se sobrescriben.


        Cada documento creado consume un crédito de la organización. Los
        participantes se invitan por correo.


        **Orden de firma.** `document.isSequential` decide si
        `participants[].order` es obligatorio:


        | `document.isSequential` | `SIGNER` | `WITNESS` |

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

        | `true` o se omite | `order` obligatorio: firman uno a la vez | No
        requiere `order` ni participa en él |

        | `false` | `order` no es obligatorio: firman en cualquier momento | No
        requiere `order` ni participa en él |


        En un documento secuencial, `order` es un entero que inicia en `1` y
        debe ser único y consecutivo entre firmantes (`1`, `2`, `3`…). Es este
        valor, no la posición en `participants`, lo que define el orden. Si no
        se cumple, la respuesta es `422` con `INVALID_SIGNING_ORDER` o
        `DUPLICATE_SIGNING_ORDER`.


        **Ejemplo de `payload`: flujo secuencial** (dos firmantes y un testigo):


        ```json

        {
          "creator": {
            "email": "ana.lopez@cliente.example",
            "firstName": "Ana",
            "lastName": "López"
          },
          "document": {
            "signatureType": "SIMPLE",
            "isSequential": true,
            "isIndexable": true
          },
          "participants": [
            {
              "role": "SIGNER",
              "firstName": "Luis",
              "lastName": "Martínez",
              "email": "luis.martinez@correo.example",
              "order": 1,
              "signaturePositions": [
                {
                  "page": 1,
                  "xRatio": 0.1,
                  "yRatio": 0.8,
                  "widthRatio": 0.25,
                  "heightRatio": 0.08
                }
              ]
            },
            {
              "role": "SIGNER",
              "firstName": "Marta",
              "lastName": "Gómez",
              "email": "marta.gomez@correo.example",
              "order": 2,
              "signaturePositions": [
                {
                  "page": 1,
                  "xRatio": 0.55,
                  "yRatio": 0.8,
                  "widthRatio": 0.25,
                  "heightRatio": 0.08
                }
              ]
            },
            {
              "role": "WITNESS",
              "firstName": "Eva",
              "lastName": "Ruiz",
              "email": "eva.ruiz@correo.example"
            }
          ]
        }

        ```


        **Ejemplo de `payload`: flujo no secuencial** (sin `order`):


        ```json

        {
          "creator": {
            "email": "ana.lopez@cliente.example",
            "firstName": "Ana",
            "lastName": "López"
          },
          "document": {
            "signatureType": "SIMPLE",
            "isSequential": false,
            "isIndexable": true
          },
          "participants": [
            {
              "role": "SIGNER",
              "firstName": "Luis",
              "lastName": "Martínez",
              "email": "luis.martinez@correo.example",
              "signaturePositions": [
                {
                  "page": 1,
                  "xRatio": 0.1,
                  "yRatio": 0.8,
                  "widthRatio": 0.25,
                  "heightRatio": 0.08
                }
              ]
            },
            {
              "role": "SIGNER",
              "firstName": "Marta",
              "lastName": "Gómez",
              "email": "marta.gomez@correo.example",
              "signaturePositions": [
                {
                  "page": 1,
                  "xRatio": 0.55,
                  "yRatio": 0.8,
                  "widthRatio": 0.25,
                  "heightRatio": 0.08
                }
              ]
            },
            {
              "role": "WITNESS",
              "firstName": "Eva",
              "lastName": "Ruiz",
              "email": "eva.ruiz@correo.example"
            }
          ]
        }

        ```


        Ejemplo con curl:


        ```bash

        curl -X POST https://<host>/api/v1/integration/documents \
          -H "X-API-Key: $FIRMALO_API_KEY" \
          -H "Idempotency-Key: erp-contrato-2026-000184" \
          -F "file=@contrato.pdf;type=application/pdf" \
          -F 'payload={"creator":{"email":"ana.lopez@cliente.example","firstName":"Ana","lastName":"López"},"document":{"signatureType":"SIMPLE","isSequential":true},"participants":[{"role":"SIGNER","firstName":"Luis","lastName":"Martínez","email":"luis.martinez@correo.example","order":1,"signaturePositions":[{"page":1,"xRatio":0.1,"yRatio":0.8,"widthRatio":0.25,"heightRatio":0.08}]}]}'
        ```
      operationId: IntegrationDocumentsController_createDocument
      parameters:
        - name: Idempotency-Key
          in: header
          description: >-
            Valor único elegido por ti para este documento (1 a 255 caracteres
            ASCII visibles), por API key, durante 24 horas. Repetir la petición
            con la misma clave y el mismo contenido devuelve el documento
            original sin subir, crear, cobrar ni notificar nada.
          required: true
          schema:
            type: string
            example: erp-contrato-2026-000184
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/CreateIntegrationDocumentDto'
            examples:
              sequential:
                summary: 'Flujo secuencial: dos firmantes y un testigo'
                value:
                  payload:
                    creator:
                      email: ana.lopez@cliente.example
                      firstName: Ana
                      lastName: López
                    document:
                      signatureType: SIMPLE
                      isSequential: true
                      isIndexable: true
                    participants:
                      - role: SIGNER
                        firstName: Luis
                        lastName: Martínez
                        email: luis.martinez@correo.example
                        order: 1
                        signaturePositions:
                          - page: 1
                            xRatio: 0.1
                            yRatio: 0.8
                            widthRatio: 0.25
                            heightRatio: 0.08
                      - role: SIGNER
                        firstName: Marta
                        lastName: Gómez
                        email: marta.gomez@correo.example
                        order: 2
                        signaturePositions:
                          - page: 1
                            xRatio: 0.55
                            yRatio: 0.8
                            widthRatio: 0.25
                            heightRatio: 0.08
                      - role: WITNESS
                        firstName: Eva
                        lastName: Ruiz
                        email: eva.ruiz@correo.example
              nonSequential:
                summary: 'Flujo no secuencial: sin `order`'
                value:
                  payload:
                    creator:
                      email: ana.lopez@cliente.example
                      firstName: Ana
                      lastName: López
                    document:
                      signatureType: SIMPLE
                      isSequential: false
                      isIndexable: true
                    participants:
                      - role: SIGNER
                        firstName: Luis
                        lastName: Martínez
                        email: luis.martinez@correo.example
                        signaturePositions:
                          - page: 1
                            xRatio: 0.1
                            yRatio: 0.8
                            widthRatio: 0.25
                            heightRatio: 0.08
                      - role: SIGNER
                        firstName: Marta
                        lastName: Gómez
                        email: marta.gomez@correo.example
                        signaturePositions:
                          - page: 1
                            xRatio: 0.55
                            yRatio: 0.8
                            widthRatio: 0.25
                            heightRatio: 0.08
                      - role: WITNESS
                        firstName: Eva
                        lastName: Ruiz
                        email: eva.ruiz@correo.example
      responses:
        '200':
          description: >-
            Repetición de una petición anterior con el mismo `Idempotency-Key`:
            el documento original (`Idempotent-Replayed: true`)
          headers:
            Idempotent-Replayed:
              description: >-
                `false` si se creó el documento; `true` si es la respuesta de
                una petición anterior con el mismo `Idempotency-Key`
              schema:
                type: boolean
                example: false
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationCreatedDocumentResponse'
              example:
                success: true
                message: El documento ya se había creado con este Idempotency-Key
                data:
                  id: 2be3b9d7-4c5d-4e6f-8a9b-0c1d2e3f4a5b
                  status: PENDING_SIGNATURE
                  signatureType: SIMPLE
                  fileName: contrato.pdf
                  createdAt: '2026-10-26T12:00:00.000Z'
                  creator:
                    email: ana.lopez@cliente.example
                  participantsCount: 2
                  publicUrl: >-
                    https://app.firmalo.com/public/documents/2be3b9d7-4c5d-4e6f-8a9b-0c1d2e3f4a5b
                  participants:
                    - email: luis.martinez@correo.example
                      status: PENDING
                      signatureType: SIMPLE
                      order: 1
        '201':
          description: Documento creado, con su URL pública de consulta
          headers:
            Idempotent-Replayed:
              description: >-
                `false` si se creó el documento; `true` si es la respuesta de
                una petición anterior con el mismo `Idempotency-Key`
              schema:
                type: boolean
                example: false
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationCreatedDocumentResponse'
              example:
                success: true
                message: Documento y flujo de firmas creados correctamente
                data:
                  id: 2be3b9d7-4c5d-4e6f-8a9b-0c1d2e3f4a5b
                  status: PENDING_SIGNATURE
                  signatureType: SIMPLE
                  fileName: contrato.pdf
                  createdAt: '2026-10-26T12:00:00.000Z'
                  creator:
                    email: ana.lopez@cliente.example
                  participantsCount: 2
                  publicUrl: >-
                    https://app.firmalo.com/public/documents/2be3b9d7-4c5d-4e6f-8a9b-0c1d2e3f4a5b
                  participants:
                    - email: luis.martinez@correo.example
                      status: PENDING
                      signatureType: SIMPLE
                      order: 1
        '400':
          description: >-
            `INTEGRATION_TENANT_OVERRIDE_NOT_ALLOWED`: la petición trae
            `organizationId`, `taxId`, `userId`, `accountId`, `createdBy`,
            `createdByUserId`, `permissionIds`, `X-Account-Id` o
            `X-Organization-Id` (la organización y el autor salen de la llave).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationErrorResponseDto'
              examples:
                tenantOverride:
                  summary: Campo de organización o autor no permitido
                  value:
                    success: false
                    error:
                      code: INTEGRATION_TENANT_OVERRIDE_NOT_ALLOWED
                      message: >-
                        La organización, la cuenta y el autor salen de la API
                        key; no se aceptan: organizationId
                idempotencyKeyRequired:
                  summary: Falta Idempotency-Key
                  value:
                    success: false
                    error:
                      code: INTEGRATION_IDEMPOTENCY_KEY_REQUIRED
                      message: >-
                        El header Idempotency-Key es obligatorio: de 1 a 255
                        caracteres ASCII visibles
                approvalNotSupported:
                  summary: Aprobación previa no soportada
                  value:
                    success: false
                    error:
                      code: INTEGRATION_APPROVAL_NOT_SUPPORTED
                      message: >-
                        La API de integración todavía no admite documentos con
                        aprobación previa (requiresApproval: true)
        '401':
          description: >-
            `API_KEY_INVALID` (falta, formato incorrecto, secreto que no
            coincide o llave revocada) o `API_KEY_EXPIRED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationErrorResponseDto'
              examples:
                invalid:
                  summary: Llave ausente, inválida o revocada
                  value:
                    success: false
                    error:
                      code: API_KEY_INVALID
                      message: API key inválida o revocada
                expired:
                  summary: Llave vencida
                  value:
                    success: false
                    error:
                      code: API_KEY_EXPIRED
                      message: La API key venció. Genera una nueva desde Integraciones.
        '402':
          description: >-
            `INSUFFICIENT_DOCUMENT_CREDITS`: la organización no tiene créditos
            de documento disponibles. No se crea nada y la `Idempotency-Key`
            queda libre para reintentar.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationErrorResponseDto'
              examples:
                noCredits:
                  summary: Sin créditos
                  value:
                    success: false
                    error:
                      code: INSUFFICIENT_DOCUMENT_CREDITS
                      message: >-
                        No tienes documentos disponibles. Compra más o espera a
                        tu próximo periodo.
        '403':
          description: >-
            `API_KEY_PERMISSION_DENIED` (la llave no está autorizada para la
            operación), `API_KEY_ORGANIZATION_INACTIVE` o
            `API_KEY_ACTOR_INACTIVE` (quien emitió la llave ya no es miembro
            activo).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationErrorResponseDto'
              examples:
                permissionDenied:
                  summary: Operación no autorizada
                  value:
                    success: false
                    error:
                      code: API_KEY_PERMISSION_DENIED
                      message: La API key no está autorizada para esta operación
                organizationInactive:
                  summary: Organización desactivada
                  value:
                    success: false
                    error:
                      code: API_KEY_ORGANIZATION_INACTIVE
                      message: La organización de esta API key está desactivada
        '409':
          description: >-
            `INTEGRATION_IDEMPOTENCY_KEY_MISMATCH` (la clave ya se usó con otro
            archivo o datos) o `INTEGRATION_IDEMPOTENCY_KEY_IN_PROGRESS` (otra
            petición con la misma clave sigue en curso).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationErrorResponseDto'
              examples:
                mismatch:
                  summary: Clave reutilizada con otro contenido
                  value:
                    success: false
                    error:
                      code: INTEGRATION_IDEMPOTENCY_KEY_MISMATCH
                      message: >-
                        Este Idempotency-Key ya se usó con otro archivo o con
                        otros datos. Usa una clave nueva para un documento
                        nuevo.
                inProgress:
                  summary: Petición en curso
                  value:
                    success: false
                    error:
                      code: INTEGRATION_IDEMPOTENCY_KEY_IN_PROGRESS
                      message: >-
                        Ya hay una petición en curso con este Idempotency-Key.
                        Vuelve a intentarlo en unos segundos.
        '422':
          description: >-
            Validación del payload o del archivo, con `error.details.field`
            apuntando al campo (p. ej. `participants[2].email`). Se valida antes
            de subir el archivo, aprovisionar al creador o reservar la
            `Idempotency-Key`: `INVALID_PAYLOAD`, `INVALID_CREATOR`,
            `INVALID_DOCUMENT_OPTIONS`, `INVALID_DOCUMENT_FILE` (no es un PDF
            legible o pesa de más); `PARTICIPANTS_REQUIRED`, `SIGNER_REQUIRED`,
            `MAX_PARTICIPANTS_EXCEEDED` (50 por omisión),
            `INVALID_PARTICIPANT_ROLE`, `INVALID_PARTICIPANT_NAME`,
            `INVALID_PARTICIPANT_EMAIL`, `DUPLICATE_PARTICIPANT_EMAIL` (sin
            espacios y en minúsculas, aunque cambie el rol);
            `SIGNER_SIGNATURE_POSITION_REQUIRED`,
            `WITNESS_SIGNATURE_POSITION_NOT_ALLOWED`,
            `MAX_SIGNATURE_POSITIONS_EXCEEDED`, `INVALID_SIGNING_ORDER` /
            `DUPLICATE_SIGNING_ORDER` (con `isSequential`, `order` único y
            consecutivo desde 1 entre firmantes); `INVALID_SIGNATURE_POSITION`,
            `SIGNATURE_POSITION_PAGE_OUT_OF_RANGE`,
            `SIGNATURE_POSITION_OVERLAP`; y `INTEGRATION_CREATOR_UNAVAILABLE` /
            `INTEGRATION_CREATOR_MEMBERSHIP_REVOKED` para el creador.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationErrorResponseDto'
              examples:
                duplicateParticipant:
                  summary: Participante repetido
                  value:
                    success: false
                    error:
                      code: DUPLICATE_PARTICIPANT_EMAIL
                      message: >-
                        Un colaborador sólo puede participar una vez en el
                        documento.
                      details:
                        field: participants[1].email
                        email: luis.martinez@correo.example
                positionOutOfRange:
                  summary: Posición fuera del documento
                  value:
                    success: false
                    error:
                      code: SIGNATURE_POSITION_PAGE_OUT_OF_RANGE
                      message: >-
                        La posición de firma está en la página 3, pero el
                        documento sólo tiene 2.
                      details:
                        field: participants[0].signaturePositions[0].page
                        page: 3
                        totalPages: 2
                invalidFile:
                  summary: El archivo no es un PDF
                  value:
                    success: false
                    error:
                      code: INVALID_DOCUMENT_FILE
                      message: >-
                        El archivo no es un PDF válido: su contenido no
                        corresponde al tipo declarado
                      details:
                        field: file
                invalidRequest:
                  summary: Campos inválidos
                  value:
                    success: false
                    error:
                      code: INVALID_REQUEST
                      message: La petición tiene campos inválidos.
                      details:
                        errors:
                          - limit must not be greater than 100
      security:
        - integration-api-key: []
components:
  schemas:
    CreateIntegrationDocumentDto:
      type: object
      properties:
        payload:
          description: >-
            Campo de texto con un JSON con `creator`, `document` y
            `participants`.
          example:
            creator:
              email: ana.lopez@cliente.example
              firstName: Ana
              lastName: López
            document:
              signatureType: SIMPLE
              isSequential: true
              isIndexable: true
            participants:
              - role: SIGNER
                firstName: Luis
                lastName: Martínez
                email: luis.martinez@correo.example
                order: 1
                signaturePositions:
                  - page: 1
                    xRatio: 0.1
                    yRatio: 0.8
                    widthRatio: 0.25
                    heightRatio: 0.08
              - role: SIGNER
                firstName: Marta
                lastName: Gómez
                email: marta.gomez@correo.example
                order: 2
                signaturePositions:
                  - page: 1
                    xRatio: 0.55
                    yRatio: 0.8
                    widthRatio: 0.25
                    heightRatio: 0.08
              - role: WITNESS
                firstName: Eva
                lastName: Ruiz
                email: eva.ruiz@correo.example
          allOf:
            - $ref: '#/components/schemas/IntegrationDocumentPayloadDto'
        file:
          type: string
          format: binary
          description: PDF del documento a firmar, como archivo binario.
      required:
        - payload
        - file
    IntegrationCreatedDocumentResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
          description: Indica si la operación fue exitosa
        message:
          type: string
          example: Operación realizada con éxito
          description: Mensaje descriptivo del resultado de la operación
          enum:
            - Operación realizada con éxito
            - Recurso creado correctamente
            - Recurso no encontrado
            - Datos de entrada inválidos
            - No autorizado para realizar esta acción
            - El firmante no está asociado al recurso
            - El recurso ya existe
            - Error interno del servidor
        data:
          description: Datos de la respuesta
          allOf:
            - $ref: '#/components/schemas/IntegrationCreatedDocumentData'
      required:
        - success
        - message
        - data
    IntegrationErrorResponseDto:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          $ref: '#/components/schemas/IntegrationErrorData'
      required:
        - success
        - error
    IntegrationDocumentPayloadDto:
      type: object
      properties:
        creator:
          $ref: '#/components/schemas/IntegrationCreatorDto'
        document:
          $ref: '#/components/schemas/IntegrationDocumentOptionsDto'
        participants:
          type: array
          items:
            $ref: '#/components/schemas/IntegrationParticipantDto'
      required:
        - creator
        - document
        - participants
    IntegrationCreatedDocumentData:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 2be3b9d7-4c5d-4e6f-8a9b-0c1d2e3f4a5b
        status:
          type: string
          enum:
            - CREATED
            - PENDING_APPROVAL
            - PENDING_SIGNATURE
            - SIGNED
            - REJECTED
            - EXPIRED
            - CANCELLATION_PENDING
            - CANCELLED
          example: PENDING_SIGNATURE
        signatureType:
          type: string
          enum:
            - SIMPLE
            - FIEL
            - BIOMETRIC
          nullable: true
          example: SIMPLE
          description: '`FIEL` es la firma avanzada'
        fileName:
          type: string
          example: contrato.pdf
        createdAt:
          format: date-time
          type: string
          example: '2026-10-26T12:00:00.000Z'
        creator:
          $ref: '#/components/schemas/IntegrationDocumentCreatorData'
        participantsCount:
          type: number
          example: 1
          description: Firmantes y testigos del documento
        publicUrl:
          type: string
          example: >-
            https://app.firmalo.com/public/documents/2be3b9d7-4c5d-4e6f-8a9b-0c1d2e3f4a5b
          description: URL pública de consulta del documento (`PUBLIC_WEB_APP_URL`)
        participants:
          description: >-
            Firmantes en orden de firma. No incluye URLs de firma: cada uno
            recibe su invitación por correo, y el portal pide un acceso temporal
            con `POST /integration/documents/:documentId/signing-access`.
          type: array
          items:
            $ref: '#/components/schemas/IntegrationDocumentParticipantData'
      required:
        - id
        - status
        - signatureType
        - fileName
        - createdAt
        - creator
        - participantsCount
        - publicUrl
        - participants
    IntegrationErrorData:
      type: object
      properties:
        code:
          type: string
          example: DUPLICATE_PARTICIPANT_EMAIL
          description: >-
            Código estable del error (p. ej. `PARTICIPANTS_REQUIRED`,
            `SIGNER_REQUIRED`, `MAX_PARTICIPANTS_EXCEEDED`,
            `INVALID_PARTICIPANT_ROLE`, `INVALID_PARTICIPANT_EMAIL`,
            `DUPLICATE_PARTICIPANT_EMAIL`, `SIGNER_SIGNATURE_POSITION_REQUIRED`,
            `WITNESS_SIGNATURE_POSITION_NOT_ALLOWED`, `INVALID_SIGNING_ORDER`,
            `DUPLICATE_SIGNING_ORDER`, `INVALID_SIGNATURE_POSITION`,
            `SIGNATURE_POSITION_PAGE_OUT_OF_RANGE`,
            `SIGNATURE_POSITION_OVERLAP`, `API_KEY_INVALID`, `INTERNAL_ERROR`)
        message:
          type: string
          example: Un colaborador sólo puede participar una vez en el documento.
        details:
          type: object
          example:
            field: participants[2].email
            email: ana@correo.com
          description: >-
            Datos del error: `field` con la ruta del campo afectado cuando
            aplica, y los valores que lo explican (`limit`, `received`, `page`,
            `totalPages`…).
      required:
        - code
        - message
    IntegrationCreatorDto:
      type: object
      properties:
        email:
          type: string
          example: ana@cliente.com
          description: >-
            Correo del creador. Si no existe en la plataforma, se le da de alta
            como usuario provisional y miembro de la organización.
        firstName:
          type: string
          example: Ana
          description: >-
            Sólo se usa si el usuario no existe; nunca sobrescribe datos de uno
            existente.
        lastName:
          type: string
          example: López
      required:
        - email
        - firstName
        - lastName
    IntegrationDocumentOptionsDto:
      type: object
      properties:
        signatureType:
          type: string
          enum:
            - SIMPLE
            - ADVANCED
            - BIOMETRIC
          example: SIMPLE
        isSequential:
          type: boolean
          default: true
          description: >-
            Con `true`, o si se omite, los firmantes firman uno a la vez y cada
            participante con `role: SIGNER` debe incluir `order`. Con `false`,
            cualquier firmante pendiente puede firmar en cualquier momento y
            `order` no es obligatorio ni define el orden de firma. Un `WITNESS`
            nunca requiere `order`.
        requiresApproval:
          type: boolean
          default: false
          description: >-
            Por ahora sólo `false`: la aprobación previa no se admite por
            integración.
        isIndexable:
          type: boolean
          default: true
      required:
        - signatureType
    IntegrationParticipantDto:
      type: object
      properties:
        role:
          type: string
          enum:
            - SIGNER
            - WITNESS
          description: >-
            `SIGNER` firma el documento: requiere `signaturePositions` y, si el
            documento es secuencial, `order`. `WITNESS` no requiere `order` ni
            participa en él, y no admite `signaturePositions`.
        firstName:
          type: string
          example: Luis
          maxLength: 100
        lastName:
          type: string
          example: Martínez
          maxLength: 100
        email:
          type: string
          example: luis@correo.com
          description: 'Se compara sin espacios y en minúsculas: no puede repetirse.'
        order:
          type: integer
          minimum: 1
          example: 1
          description: >-
            Posición del firmante en el orden de firma: entero que inicia en 1.
            Obligatorio para cada `SIGNER` si `document.isSequential` es `true`
            o se omite. Entre los firmantes debe ser único y consecutivo, sin
            huecos (1, 2, 3…), y es este valor, no la posición en
            `participants`, lo que define el orden. Un `WITNESS` no lo requiere
            y no cuenta en la secuencia; con `isSequential: false` tampoco es
            obligatorio. Si se envía en esos casos no tiene efecto, pero debe
            ser un entero mayor o igual a 1.
        signaturePositions:
          description: >-
            Obligatorio para cada SIGNER (al menos una); prohibido para un
            WITNESS. Ratios 0-1 de la página.
          type: array
          items:
            $ref: '#/components/schemas/SignaturePositionDto'
        requiresTwoFactorAuth:
          type: boolean
          description: >-
            Código de verificación al firmar. En SIMPLE siempre se exige; en
            ADVANCED y BIOMETRIC, sólo si es true.
        rfc:
          type: string
          example: LOPA800101AB1
          description: >-
            RFC del firmante. Sólo para un SIGNER de un documento `ADVANCED`: si
            viene, el titular del certificado de su e.firma tiene que tener
            exactamente ese RFC; si no viene, se compara el nombre del
            colaborador con el del certificado.
      required:
        - role
        - firstName
        - lastName
        - email
    IntegrationDocumentCreatorData:
      type: object
      properties:
        email:
          type: string
          example: ana@cliente.com
      required:
        - email
    IntegrationDocumentParticipantData:
      type: object
      properties:
        email:
          type: string
          example: ana@correo.com
        status:
          type: string
          enum:
            - PENDING
            - WAITING_FOR_PREVIOUS_SIGNER
            - SIGNED
            - REJECTED
          example: PENDING
          description: >-
            `PENDING` si puede firmar ya; `WAITING_FOR_PREVIOUS_SIGNER` si el
            documento es secuencial y falta la firma de un firmante anterior
        signatureType:
          type: string
          enum:
            - SIMPLE
            - FIEL
            - BIOMETRIC
          nullable: true
          example: SIMPLE
        order:
          type: number
          example: 1
          description: Orden de firma, desde 1
      required:
        - email
        - status
        - signatureType
        - order
    SignaturePositionDto:
      type: object
      properties:
        signatureId:
          type: string
          example: sig_loc_01
        page:
          type: number
          example: 1
        xRatio:
          type: number
          example: 0.65
        yRatio:
          type: number
          example: 0.8
        widthRatio:
          type: number
          example: 0.2
        heightRatio:
          type: number
          example: 0.08
      required:
        - page
        - xRatio
        - yRatio
        - widthRatio
        - heightRatio
  securitySchemes:
    integration-api-key:
      type: apiKey
      in: header
      name: X-API-Key
      description: >-
        API key de tu organización, con formato `fma_live_<publicId>_<secret>`.
        Se emite (y se rota o revoca) desde **Integraciones** en Firmalo; el
        secreto completo sólo se muestra al emitirla.


        Ejemplo (no es una llave real):
        `fma_live_0123456789abcdef_EjemploNoRealEjemploNoRealEjemploNoReal1234`


        La organización y el contexto de acceso salen de la llave: no se usan
        JWT, `X-Account-Id` ni `X-Organization-Id`. Llama a la API sólo desde tu
        backend; nunca expongas la llave en un navegador o una app móvil.

````

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