Chains

Un Chain es un paquete de varios documentos que un mismo grupo de roles firma en un orden definido. En lugar de orquestar manualmente N documentos por separado, un chain te permite crearlos todos de golpe a partir de templates DOCX, compartir variables entre ellos por nombre, e invitar a los firmantes con un solo correo que los lleva a un lobby donde van firmando documento por documento.

Cada chain define:

  • Una lista ordenada de documentos (cada uno generado a partir de un templateId).
  • Una lista de roles (Cliente, Prestador, etc.) con sus emails de contacto.
  • Una lista de variables asignadas a cada rol — cuando un firmante llena nombre_cliente en el primer doc, ese valor se propaga automáticamente a todos los docs que tengan esa variable.
  • Opcionalmente, valores fijos del owner que se inyectan antes de invitar.

El ciclo de vida de un chain es:

DRAFT → (invite) → INVITED → (firmantes llenan vars) → FILLING → (PDFs generados por Temporal) → SIGNING → (todos firman) → COMPLETED.

Los estados terminales son COMPLETED, VOIDED y EXPIRED.


POST/v2/chains

Crear chain

Crea un chain en estado DRAFT. El backend genera una fila Document por cada templateId (en el orden recibido), crea las roles y signatures, y deja el chain listo para invitar.

Request body

  • Name
    name
    Type
    string
    Description

    Nombre visible del chain (opcional).

  • Name
    templateIds
    Type
    array
    Description

    Lista de UUIDs de templates DOCX. Mínimo 1. Cada template se convierte en un documento del chain, en el orden recibido (chainPosition 0, 1, 2, …).

  • Name
    templatePackId
    Type
    string
    Description

    UUID opcional de un template pack preconfigurado. Mutuamente compatible con templateIds.

  • Name
    roles
    Type
    array
    Description

    Lista de roles. Cada rol define quién debe firmar y con qué nombre aparece en el chain. Mínimo 1 rol. Cada item: { name, contactEmail?, contactPhone?, contactName?, color?, sortOrder }.

  • Name
    variableAssignments
    Type
    array
    Description

    Lista de asignaciones variable → rol. Cada item: { variableName, roleName, label?, type?, required?, inputMode?, sourceHint?, captureAction? }. inputMode puede ser free, optional o required.

  • Name
    ownerValues
    Type
    array
    Description

    Lista de valores fijos provistos por el owner antes de invitar. Cada item: { variableName, value }.

  • Name
    signatureValidations
    Type
    object
    Description

    Configuración global de firma para todos los docs del chain. Ejemplo: { "autografa": true, "id_scan": true }.

  • Name
    signatureFieldConfig
    Type
    object
    Description

    Cómo se colocan los campos de firma en los PDFs generados. Soporta mode: "auto" (último renglón de la última página) o mode: "anchor" (busca un texto ancla y coloca el campo allí). Ver schema en el backend.

  • Name
    config
    Type
    object
    Description

    Bag libre de configuración adicional.

  • Name
    expiresInDays
    Type
    integer
    Description

    Número de días hasta que el chain expira. Si no se provee, hereda el default del tenant.

Response — 201 Created

Devuelve el ChainResponse completo (mismo shape que GET /v2/chains/{id}).

Python — crear chain

import httpx

API_KEY = "allsign_live_sk_TU_API_KEY"
BASE = "https://api.allsign.io"

body = {
    "name": "Onboarding cliente Q2",
    "templateIds": ["uuid-template-1", "uuid-template-2"],
    "roles": [
        {"name": "Cliente",   "contactEmail": "cliente@ejemplo.com",   "sortOrder": 0},
        {"name": "Prestador", "contactEmail": "prestador@ejemplo.com", "sortOrder": 1},
    ],
    "variableAssignments": [],
    "ownerValues": [],
}

r = httpx.post(
    f"{BASE}/v2/chains",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json=body,
)
chain = r.json()
print(chain["id"], chain["status"])  # → ... DRAFT

Response (201)

{
  "id": "c1a2b3c4-...",
  "name": "Onboarding cliente Q2",
  "status": "DRAFT",
  "ownerId": "u-...",
  "tenantId": "t-...",
  "createdAt": "2026-04-07T20:00:00Z",
  "updatedAt": "2026-04-07T20:00:00Z",
  "roles": [
    { "id": "r-...", "name": "Cliente",   "contactEmail": "cliente@ejemplo.com",   "sortOrder": 0 },
    { "id": "r-...", "name": "Prestador", "contactEmail": "prestador@ejemplo.com", "sortOrder": 1 }
  ],
  "documents": [
    { "id": "d-...", "name": "Contrato.docx", "chainPosition": 0, "templateId": "uuid-template-1" },
    { "id": "d-...", "name": "Anexo.docx",    "chainPosition": 1, "templateId": "uuid-template-2" }
  ]
}

