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_clienteen 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.
Para entender el objeto Document que está debajo de cada item del chain, consulta el objeto Document. Para crear los templates referenciados en templateIds, ve a Templates.
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 (
chainPosition0, 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? }.inputModepuede serfree,optionalorequired.
- 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) omode: "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" }
]
}
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
}
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
| Status | Cuándo ocurre |
|---|---|
404 Not Found | El chain no existe o no pertenece al caller |
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
signatureValidationsaplicado a todos los docs del chain (p. ej. habilitarid_scanoautografa).
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 }dondefieldssigue el mismo shape que enPOST /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
| Status | Cuándo ocurre |
|---|---|
404 Not Found | El chain no existe o no pertenece al caller |
409 Conflict | El chain no está en DRAFT |
Invitar firmantes
Transiciona el chain de DRAFT → INVITED. 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) owhatsapp.
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
| Status | Cuándo ocurre |
|---|---|
400 Bad Request | El chain no está en DRAFT, o un rol no tiene email ni teléfono |
404 Not Found | El chain no existe |
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"
}
]
}
]
}
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? }.sourcedefault:"manual".
Response — 200 OK
Response (200)
{
"filledCount": 3,
"remainingCount": 2,
"chainStatus": "FILLING",
"allFilled": false
}
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), devuelve409 Conflict. - En cualquier otro caso, dispara el
RegenerateChainPdfWorkflowde Temporal y espera su resultado. Si éste era el último doc enFAILED, el workflow promuevechain.statusde vuelta aSIGNINGautomá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
| Status | Cuándo ocurre |
|---|---|
404 Not Found | El chain no existe, o el documento no pertenece al chain |
409 Conflict | El chain está en estado terminal (COMPLETED, VOIDED, EXPIRED) |
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.
Rotación de tokens. Cuando el lobby pide URLs para los documentos secundarios del chain (todos menos el primero), el backend rota el token_hash de cada GuestSession secundaria a un plaintext recién generado. El plaintext anterior nunca se persistió ni se entregó, así que nada en el wild lo referencia. Esto significa que cada llamada a pending-docs produce URLs únicas — no las caches ni las compartas: pídelas just-in-time. La verificación OTP del firmante en la sesión primaria se hereda a las secundarias para evitar pedir el código en cada doc.
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
truesi el caller ya firmó este documento.
- Name
signatureStatus- Type
- string
- Description
Status crudo del row
Signaturedel caller para este doc (PENDING,SIGNED, etc.).nullsi no existe.
- Name
guestUrl- Type
- string
- Description
URL recién emitida (con token rotado para los docs secundarios).
nullsi no se pudo construir.
- Name
isPrimary- Type
- boolean
- Description
truesolo para el documento cuyaGuestSessiones la primaria del caller (la que se entregó por correo).
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
}