GET/v2/chains

Listar chains

Devuelve los chains del usuario autenticado con paginación.

Query parameters

  • Name
    page
    Type
    integer
    Description

    Página (1-indexed). Default: 1.

  • Name
    pageSize
    Type
    integer
    Description

    Tamaño de página (1–100). Default: 20.

  • Name
    status
    Type
    string
    Description

    Filtra por estado: DRAFT, INVITED, FILLING, SIGNING, COMPLETED, VOIDED, EXPIRED.

Response — 200 OK

Response (200)

{
  "data": [ /* ChainResponse[] */ ],
  "total": 42,
  "page": 1,
  "pageSize": 20
}

GET/v2/chains/{id}

Obtener chain

Devuelve el detalle completo de un chain incluyendo roles, documents y status.

Path parameters

  • Name
    id
    Type
    string
    Description

    UUID del chain.

Errores

StatusCuándo ocurre
404 Not FoundEl chain no existe o no pertenece al caller

PATCH/v2/chains/{id}

Editar chain

Actualiza un chain mientras esté en DRAFT. Solo se tocan las claves presentes en el body — cualquier campo omitido queda intacto. Internamente, los cambios por documento se delegan al mismo helper update_document_v2 que usa PATCH /v2/documents/{id}, así que la semántica es idéntica a editar cada doc individualmente.

Campos soportados hoy:

  • Document fields — posición y configuración de los campos de firma por documento (reusa el mismo writer que POST /v2/chains).
  • Signature validations — el bag global signatureValidations aplicado a todos los docs del chain (p. ej. habilitar id_scan o autografa).

Intentar editar un chain que ya salió de DRAFT (INVITED/FILLING/SIGNING/…) devuelve 409 Conflict.

Path parameters

  • Name
    id
    Type
    string
    Description

    UUID del chain.

Request body

  • Name
    documentFields
    Type
    array
    Description

    Lista de items { documentId, fields } donde fields sigue el mismo shape que en POST /v2/documents. Solo los documentos listados se actualizan; los demás no se tocan.

  • Name
    signatureValidations
    Type
    object
    Description

    Reemplaza el bag global aplicado a todos los docs del chain.

Response — 200 OK

Devuelve el ChainResponse completo, ya con el progreso anotado (totalDocuments, signedCount, documents[].status).

cURL — mover un campo de firma

curl -X PATCH "https://api.allsign.io/v2/chains/CHAIN_UUID" \
  -H "Authorization: Bearer ALLSIGN_LIVE_SK" \
  -H "Content-Type: application/json" \
  -d '{
    "documentFields": [
      {
        "documentId": "d-aaa...",
        "fields": [
          { "type": "signature", "page": 1, "x": 120, "y": 640, "width": 180, "height": 48, "roleName": "Cliente" }
        ]
      }
    ]
  }'

Errores

StatusCuándo ocurre
404 Not FoundEl chain no existe o no pertenece al caller
409 ConflictEl chain no está en DRAFT

POST/v2/chains/{id}/invite

Invitar firmantes

Transiciona el chain de DRAFTINVITED. Para cada rol: crea las GuestSessions, marca la primera (la del primer doc) como is_chain_primary=true, genera un OTP inline, construye la URL del lobby y envía un correo con la invitación. El resto de los docs heredan tokens secundarios que se rotan al vuelo cuando el lobby los pide.

Los firmantes solo reciben un correo por persona, sin importar cuántos docs tenga el chain.

Path parameters

  • Name
    id
    Type
    string
    Description

    UUID del chain.

Request body

  • Name
    invitationChannel
    Type
    string
    Description

    Canal de invitación: email (default) o whatsapp.

Response — 200 OK

Response (200)

{
  "message": "Invitations sent",
  "chainId": "c1a2b3c4-...",
  "status": "INVITED",
  "invitations": [
    {
      "role": "Cliente",
      "email": "cliente@ejemplo.com",
      "success": true,
      "guestUrl": "https://app.allsign.io/sign/<token>/onboarding?otp=123456"
    }
  ]
}

Errores

StatusCuándo ocurre
400 Bad RequestEl chain no está en DRAFT, o un rol no tiene email ni teléfono
404 Not FoundEl chain no existe

GET/v2/chains/{id}/variables

Variables a llenar

Devuelve las variables pendientes del chain agrupadas por rol. Útil para construir un formulario admin del lado del owner.

Path parameters

  • Name
    id
    Type
    string
    Description

    UUID del chain.

Query parameters

  • Name
    roleId
    Type
    string
    Description

    Filtra el resultado a un solo rol.

Response — 200 OK

Response (200)

{
  "chainId": "c1a2b3c4-...",
  "chainName": "Onboarding cliente Q2",
  "chainStatus": "INVITED",
  "roles": [
    {
      "roleId": "r-...",
      "roleName": "Cliente",
      "roleColor": null,
      "variables": [
        {
          "id": "v-...",
          "variableName": "nombre_cliente",
          "label": "Nombre Cliente",
          "type": "text",
          "required": true,
          "inputMode": "free",
          "value": null,
          "verified": false,
          "documentId": "d-...",
          "documentName": "Contrato.docx"
        }
      ]
    }
  ]
}

POST/v2/chains/{id}/fill-variables

Llenar variables

Llena variables para un rol específico desde el lado del owner. Los valores se propagan a todos los documentos del chain donde exista la misma variable. Cuando todas las variables requeridas de todos los roles están llenas, el chain transiciona automáticamente de FILLING a SIGNING y dispara el workflow de Temporal que genera los PDFs finales.

Request body

  • Name
    roleId
    Type
    string
    Description

    UUID del rol al que pertenecen las variables.

  • Name
    variables
    Type
    array
    Description

    Lista de items { variableName, value, source? }. source default: "manual".

Response — 200 OK

Response (200)

{
  "filledCount": 3,
  "remainingCount": 2,
  "chainStatus": "FILLING",
  "allFilled": false
}

POST/v2/chains/{id}/regenerate-pdf

Regenerar PDF

Reintenta la generación del PDF de un solo documento del chain cuya renderización inicial falló. Lee chain.config.pdfStatus[docId] y:

  • Si ya está en SUCCESS, retorna 200 inmediatamente (idempotente).
  • Si el chain está en estado terminal (COMPLETED, VOIDED, EXPIRED), devuelve 409 Conflict.
  • En cualquier otro caso, dispara el RegenerateChainPdfWorkflow de Temporal y espera su resultado. Si éste era el último doc en FAILED, el workflow promueve chain.status de vuelta a SIGNING automáticamente.

Llamadas concurrentes para el mismo (chainId, docId) se enganchan al workflow en vuelo (USE_EXISTING) y todas reciben el mismo resultado final.

Path parameters

  • Name
    id
    Type
    string
    Description

    UUID del chain.

Query parameters

  • Name
    docId
    Type
    string
    Description

    UUID del documento dentro del chain cuyo PDF se debe re-renderizar.

Response — 200 OK

Response (200)

{
  "success": true,
  "chainId": "c1a2b3c4-...",
  "documentId": "d-...",
  "chainStatus": "SIGNING",
  "pdfStatus": {
    "d-...": {
      "status": "SUCCESS",
      "attempts": 2,
      "lastAttemptAt": "2026-04-07T20:05:00Z",
      "s3Key": "chains/c1a2b3c4/d-.../v2.pdf",
      "pdfHash": "a1b2c3..."
    }
  },
  "failedDocumentIds": [],
  "message": "Document PDF regenerated successfully. Chain is now SIGNING."
}

Errores

StatusCuándo ocurre
404 Not FoundEl chain no existe, o el documento no pertenece al chain
409 ConflictEl chain está en estado terminal (COMPLETED, VOIDED, EXPIRED)

DELETE/v2/chains/{id}

Eliminar chain

Elimina un chain. Por defecto realiza un soft-delete marcando status = VOIDED para preservar la auditoría. Pasa ?hard=true para borrar permanentemente la fila más sus roles, variables y GuestSessions. Las filas Document subyacentes sobreviven (su chainId queda en NULL) para conservar la evidencia firmada.

El soft-delete se rechaza con 409 Conflict para chains en COMPLETED — usa hard delete si necesitas remover su metadata.

Query parameters

  • Name
    hard
    Type
    boolean
    Description

    Si es true, elimina permanentemente el chain y sus dependientes. Default: false.

Response — 200 OK

Response (200)

{
  "success": true,
  "id": "c1a2b3c4-...",
  "name": "Onboarding cliente Q2",
  "message": "Chain 'Onboarding cliente Q2' archived (VOIDED).",
  "softDelete": true,
  "cascade": {
    "documents": 2,
    "roles": 2,
    "variables": 5,
    "guestSessions": 4
  }
}

Endpoints guest

Estos endpoints son consumidos por la UI del firmante (el lobby web al que llegan desde el correo de invitación). No requieren API key — el firmante se autentica enviando el token plain del guest session en el body del request. AllSign valida el token contra GuestSession.token_hash (SHA-256) y resuelve automáticamente el chain, el rol y el documento asociados.


POST/v2/guest/chain/pending-docs

Listar documentos pendientes

Devuelve todos los documentos del chain con el estado de firma del caller, más una guestUrl recién emitida para cada doc. Pendientes y firmados ambos vienen con URL — el frontend decide si la URL lleva al workspace de firma (pendiente) o a /confirmation con el PDF de evidencia (firmado).

Request body

  • Name
    token
    Type
    string
    Description

    Plain text del guest token recibido en la URL de invitación.

Response — 200 OK

Response (200)

{
  "chainId": "c1a2b3c4-...",
  "chainName": "Onboarding cliente Q2",
  "chainStatus": "SIGNING",
  "totalDocuments": 3,
  "signedCount": 1,
  "pendingCount": 2,
  "documents": [
    {
      "documentId": "d-aaa...",
      "name": "Contrato.docx",
      "chainPosition": 0,
      "signed": true,
      "signatureStatus": "SIGNED",
      "guestUrl": "https://app.allsign.io/sign/<token-primario>",
      "isPrimary": true
    },
    {
      "documentId": "d-bbb...",
      "name": "Anexo.docx",
      "chainPosition": 1,
      "signed": false,
      "signatureStatus": "PENDING",
      "guestUrl": "https://app.allsign.io/sign/<token-rotado>",
      "isPrimary": false
    },
    {
      "documentId": "d-ccc...",
      "name": "Carta consentimiento.docx",
      "chainPosition": 2,
      "signed": false,
      "signatureStatus": "PENDING",
      "guestUrl": "https://app.allsign.io/sign/<token-rotado>",
      "isPrimary": false
    }
  ],
  "owner": {
    "name": "Israel Ortiz",
    "email": "israel@allsign.mx",
    "image": null
  },
  "role": {
    "id": "r-...",
    "name": "Cliente",
    "color": null
  }
}

Cada item de documents[] contiene:

  • Name
    documentId
    Type
    string
    Description

    UUID del documento dentro del chain.

  • Name
    name
    Type
    string
    Description

    Nombre del documento.

  • Name
    chainPosition
    Type
    integer
    Description

    Posición 0-indexed dentro del chain.

  • Name
    signed
    Type
    boolean
    Description

    true si el caller ya firmó este documento.

  • Name
    signatureStatus
    Type
    string
    Description

    Status crudo del row Signature del caller para este doc (PENDING, SIGNED, etc.). null si no existe.

  • Name
    guestUrl
    Type
    string
    Description

    URL recién emitida (con token rotado para los docs secundarios). null si no se pudo construir.

  • Name
    isPrimary
    Type
    boolean
    Description

    true solo para el documento cuya GuestSession es la primaria del caller (la que se entregó por correo).


POST/v2/guest/chain/next-doc

Siguiente documento pendiente

Helper de conveniencia para el redirect post-firma. Devuelve el siguiente documento sin firmar del caller, o nextDocument: null cuando ya terminó. Internamente reusa pending-docs y aplica la misma rotación de tokens.

Request body

  • Name
    token
    Type
    string
    Description

    Plain text del guest token.

Response — 200 OK

Response (200) — quedan pendientes

{
  "chainId": "c1a2b3c4-...",
  "chainStatus": "SIGNING",
  "pendingCount": 2,
  "nextDocument": {
    "documentId": "d-bbb...",
    "name": "Anexo.docx",
    "chainPosition": 1,
    "signed": false,
    "signatureStatus": "PENDING",
    "guestUrl": "https://app.allsign.io/sign/<token-rotado>",
    "isPrimary": false
  }
}

Response (200) — chain completo para el caller

{
  "chainId": "c1a2b3c4-...",
  "chainStatus": "COMPLETED",
  "pendingCount": 0,
  "nextDocument": null
}

Was this page helpful?