# AllSign API v3 — documentación completa Versión fechada del contrato: `AllSign-Version: 2026-07-11`. Fuente: https://allsign.io/developers/docs/errors.md # Errores de la API v3 Toda respuesta no-2xx es un documento **RFC 9457 `application/problem+json`**. Los clientes programan contra `code` (estable, append-only), nunca contra `detail` (inglés técnico, puede cambiar). ## Para agentes y asistentes de IA - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** Un cliente que decide por `code` sobrevive a cambios de redacción, y cada `code` tiene su fila en el catálogo con su status y su significado. - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) ## Estructura del error Shape plano (sin envoltorio): 5 miembros core de RFC 9457 + extensiones de AllSign. ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND", "title": "Document not found", "status": 404, "detail": "No document exists with id doc_...", "instance": "/v3/documents/doc_...", "code": "DOCUMENT_NOT_FOUND", "requestId": "req_...", "errors": [] } ``` ## Extensiones por código Algunos códigos agregan campos: `DOCUMENT_NOT_SENDABLE` trae `reason`; `PERMISSION_DENIED` trae `requiredScope` y `yourScopes` cuando falta un scope; `RATE_LIMITED` trae `retryAfter` (segundos) y el header `Retry-After`, salvo el del recordatorio a firmante (ver [Rate limits](https://allsign.io/developers/docs/rate-limits#el-429)). Cada `type` enlaza al ancla de su código en esta página (`https://allsign.io/developers/docs/errors#`). Un `404` nombra el recurso que no existe: `DOCUMENT_NOT_FOUND`, `TEMPLATE_NOT_FOUND`, `FOLDER_NOT_FOUND`… Una ruta que no existe (un typo en el path, como `/v3/document/doc_…`) responde `RESOURCE_NOT_FOUND`, así que no la confundes con un documento borrado. ## Errores a nivel de campo La validación semántica (`422 VALIDATION_ERROR`) puebla `errors[]` con un objeto por campo: `field` (dot/bracket), `pointer` (JSON Pointer), `code` y `detail`. ``` "errors": [ { "field": "signers[0].email", "pointer": "/signers/0/email", "code": "invalid_email", "detail": "not a valid email address" } ] ``` ## Catálogo de códigos La taxonomía **completa**: 84 códigos, congelada y append-only. Cada fila tiene `id=""` para que el `type` URI ancle aquí. **Activo** significa que la API lo emite hoy. **Reservado** (19 de 84) significa que el código forma parte del contrato pero *ninguna ruta lo devuelve todavía*: o el caso lo cubre otro código (un JSON malformado o un `Content-Type` que no es JSON salen como `VALIDATION_ERROR`, no como `MALFORMED_JSON` ni `UNSUPPORTED_MEDIA_TYPE`; a una key sin el scope se le responde `PERMISSION_DENIED`, no `INSUFFICIENT_SCOPE`), o espera una capacidad que aún no lanzamos (los `TOKEN_*` esperan OAuth), o la política de seguridad lo colapsa a propósito (una key inválida, revocada o vencida responde `401 AUTHENTICATION_REQUIRED`, no `API_KEY_*`, para no confirmar que la key existe). Un `Accept` que no es JSON tampoco da `NOT_ACCEPTABLE`: la respuesta llega en JSON igual. **51 de 84** códigos traen su `problem+json` real, grabado por los tests contra sandbox: ábrelo en «Así se ve». Los demás dicen por qué no lo tienen (es un código reservado, el sandbox no cobra, hace falta una caída real…): preferimos explicarlo a inventar un ejemplo. **No programes una rama para un código reservado** — sería código muerto. Un reservado nunca cambia de nombre ni de significado al activarse, así que agregarlo después es seguro; anticiparlo, no. | Código | Status | Título | Estado | Ejemplo real | | --- | --- | --- | --- | --- | | `ANNEX_OF_ANNEX_NOT_ALLOWED` | 409 | Conflict | Activo | abajo | | `API_KEY_EXPIRED` | 401 | Unauthorized | Reservado | sin ejemplo verificado | | `API_KEY_INVALID` | 401 | Unauthorized | Reservado | sin ejemplo verificado | | `API_KEY_REVOKED` | 401 | Unauthorized | Reservado | sin ejemplo verificado | | `AUTHENTICATION_REQUIRED` | 401 | Authentication required | Activo | abajo | | `BATCH_NOT_FOUND` | 404 | Bulk-send batch not found | Activo | abajo | | `CATALOG_TEMPLATE_NOT_FOUND` | 404 | Catalog template not found | Activo | abajo | | `CONSTANCIA_NOT_FOUND` | 404 | Constancia not found | Activo | abajo | | `CONTRACT_REQUIRED` | 403 | NOM-151 contract required | Activo | sin ejemplo verificado | | `DEV_FEATURE_RESTRICTED` | 403 | Feature restricted in this environment | Activo | abajo | | `DOCUMENT_ALREADY_SIGNED` | 409 | Document already signed | Activo | abajo | | `DOCUMENT_ALREADY_VOIDED` | 409 | Conflict | Reservado | sin ejemplo verificado | | `DOCUMENT_CONFLICT` | 409 | Document conflict | Activo | abajo | | `DOCUMENT_HAS_NO_FIELDS` | 422 | Unprocessable Entity | Reservado | sin ejemplo verificado | | `DOCUMENT_NOT_EDITABLE` | 409 | Conflict | Activo | abajo | | `DOCUMENT_NOT_FOUND` | 404 | Document not found | Activo | abajo | | `DOCUMENT_NOT_FULLY_SIGNED` | 409 | Conflict | Activo | abajo | | `DOCUMENT_NOT_SENDABLE` | 409 | Document not sendable | Activo | abajo | | `DOCUMENT_PDF_LOCKED` | 409 | Conflict | Activo | sin ejemplo verificado | | `DOCUMENT_TOO_LARGE` | 413 | Document too large | Activo | abajo | | `DUPLICATE_SIGNER` | 422 | Duplicate signer | Activo | abajo | | `ENVIRONMENT_MISMATCH` | 409 | Environment mismatch | Activo | sin ejemplo verificado | | `EVENT_NOT_FOUND` | 404 | Not Found | Reservado | sin ejemplo verificado | | `EXPAND_DEPTH_EXCEEDED` | 400 | Expand depth exceeded | Activo | abajo | | `EXTERNAL_ID_CONFLICT` | 409 | Conflict | Activo | abajo | | `FEATURE_NOT_AVAILABLE` | 403 | Feature not available for this tenant | Activo | sin ejemplo verificado | | `FIELD_CONFLICT` | 409 | Field name already taken | Activo | abajo | | `FIELD_NOT_FOUND` | 404 | Field not found | Activo | abajo | | `FOLDER_NOT_EMPTY` | 409 | Conflict | Activo | abajo | | `FOLDER_NOT_FOUND` | 404 | Folder not found | Activo | abajo | | `IDEMPOTENCY_KEY_IN_PROGRESS` | 409 | Idempotency key in progress | Activo | abajo | | `IDEMPOTENCY_KEY_INVALID` | 400 | Idempotency key invalid | Activo | abajo | | `IDEMPOTENCY_KEY_REQUIRED` | 400 | Idempotency key required | Activo | abajo | | `IDEMPOTENCY_KEY_REUSED` | 409 | Idempotency key reused | Activo | abajo | | `INSUFFICIENT_CREDITS` | 402 | Insufficient credits | Activo | sin ejemplo verificado | | `INSUFFICIENT_SCOPE` | 403 | Forbidden | Reservado | sin ejemplo verificado | | `INTERNAL_ERROR` | 500 | Internal server error | Activo | sin ejemplo verificado | | `INVALID_CURSOR` | 400 | Invalid cursor | Activo | abajo | | `INVALID_EXPAND` | 400 | Invalid expand path | Activo | abajo | | `INVALID_FILTER` | 400 | Bad Request | Reservado | sin ejemplo verificado | | `INVALID_ID` | 400 | Malformed identifier | Activo | abajo | | `INVALID_SORT` | 400 | Bad Request | Reservado | sin ejemplo verificado | | `INVALID_STATE_TRANSITION` | 409 | Conflict | Activo | abajo | | `IP_NOT_ALLOWED` | 403 | IP address not allowed | Activo | sin ejemplo verificado | | `LIMIT_OUT_OF_RANGE` | 400 | Bad Request | Reservado | sin ejemplo verificado | | `MALFORMED_JSON` | 400 | Bad Request | Reservado | sin ejemplo verificado | | `METHOD_NOT_ALLOWED` | 405 | Method Not Allowed | Activo | abajo | | `NOT_ACCEPTABLE` | 406 | Not Acceptable | Reservado | sin ejemplo verificado | | `NOT_YOUR_TURN` | 409 | Not this signer's turn yet | Activo | abajo | | `OAUTH_NOT_ENABLED` | 401 | Unauthorized | Reservado | sin ejemplo verificado | | `OAUTH_NOT_IMPLEMENTED` | 501 | Not Implemented | Activo | sin ejemplo verificado | | `OBSERVER_IS_SIGNER` | 409 | Observer already signs | Activo | abajo | | `OBSERVER_NOT_FOUND` | 404 | Observer not found | Activo | abajo | | `PAYLOAD_TOO_LARGE` | 413 | Request Entity Too Large | Reservado | sin ejemplo verificado | | `PDF_ALREADY_SIGNED` | 422 | PDF already signed | Activo | abajo | | `PERMISSION_DENIED` | 403 | Permission denied | Activo | abajo | | `PLAN_REQUIRED` | 403 | Plan does not include this template | Activo | sin ejemplo verificado | | `PREVIEW_UNAVAILABLE` | 404 | Preview unavailable | Activo | abajo | | `QUOTA_EXCEEDED` | 429 | Too Many Requests | Reservado | sin ejemplo verificado | | `RATE_LIMITED` | 429 | Rate limited | Activo | sin ejemplo verificado | | `RECIPIENT_NOT_SIGNER` | 422 | Recipient is not a signer | Activo | abajo | | `RESOURCE_NOT_FOUND` | 404 | Resource not found | Activo | abajo | | `ROLE_NOT_FOUND` | 404 | Role not found | Activo | abajo | | `ROLE_OCCUPIED` | 409 | Role is held by a signer | Activo | abajo | | `SEGURIDATA_UNAVAILABLE` | 503 | Constancia provider unavailable | Activo | sin ejemplo verificado | | `SEQUENTIAL_NOT_AVAILABLE` | 403 | Signing order not available | Activo | sin ejemplo verificado | | `SERVICE_UNAVAILABLE` | 503 | Service Unavailable | Activo | sin ejemplo verificado | | `SIGNER_INVALID` | 422 | Signer invalid | Activo | abajo | | `SIGNER_NOT_FOUND` | 404 | Not Found | Activo | abajo | | `SIGNING_ORDER_INCOMPLETE` | 422 | Signing order incomplete | Activo | abajo | | `TEMPLATE_ENDPOINT_REQUIRED` | 422 | Template endpoint required | Activo | abajo | | `TEMPLATE_IN_USE` | 409 | Template in use | Activo | abajo | | `TEMPLATE_NOT_FOUND` | 404 | Template not found | Activo | abajo | | `TOKEN_AUDIENCE_MISMATCH` | 403 | Forbidden | Reservado | sin ejemplo verificado | | `TOKEN_EXPIRED` | 401 | Unauthorized | Reservado | sin ejemplo verificado | | `TOKEN_INVALID` | 401 | Unauthorized | Reservado | sin ejemplo verificado | | `UNKNOWN_EVENT_TYPE` | 422 | Unknown event type | Activo | abajo | | `UNSUPPORTED_API_VERSION` | 400 | Unsupported API version | Activo | abajo | | `UNSUPPORTED_FORM_XFA` | 422 | Unsupported XFA form | Activo | abajo | | `UNSUPPORTED_MEDIA_TYPE` | 415 | Unsupported Media Type | Reservado | sin ejemplo verificado | | `VALIDATION_ERROR` | 422 | Validation failed | Activo | abajo | | `WEBHOOK_LIMIT_EXCEEDED` | 409 | Conflict | Activo | sin ejemplo verificado | | `WEBHOOK_NOT_FOUND` | 404 | Webhook endpoint not found | Activo | abajo | | `WEBHOOK_URL_INVALID` | 422 | Webhook URL invalid | Activo | abajo | **Así se ve `ANNEX_OF_ANNEX_NOT_ALLOWED`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_aa07ee47bbea45edb879fdc2ac9db8e7/annexes (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#ANNEX_OF_ANNEX_NOT_ALLOWED", "title": "Conflict", "status": 409, "detail": "This document is itself an annex. Attach the new annex to the root document instead.", "instance": "/v3/documents/doc_aa07ee47bbea45edb879fdc2ac9db8e7/annexes", "code": "ANNEX_OF_ANNEX_NOT_ALLOWED", "requestId": "req_afd05ccadf4e4d4e8c22a38f25e3d8c7" } ``` **Así se ve `AUTHENTICATION_REQUIRED`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/documents (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#AUTHENTICATION_REQUIRED", "title": "Authentication required", "status": 401, "detail": "Provide an API key via Authorization: Bearer $ALLSIGN_API_KEY…", "instance": "/v3/documents", "code": "AUTHENTICATION_REQUIRED", "requestId": "req_b18d2960e68d4135ad139055bdf24f98" } ``` **Así se ve `BATCH_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/documents/bulk-sends/bat_e6282f72d60345d384d2b16cf0700624 (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#BATCH_NOT_FOUND", "title": "Bulk-send batch not found", "status": 404, "detail": "No bulk-send batch was found with that id.", "instance": "/v3/documents/bulk-sends/bat_e6282f72d60345d384d2b16cf0700624", "code": "BATCH_NOT_FOUND", "requestId": "req_7e1406b4cbd74967a85f8b8844351fe6" } ``` **Así se ve `CATALOG_TEMPLATE_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/templates/catalog/no-existe:copy (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#CATALOG_TEMPLATE_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "No published catalog template was found with slug 'no-existe'.", "instance": "/v3/templates/catalog/no-existe:copy", "code": "CATALOG_TEMPLATE_NOT_FOUND", "requestId": "req_97a848684ace46d8bb72f456f96c237b" } ``` **Así se ve `CONSTANCIA_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/constancias/cst_6d09d152ab804645b8884b7e20337328 (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#CONSTANCIA_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "No constancia was found with that id.", "instance": "/v3/constancias/cst_6d09d152ab804645b8884b7e20337328", "code": "CONSTANCIA_NOT_FOUND", "requestId": "req_c8a10a1a99bb4db09e4d9b3a9f42a9ef" } ``` **Así se ve `DEV_FEATURE_RESTRICTED`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/bulk-sends (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#DEV_FEATURE_RESTRICTED", "title": "Forbidden", "status": 403, "detail": "The feature 'bulk_send' is not available with your current API key (environment: test).", "instance": "/v3/documents/bulk-sends", "code": "DEV_FEATURE_RESTRICTED", "requestId": "req_87e254c1249946cfbdc8516819d10deb", "featureRequested": "bulk_send", "environment": "test", "allowedFeatures": [ "firma_autografa", "firma_simple", "embedded_signing" ], "allowedFeaturesDisplay": [ "Firma Autógrafa (Wet Signature)", "Firma Simple (Click-to-sign)", "Embedded Signing Widget" ], "upgradeMessage": "This feature is only available with a production key (allsign_live_sk_…). Create a live key in your AllSign dashboard under Developers → API Keys.", "upgradeUrl": "https://allsign.io/pricing" } ``` **Así se ve `DOCUMENT_ALREADY_SIGNED`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_f225e42012ba44dd853a06e460b02674/save-to-template (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_ALREADY_SIGNED", "title": "Document already signed", "status": 409, "detail": "A document with signatures already on it cannot be saved as a template.", "instance": "/v3/documents/doc_f225e42012ba44dd853a06e460b02674/save-to-template", "code": "DOCUMENT_ALREADY_SIGNED", "requestId": "req_6d22adad6f034dcb857a752eed5af7a9" } ``` **Así se ve `DOCUMENT_CONFLICT`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_09bf1cccce9b463691037861ca4eea6f/observers (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_CONFLICT", "title": "Conflict", "status": 409, "detail": "The document was voided; it no longer takes observers.", "instance": "/v3/documents/doc_09bf1cccce9b463691037861ca4eea6f/observers", "code": "DOCUMENT_CONFLICT", "requestId": "req_77042c955bec4f2b8bd00e6388216505" } ``` **Así se ve `DOCUMENT_NOT_EDITABLE`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_19deea649a6e421585db7e09bdc1faa4/fields:from-document (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_EDITABLE", "title": "Conflict", "status": 409, "detail": "This document is already in signing; its fields can no longer be imported.", "instance": "/v3/documents/doc_19deea649a6e421585db7e09bdc1faa4/fields:from-document", "code": "DOCUMENT_NOT_EDITABLE", "requestId": "req_1cd7885fff374f8bb522d76be322ad0e" } ``` **Así se ve `DOCUMENT_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/documents/doc_9c9f2f6749794c8c8717b0fd5458f074/file (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND", "title": "Document not found", "status": 404, "detail": "Document not found", "instance": "/v3/documents/doc_9c9f2f6749794c8c8717b0fd5458f074/file", "code": "DOCUMENT_NOT_FOUND", "requestId": "req_9099fea09e7e4822a33e00dde0d7dc14" } ``` **Así se ve `DOCUMENT_NOT_FULLY_SIGNED`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_78e3621115ea4fbc88605ae1c3118ec6/annexes (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FULLY_SIGNED", "title": "Conflict", "status": 409, "detail": "Annexes can only be attached to a fully signed document. This document's current status does not allow it.", "instance": "/v3/documents/doc_78e3621115ea4fbc88605ae1c3118ec6/annexes", "code": "DOCUMENT_NOT_FULLY_SIGNED", "requestId": "req_3f16b52d15ec400c8cc87067adfef964" } ``` **Así se ve `DOCUMENT_NOT_SENDABLE`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_82fa050abbc44b9587b71c8f84e8f923/send (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_SENDABLE", "title": "Conflict", "status": 409, "detail": "The document requires handwritten signatures (signatureValidation.autografa=true) but has no signature fields placed, so it cannot be sent for signing. Place at least one signature field before sending.", "instance": "/v3/documents/doc_82fa050abbc44b9587b71c8f84e8f923/send", "code": "DOCUMENT_NOT_SENDABLE", "requestId": "req_fdb94dfd9e8e41809ba7db813e2097e4", "reason": "The document requires handwritten signatures (signatureValidation.autografa=true) but has no signature fields placed, so it cannot be sent for signing. Place at least one signature field before sending." } ``` **Así se ve `DOCUMENT_TOO_LARGE`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_TOO_LARGE", "title": "Request Entity Too Large", "status": 413, "detail": "The file (10.0 MB decoded) exceeds the 10 MB limit for source=file.", "instance": "/v3/documents", "code": "DOCUMENT_TOO_LARGE", "requestId": "req_96b273bd7e82482aa0d45651aeb4631c" } ``` **Así se ve `DUPLICATE_SIGNER`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_1dc79b7f4dd64d08ba9d3ed7a5626977/signers/sgr_ea59f1a241c543029768ecb7f38a65bb/reassign (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#DUPLICATE_SIGNER", "title": "Duplicate signer", "status": 422, "detail": "Esa persona ya es firmante de este documento.", "instance": "/v3/documents/doc_1dc79b7f4dd64d08ba9d3ed7a5626977/signers/sgr_ea59f1a241c543029768ecb7f38a65bb/reassign", "code": "DUPLICATE_SIGNER", "requestId": "req_3851c69baf2940bf874d02b256d8021a" } ``` **Así se ve `EXPAND_DEPTH_EXCEEDED`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/documents/doc_8434c2beca964c7b959f96e5bbc4d1b3?expand=signers,fields,events,annexes (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#EXPAND_DEPTH_EXCEEDED", "title": "Bad Request", "status": 400, "detail": "At most 3 expand paths are allowed.", "instance": "/v3/documents/doc_8434c2beca964c7b959f96e5bbc4d1b3", "code": "EXPAND_DEPTH_EXCEEDED", "requestId": "req_df05e47a00434d8db363f5efdbd30f97" } ``` **Así se ve `EXTERNAL_ID_CONFLICT`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/constancias (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#EXTERNAL_ID_CONFLICT", "title": "Conflict", "status": 409, "detail": "externalId 'pedido-muzi0on9' already identifies a constancia for another document (hash 1872a4d06486…). Use a different externalId, or send the same hash to retrieve the one already issued.", "instance": "/v3/constancias", "code": "EXTERNAL_ID_CONFLICT", "requestId": "req_0d782805b9534c43bd20d8468e9b3ce2" } ``` **Así se ve `FIELD_CONFLICT`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/templates/tmpl_4a870c692ac14e2d81be7f9c134d527a/fields (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#FIELD_CONFLICT", "title": "Conflict", "status": 409, "detail": "A field named 'nombre' already exists in this template.", "instance": "/v3/templates/tmpl_4a870c692ac14e2d81be7f9c134d527a/fields", "code": "FIELD_CONFLICT", "requestId": "req_c1f5a7de743a4edb8e6969784838bc7e", "errors": [ { "field": "name", "code": "DUPLICATE", "detail": "Field name already exists." } ] } ``` **Así se ve `FIELD_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a DELETE /v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields/rfc (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#FIELD_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Field 'rfc' not found in this template.", "instance": "/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields/rfc", "code": "FIELD_NOT_FOUND", "requestId": "req_dc07200411324e03b6bbce3cca2ea6de" } ``` **Así se ve `FOLDER_NOT_EMPTY`** · verificado 8 oct 2026. Respuesta real de sandbox a DELETE /v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#FOLDER_NOT_EMPTY", "title": "Conflict", "status": 409, "detail": "Folder has documents. Move or delete them before deleting the folder.", "instance": "/v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c", "code": "FOLDER_NOT_EMPTY", "requestId": "req_0f54ba0e06d645f9adae72d71abadcc9" } ``` **Así se ve `FOLDER_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#FOLDER_NOT_FOUND", "title": "Folder not found", "status": 404, "detail": "No folder was found with that id.", "instance": "/v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c", "code": "FOLDER_NOT_FOUND", "requestId": "req_8014888fbbde44e0845518524e2aee04" } ``` **Así se ve `IDEMPOTENCY_KEY_IN_PROGRESS`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_IN_PROGRESS", "title": "Conflict", "status": 409, "detail": "A request with this Idempotency-Key is still in progress.", "instance": "/v3/documents", "code": "IDEMPOTENCY_KEY_IN_PROGRESS", "requestId": "req_2e344a0ea69a46418d8a96c1de58ba64", "retryAfter": 2 } ``` **Así se ve `IDEMPOTENCY_KEY_INVALID`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_INVALID", "title": "Bad Request", "status": 400, "detail": "Idempotency-Key must be a UUID v4.", "instance": "/v3/documents", "code": "IDEMPOTENCY_KEY_INVALID", "requestId": "req_e289ae908d284f7bbbdfc6abc2178d32" } ``` **Así se ve `IDEMPOTENCY_KEY_REQUIRED`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_REQUIRED", "title": "Bad Request", "status": 400, "detail": "POST requests that create or charge require a unique Idempotency-Key (UUID v4).", "instance": "/v3/documents", "code": "IDEMPOTENCY_KEY_REQUIRED", "requestId": "req_48ab6b50c363414c8d0e41850d05d5f5" } ``` **Así se ve `IDEMPOTENCY_KEY_REUSED`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_REUSED", "title": "Idempotency key reused", "status": 409, "detail": "This Idempotency-Key was already used with a different request.", "instance": "/v3/documents", "code": "IDEMPOTENCY_KEY_REUSED", "requestId": "req_7a71eb8164e14a01a6760499df342f6f" } ``` **Así se ve `INVALID_CURSOR`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/documents?startingAfter=pagina-2 (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_CURSOR", "title": "Bad Request", "status": 400, "detail": "The pagination cursor is malformed.", "instance": "/v3/documents", "code": "INVALID_CURSOR", "requestId": "req_631464956abd4a51be5d9580e50e14b8" } ``` **Así se ve `INVALID_EXPAND`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/documents/doc_8434c2beca964c7b959f96e5bbc4d1b3?expand=firmantes (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_EXPAND", "title": "Bad Request", "status": 400, "detail": "Unknown expand path(s): firmantes.", "instance": "/v3/documents/doc_8434c2beca964c7b959f96e5bbc4d1b3", "code": "INVALID_EXPAND", "requestId": "req_2042308d311a44c693926d1a9b56dea3" } ``` **Así se ve `INVALID_ID`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/documents/doc_123/evidence (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_ID", "title": "Malformed identifier", "status": 400, "detail": "Invalid document id.", "instance": "/v3/documents/doc_123/evidence", "code": "INVALID_ID", "requestId": "req_8a1bc74e22d945a6ad9358d2f8b27ec2" } ``` **Así se ve `INVALID_STATE_TRANSITION`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_ca783e90f0444172a7fbe75400ab5ebc/signers/sgr_f70f3376d8fc450a97111874e086f6b6/remind (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_STATE_TRANSITION", "title": "Conflict", "status": 409, "detail": "Document is in terminal state 'ANULADO' — cannot remind.", "instance": "/v3/documents/doc_ca783e90f0444172a7fbe75400ab5ebc/signers/sgr_f70f3376d8fc450a97111874e086f6b6/remind", "code": "INVALID_STATE_TRANSITION", "requestId": "req_18baf385999e4893975db73d70c38511", "reason": "document_terminal" } ``` **Así se ve `METHOD_NOT_ALLOWED`** · verificado 8 oct 2026. Respuesta real de sandbox a PUT /v3/documents, una petición que el contrato no define, backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#METHOD_NOT_ALLOWED", "title": "Method Not Allowed", "status": 405, "detail": "Method Not Allowed", "instance": "/v3/documents", "code": "METHOD_NOT_ALLOWED", "requestId": "req_d9780cacad2345e8859a2ada12147116" } ``` **Así se ve `NOT_YOUR_TURN`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/signers/sgr_0dcad5b836254b2791fab1bb6e6995f2/remind (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#NOT_YOUR_TURN", "title": "Conflict", "status": 409, "detail": "Signer belongs to stage 3 and stage 1 has not finished — nothing to remind yet.", "instance": "/v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/signers/sgr_0dcad5b836254b2791fab1bb6e6995f2/remind", "code": "NOT_YOUR_TURN", "requestId": "req_fda92aa97e3743a9805cbc1eaee1292c", "reason": "not_your_turn" } ``` **Así se ve `OBSERVER_IS_SIGNER`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_5dbd1cc097404878a5668c6a00774830/observers (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#OBSERVER_IS_SIGNER", "title": "Conflict", "status": 409, "detail": "ana@ejemplo.com already signs this document; a signer cannot also be an observer.", "instance": "/v3/documents/doc_5dbd1cc097404878a5668c6a00774830/observers", "code": "OBSERVER_IS_SIGNER", "requestId": "req_68de996d2d6442b981652319043cc610" } ``` **Así se ve `OBSERVER_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a DELETE /v3/documents/doc_5dbd1cc097404878a5668c6a00774830/observers/rol_245aa9e7229547b5b3e5cf30afe54094 (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#OBSERVER_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Observer not found in this document. Organization observers are removed from Settings → Observers.", "instance": "/v3/documents/doc_5dbd1cc097404878a5668c6a00774830/observers/rol_245aa9e7229547b5b3e5cf30afe54094", "code": "OBSERVER_NOT_FOUND", "requestId": "req_22602288f4704ad2857ebd2b3dfa1468" } ``` **Así se ve `PDF_ALREADY_SIGNED`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/templates (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#PDF_ALREADY_SIGNED", "title": "PDF already signed", "status": 422, "detail": "This PDF already carries a digital signature, and editing it would invalidate it. Upload the unsigned version or a flat PDF.", "instance": "/v3/templates", "code": "PDF_ALREADY_SIGNED", "requestId": "req_7ce35e2fb6a24801a7f2dcd25b07db59" } ``` **Así se ve `PERMISSION_DENIED`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/signing-sessions/ses_ee38d79b09ad45b9ad094034c1910ecc/init (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#PERMISSION_DENIED", "title": "Permission denied", "status": 403, "detail": "The origin 'https://otro.ejemplo.com' is not authorized to embed this session.", "instance": "/v3/signing-sessions/ses_ee38d79b09ad45b9ad094034c1910ecc/init", "code": "PERMISSION_DENIED", "requestId": "req_beaface9931b45e4a4eb9e51c8b13f9c" } ``` **Así se ve `PREVIEW_UNAVAILABLE`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/preview (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#PREVIEW_UNAVAILABLE", "title": "Not Found", "status": 404, "detail": "This template has no renderable page image.", "instance": "/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/preview", "code": "PREVIEW_UNAVAILABLE", "requestId": "req_b9de17df65f64abb9601e36d7b31625a" } ``` **Así se ve `RECIPIENT_NOT_SIGNER`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_966231febee54daeb77ff684f988b07b/send (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#RECIPIENT_NOT_SIGNER", "title": "Unprocessable Entity", "status": 422, "detail": "These recipients are not signers of this document: maria@ejemplo.com. Attach signers with `signers[]` when you create the document.", "instance": "/v3/documents/doc_966231febee54daeb77ff684f988b07b/send", "code": "RECIPIENT_NOT_SIGNER", "requestId": "req_381f6083e0434eebb1fba906d993129d", "errors": [ { "field": "recipients[0].email", "code": "NOT_A_SIGNER", "detail": "maria@ejemplo.com is not a signer of this document." } ] } ``` **Así se ve `RESOURCE_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/signing-sessions (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#RESOURCE_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "No participant with email nadie@ejemplo.com on document c2af0193-88b8-4767-be86-e9bc40106542", "instance": "/v3/signing-sessions", "code": "RESOURCE_NOT_FOUND", "requestId": "req_243a82a23c1a45aba4757d8ed3758d31" } ``` **Así se ve `ROLE_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a DELETE /v3/documents/doc_d60c6183f829411b8d033f2fc4c9477d/roles/rol_1d2abe482f0045a8b0fc16c94d49005f (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#ROLE_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Role not found in this document.", "instance": "/v3/documents/doc_d60c6183f829411b8d033f2fc4c9477d/roles/rol_1d2abe482f0045a8b0fc16c94d49005f", "code": "ROLE_NOT_FOUND", "requestId": "req_6724d101e4b248a7bd87d2f7ffd6e6ac" } ``` **Así se ve `ROLE_OCCUPIED`** · verificado 8 oct 2026. Respuesta real de sandbox a DELETE /v3/documents/doc_d60c6183f829411b8d033f2fc4c9477d/roles/rol_00823ecb9d294d13869402400226758e (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#ROLE_OCCUPIED", "title": "Conflict", "status": 409, "detail": "Role 'Arrendador' is held by a signer. Remove the signer first, then delete the role.", "instance": "/v3/documents/doc_d60c6183f829411b8d033f2fc4c9477d/roles/rol_00823ecb9d294d13869402400226758e", "code": "ROLE_OCCUPIED", "requestId": "req_8704c255f6cf49999ae7d48dfa1ace21" } ``` **Así se ve `SIGNER_INVALID`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_1dc79b7f4dd64d08ba9d3ed7a5626977/signers/sgr_ea59f1a241c543029768ecb7f38a65bb/reassign (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#SIGNER_INVALID", "title": "Signer invalid", "status": 422, "detail": "Es la misma persona; no hay nada que reasignar.", "instance": "/v3/documents/doc_1dc79b7f4dd64d08ba9d3ed7a5626977/signers/sgr_ea59f1a241c543029768ecb7f38a65bb/reassign", "code": "SIGNER_INVALID", "requestId": "req_922f9a91fb0c491495d387fe0f98bc12", "reason": "same_person" } ``` **Así se ve `SIGNER_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_ca783e90f0444172a7fbe75400ab5ebc/signers/sgr_8bc0129ec73d4d49b5d2c0335a209b8d/remind (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#SIGNER_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Signer '8bc0129e-c73d-4d49-b5d2-c0335a209b8d' not found in this document.", "instance": "/v3/documents/doc_ca783e90f0444172a7fbe75400ab5ebc/signers/sgr_8bc0129ec73d4d49b5d2c0335a209b8d/remind", "code": "SIGNER_NOT_FOUND", "requestId": "req_aa688e8a2b7e46ab88bb8e67a281ba61" } ``` **Así se ve `SIGNING_ORDER_INCOMPLETE`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents/doc_2d02df9682eb4daa89e1dcf73635d684/send (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#SIGNING_ORDER_INCOMPLETE", "title": "Unprocessable Entity", "status": 422, "detail": "Every signer is in the same stage, so signing in order would behave exactly like signing all at once. Use signingOrder 'parallel' or spread the signers across different stages.", "instance": "/v3/documents/doc_2d02df9682eb4daa89e1dcf73635d684/send", "code": "SIGNING_ORDER_INCOMPLETE", "requestId": "req_977bab702af14b0790961d843440acd6", "problems": [ "Every signer is in the same stage, so signing in order would behave exactly like signing all at once. Use signingOrder 'parallel' or spread the signers across different stages." ], "reason": "single_stage" } ``` **Así se ve `TEMPLATE_ENDPOINT_REQUIRED`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#TEMPLATE_ENDPOINT_REQUIRED", "title": "Template endpoint required", "status": 422, "detail": "Esta plantilla firma en orden y esta ruta no aplica su orden de firma: el documento nacería con todos los firmantes a la vez. Créalo con POST /v3/templates/tmpl_30cc50acf8d447c285146e8992ba3dc4/documents. No se creó ningún documento.", "instance": "/v3/documents", "code": "TEMPLATE_ENDPOINT_REQUIRED", "requestId": "req_9e0b14b183204b34b6c451acc11c8f23", "templateId": "tmpl_30cc50acf8d447c285146e8992ba3dc4" } ``` **Así se ve `TEMPLATE_IN_USE`** · verificado 8 oct 2026. Respuesta real de sandbox a DELETE /v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539 (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#TEMPLATE_IN_USE", "title": "Conflict", "status": 409, "detail": "Template 2a1bc982-e2f0-4c97-9ff8-d8c2a6468539 is in use by 1 document(s) and 0 chain(s). Archive or complete them before deleting.", "instance": "/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539", "code": "TEMPLATE_IN_USE", "requestId": "req_b63edfb7b1c9427f8ea3e6ac34f145b3" } ``` **Así se ve `TEMPLATE_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#TEMPLATE_NOT_FOUND", "title": "Template not found", "status": 404, "detail": "Template not found or has been deleted.", "instance": "/v3/documents", "code": "TEMPLATE_NOT_FOUND", "requestId": "req_252364b6627a443285dda69be2eb4213" } ``` **Así se ve `UNKNOWN_EVENT_TYPE`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/webhooks (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#UNKNOWN_EVENT_TYPE", "title": "Unprocessable Entity", "status": 422, "detail": "One or more event types are not in the v3 catalog.", "instance": "/v3/webhooks", "code": "UNKNOWN_EVENT_TYPE", "requestId": "req_45571c3852cb4ac6bae34f64dc886a28", "errors": [ { "field": "events[0]", "code": "UNKNOWN_EVENT_TYPE", "detail": "'documento.firmado' is not a valid v3 event type." } ] } ``` **Así se ve `UNSUPPORTED_API_VERSION`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/documents (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#UNSUPPORTED_API_VERSION", "title": "Unsupported API version", "status": 400, "detail": "Unknown AllSign-Version '2020-01-01'. Supported: 2026-07-11.", "instance": "/v3/documents", "code": "UNSUPPORTED_API_VERSION", "requestId": "req_bef341c69ac84957ad49e9fc8f4f5a4d" } ``` **Así se ve `UNSUPPORTED_FORM_XFA`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/templates (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#UNSUPPORTED_FORM_XFA", "title": "Unsupported XFA form", "status": 422, "detail": "This PDF uses an XFA (LiveCycle) form, which is not supported. Export it as a standard AcroForm PDF and try again.", "instance": "/v3/templates", "code": "UNSUPPORTED_FORM_XFA", "requestId": "req_fdd1b0f5006646e5a3a2894a39851d3f" } ``` **Así se ve `VALIDATION_ERROR`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/documents (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#VALIDATION_ERROR", "title": "Validation failed", "status": 422, "detail": "The values are not valid. Fix the fields listed in `errors`. You can test a set of values for free with POST /v3/templates/{id}/validate-values.", "instance": "/v3/documents", "code": "VALIDATION_ERROR", "requestId": "req_71750d833b65413ea2c9794e81405461", "errors": [ { "field": "templateValues.ciudad_celebracion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.fecha_celebracion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.folio_acuerdo", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.jurisdiccion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.monto_pena", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.monto_pena_letra", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.objeto_relacion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__domicilio", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__email", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__representante", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__rfc", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__telefono", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__domicilio", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__email", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__nombre", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__representante", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__rfc", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__telefono", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.tipo_informacion_confidencial", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.vigencia", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.vigencia_post_terminacion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." } ] } ``` **Así se ve `WEBHOOK_NOT_FOUND`** · verificado 8 oct 2026. Respuesta real de sandbox a GET /v3/webhooks/whe_54c1d95de8c64a369095bc044665b5b3 (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#WEBHOOK_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "No webhook endpoint was found with that id.", "instance": "/v3/webhooks/whe_54c1d95de8c64a369095bc044665b5b3", "code": "WEBHOOK_NOT_FOUND", "requestId": "req_e424fdd005fc4298b0231312f2126597" } ``` **Así se ve `WEBHOOK_URL_INVALID`** · verificado 8 oct 2026. Respuesta real de sandbox a POST /v3/webhooks (ver la operación), backend 24c0469. ``` { "type": "https://allsign.io/developers/docs/errors#WEBHOOK_URL_INVALID", "title": "Unprocessable Entity", "status": 422, "detail": "The webhook url must be an absolute https:// URL.", "instance": "/v3/webhooks", "code": "WEBHOOK_URL_INVALID", "requestId": "req_4404713e03b346ffbe800e0deab820a0", "errors": [ { "field": "url", "code": "INVALID_VALUE", "detail": "Must start with https://" } ] } ``` ## Correlación con AllSign-Request-Id Cada respuesta trae el header `AllSign-Request-Id: req_…` y lo repite en `requestId` dentro del cuerpo del error. Cítalo al reportar un problema — es la llave para rastrear la petición en nuestros logs. --- Fuente: https://allsign.io/developers/docs/index.md API v3 · Firma electrónica con validez legal # La API de firma que se integra sola Firma electrónica con biometría, anti-deepfake y NOM-151 desde una API — camelCase, errores RFC 9457, paginación cursor, idempotencia y webhooks estándar. ## Para agentes y asistentes de IA - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** El prefijo de la key decide el entorno: `allsign_test_sk_` / `allsign_dev_sk_` = sandbox, `allsign_live_sk_` = producción. Para pasar a producción solo cambias la key. → [Entornos](https://allsign.io/developers/docs/environments) - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) cURL ``` curl "https://api.allsign.io/v3/documents" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "source": "template", "templateId": "tmpl_781b330d64722697116d41b9d3263760", "templateValues": { "nombre_completo": "Juan Pérez", "monto": "$150,000.00" }, "signers": [ { "email": "juan@ejemplo.com", "name": "Juan Pérez" } ] }' ``` NOM-151 Constancia de conservación con validez legal en México. FEA · e.firma SAT Firma Electrónica Avanzada con tu e.firma del SAT. Biometría anti-deepfake Selfie contra identificación oficial, al momento de firmar. Video de firma Graba al firmante durante todo el proceso. Firma embebida El firmante nunca sale de tu producto — firma en un iframe. Webhooks firmados HMAC + reintentos automáticos, formato Standard Webhooks. ## Dime qué quieres construir El asistente te hace una pregunta a la vez y te arma la receta de integración a tu medida. beta ## Qué hay aquí [Documents Crear, enviar, anular y consultar documentos de firma.](https://allsign.io/developers/docs/endpoints/documents) [Templates Plantillas de solo lectura y sus variables.](https://allsign.io/developers/docs/endpoints/templates) [Folders Organiza documentos en carpetas.](https://allsign.io/developers/docs/endpoints/folders) [Analytics KPIs, embudo de firma y actividad del equipo.](https://allsign.io/developers/docs/endpoints/analytics) [Users El principal autenticado y tu equipo.](https://allsign.io/developers/docs/endpoints/users) [Signing Sessions Firma embebida en tu propio producto.](https://allsign.io/developers/docs/endpoints/signing-sessions) [Constancias NOM-151 Constancia de conservación desde un hash, sin subir el documento.](https://allsign.io/developers/docs/endpoints/constancias) Conceptos: [Quickstart](https://allsign.io/developers/docs/quickstart) · [Autenticación](https://allsign.io/developers/docs/authentication) · [Errores](https://allsign.io/developers/docs/errors) (84 códigos) · [Paginación](https://allsign.io/developers/docs/pagination) · [Idempotencia](https://allsign.io/developers/docs/idempotency) · [Versionado](https://allsign.io/developers/docs/versioning) · [Webhooks](https://allsign.io/developers/docs/webhooks) · [Entornos](https://allsign.io/developers/docs/environments) · [Rate limits](https://allsign.io/developers/docs/rate-limits) · [Migración v2→v3](https://allsign.io/developers/docs/migration). ## Preguntas frecuentes ¿Qué formato usan las respuestas? camelCase en el wire, errores RFC 9457 (application/problem+json) con un code estable y append-only, paginación por cursor e ids con prefijo (doc\_, tmpl\_, fld\_…). ¿Cómo me entero cuando se firma un documento? Con el webhook document.completed, firmado con Standard Webhooks (cabeceras webhook-id / webhook-timestamp / webhook-signature, HMAC-SHA256): llega cuando firma el último firmante y trae el PDF de evidencia por URL. Solo si tu entorno no puede recibir webhooks, consulta GET /v3/documents/{id}/events con backoff. ¿Qué es NOM-151 y por qué importa? Es la norma mexicana de conservación de mensajes de datos. La constancia NOM-151 la emite Seguridata, Prestador de Servicios de Certificación acreditado por la Secretaría de Economía; AllSign la solicita por ti y la adjunta al expediente — es lo que le da validez legal a la firma en México. ¿Cómo evito cobros o invitaciones dobles al reintentar? Manda un Idempotency-Key en los POST que cobran o firman; reintentar con la misma key devuelve la primera respuesta, nunca ejecuta dos veces. ¿Vengo de la v2? La guía de migración v2→v3 mapea casing, errores, ids y paginación campo por campo. Contrato: `AllSign API v3` v`2026-07-11` · 111 operaciones · sitio vanilla (MPA, HTML server-rendered, ~0 KB JS salvo el asistente). --- Fuente: https://allsign.io/developers/docs/webhooks.md # Webhooks Un webhook es un endpoint HTTPS tuyo que AllSign notifica con un `POST` **firmado** cada vez que algo relevante pasa en tu tenant —un documento se crea, se envía, se completa o se anula— sin que tengas que hacer polling. Todo endpoint v3 se crea firmado (HMAC obligatorio) y sellado con una versión de contrato con fecha. ## Para agentes y asistentes de IA - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** El webhook llega cuando firmó el último firmante y trae el PDF de evidencia por URL. Si tu entorno no puede recibir webhooks, usa `GET /v3/documents/{id}/events` con backoff. ## ¿Cómo funcionan? 1. **Registras** una URL `https://` de tu servidor con [`POST /v3/webhooks`](#create-endpoint) y eliges los eventos. 2. AllSign te devuelve un **secreto de firma** (`whsec_…`) **una sola vez**. Guárdalo —no se vuelve a mostrar. 3. Cuando ocurre un evento, AllSign hace un `POST` a tu URL con el [sobre del evento](#el-sobre-del-evento) en el body y las cabeceras de firma [Standard Webhooks](#firma-standard-webhooks) (`webhook-id` / `webhook-timestamp` / `webhook-signature`). 4. Tu servidor **verifica la firma**, deduplica por `eventId`, procesa el evento y responde `2xx` rápido. ``` Tu servidor ← POST (evento firmado) ← AllSign ``` Ids opacos con prefijo (`whe_` endpoint, `whd_` entrega, `evt_` evento), **camelCase** en el wire, errores [`problem+json`](https://allsign.io/developers/docs/errors) (RFC 9457) con `code` en `UPPER_SNAKE`, y las listas de endpoints y entregas paginan por cursor (`startingAfter` / `endingBefore` + `hasMore`). Cada respuesta trae headers `RateLimit-*`. **Scopes.** Crear, editar y rotar exige `webhook:write`; leer (listar, consultar, entregas, catálogo) exige `webhook:read`; borrar exige `webhook:delete`. Una key sin el scope recibe **403 `PERMISSION_DENIED`** con `requiredScope`. ## Create endpoint POST `/v3/webhooks` Registra un endpoint firmado. Genera un secreto \`whsec\_\` (\*\*devuelto una sola vez\*\*), fuerza HMAC, y estampa la versión de contrato con fecha (\`apiVersion\`) + el entorno de la key — no hay cruce \`live\`/\`test\`. Requiere \`webhook:write\`. ## List endpoints GET `/v3/webhooks` Lista tus endpoints de webhook con paginación por cursor. \*\*El secreto nunca aparece aquí\*\* — solo \`secretLast4\`. ## Retrieve endpoint GET `/v3/webhooks/{webhook_id}` Consulta un endpoint por su \`id\`. No incluye el secreto (solo \`secretLast4\`). Un \`id\` inexistente o de otro tenant responde \*\*404 \`WEBHOOK\_NOT\_FOUND\`\*\*. ## Update endpoint PATCH `/v3/webhooks/{webhook_id}` Merge-patch: solo cambian los campos que envías. Puedes reasignar \`url\`, \`events\`, \`description\`, o pausar/reactivar con \`disabled\`. Enviar un campo desconocido o inmutable es un \*\*422 \`VALIDATION\_ERROR\`\*\*. Requiere \`webhook:write\`. ## Delete endpoint DELETE `/v3/webhooks/{webhook_id}` Elimina un endpoint. Requiere el scope \`webhook:delete\`. Las entregas en vuelo hacia ese endpoint se marcan como fallidas (\`webhook deleted\`). ## Rotate secret POST `/v3/webhooks/{webhook_id}/rotate-secret` Acuña un secreto \`whsec\_\` nuevo. El anterior se conserva como \*secreto previo\* durante una \*\*ventana de 24 h\*\* en la que el despachador firma con \*\*ambos\*\* — así rotas sin downtime. Requiere \`webhook:write\` y honra \`Idempotency-Key\` (un reintento con la misma llave reproduce el mismo secreto en vez de rotar dos veces). ## List deliveries GET `/v3/webhooks/{webhook_id}/deliveries` El log de entregas de un endpoint — para depurar qué se envió, qué respondió tu servidor y cuántos intentos hubo. Pagina por cursor. Requiere \`webhook:read\`. ## List events GET `/v3/webhooks/events` El catálogo \*\*congelado\*\* de eventos v3 a los que puedes suscribirte — la fuente de verdad contra la que valida \`POST /v3/webhooks\`. Requiere \`webhook:read\`. ## El sobre del evento Cada webhook v3 llega como un **sobre** camelCase con el payload específico dentro de `data`. **No** hay un campo `event` a nivel raíz (eso era un alias v2); el enrutamiento va por la cabecera `AllSign-Event`. Deduplica por `eventId`. ``` { "eventId": "evt_7c9e6679742540de944be07fc1f90ae7", "eventType": "document.completed", "apiVersion": "2026-07-11", "occurredAt": "2026-07-11T19:03:00.123Z", "tenantId": "550e8400-e29b-41d4-a716-446655440000", "livemode": true, "data": { } } ``` | Campo | Descripción | | --- | --- | | `eventId` | ID único del evento (`evt_…`). **Úsalo para deduplicar** —un reintento del productor reusa el mismo `eventId`. La cabecera `webhook-id` trae este mismo id, pero como UUID crudo (ver [cabeceras](#el-sobre-del-evento)). | | `eventType` | El tipo de evento (ej. `document.completed`). Coincide con la cabecera `AllSign-Event`. | | `apiVersion` | Versión de contrato con fecha que congela la forma del `data`. | | `occurredAt` | Cuándo ocurrió (ISO-8601 UTC con sufijo `Z`, precisión de milisegundos). | | `tenantId` | Tu tenant. | | `livemode` | `true` si el evento nació en entorno `live`. | | `data` | El payload específico del evento (ver el [catálogo](#catalogo-de-eventos)). | ### Cabeceras de cada entrega AllSign firma con **Standard Webhooks** ([standardwebhooks.com](https://www.standardwebhooks.com/)) —el estándar abierto que ya adoptaron OpenAI, Anthropic, Twilio y Supabase, entre otros. La ventaja práctica: puedes verificar con la **librería oficial** (`standardwebhooks`, disponible en 10+ lenguajes) en vez de escribir tu propio verificador. | Cabecera | Descripción | | --- | --- | | `webhook-id` | ID **estable** del evento —el mismo en todos los reintentos de un mismo evento. Viaja como **UUID crudo con guiones** (ej. `7c9e6679-7425-40de-944b-e07fc1f90ae7`), **no** con la forma `evt_…`. | | `webhook-timestamp` | Unix timestamp (segundos) con el que se firmó **este intento**. Un reintento trae uno nuevo. | | `webhook-signature` | La firma —ver [Firma Standard Webhooks](#firma-standard-webhooks). | | `AllSign-Event` | Tipo de evento (ej. `document.completed`), metadata de conveniencia —**no forma parte de lo firmado**, no lo uses para verificar. | | `AllSign-Delivery-Id` | ID de la **entrega** (el envío de este evento a este endpoint), a diferencia de `webhook-id`, que identifica el evento. También viaja como **UUID crudo**. | | `AllSign-Livemode` | `true` si el evento nació en entorno `live`, `false` en sandbox. Viaja en **toda** entrega, de ambas cohortes (ver [cohortes](#las-dos-cohortes)). | **El id viaja en dos representaciones.** El header `webhook-id` y el `eventId` del sobre son **el mismo id**, pero el header lo trae como UUID crudo (con guiones) y el sobre como `evt_` (con prefijo, sin guiones). Un dedup que compare el header contra `eventId` tal cual **nunca va a coincidir**, y un check `startsWith('evt_')` sobre el header rechazaría el 100% de las entregas. Deduplica por **uno solo** de los dos (recomendado: el `eventId` del sobre) o normaliza antes de comparar. Lo mismo aplica a `AllSign-Delivery-Id`: el header trae el UUID crudo y [`GET /v3/webhooks/{id}/deliveries`](#list-deliveries) lista esa misma entrega con su id `whd_`. `webhook-id` / `webhook-timestamp` / `webhook-signature` van en **minúsculas** —así los define la spec Standard Webhooks (los nombres de cabecera HTTP son case-insensitive de todos modos). `AllSign-Event` / `AllSign-Delivery-Id` / `AllSign-Livemode` siguen el estilo Hyphenated-Pascal-Case del resto de la v3, sin prefijo `X-` (RFC 6648). ## Firma Standard Webhooks Cada entrega v3 trae la cabecera `webhook-signature`: ``` webhook-signature: v1,g0hM9SsE+OTPJTGeGg9CTHqYPnJZQrfE7BMc4b1rBz8= ``` - `v1` —la versión del esquema de firma (siempre `v1` hoy). - El valor tras la coma es el **HMAC-SHA256 en base64** (no hex). **El material firmado es `"{webhook-id}.{webhook-timestamp}." + cuerpo_crudo`** —el id del evento, un punto, el timestamp, otro punto, y luego los **bytes crudos del body** tal como llegan (nunca un JSON re-serializado). La clave HMAC es el contenido de tu secreto **después de quitarle el prefijo `whsec_`**, decodificado de `base64url` a bytes crudos —nunca la cadena `whsec_…` completa en UTF-8. **Ventana de repetición: 5 minutos.** Rechaza cualquier entrega cuyo `webhook-timestamp` difiera más de `300` s de tu reloj. El timestamp viaja en su propia cabecera (`webhook-timestamp`), no empaquetado dentro de la firma —pero sigue formando parte del material firmado, así que no se puede manipular sin invalidar la firma. **Durante una [rotación de secreto](#rotate-secret)** la cabecera trae **dos** tokens separados por espacio: `v1, v1,`. Verifica contra tus secretos candidatos y acepta si **cualquiera** hace match —así ninguna entrega falla durante la ventana de 24 h. ### Verificar la firma en tu servidor La forma recomendada es la librería oficial `standardwebhooks` —ya maneja el parseo de cabeceras, la ventana de repetición y la rotación. Si no puedes agregar la dependencia, este es el fallback manual (Node.js), traducción 1:1 de la fórmula publicada: ``` import crypto from 'crypto' // El secreto completo tal como lo devolvió AllSign, incluido el prefijo. const SECRET = process.env.ALLSIGN_WEBHOOK_SECRET // "whsec_…" const REPLAY_WINDOW_S = 300 function decodeKey(secret) { const raw = secret.replace(/^whsec_/, '') return Buffer.from(raw, 'base64url') // llave = secreto SIN el prefijo, decodificado } function verifyAllSign(rawBody, id, timestamp, signatureHeader) { // Ventana de repetición: rechaza si el webhook-timestamp difiere >300s del reloj. if (Math.abs(Date.now() / 1000 - Number(timestamp)) > REPLAY_WINDOW_S) return false const signedContent = Buffer.concat([ Buffer.from(`${id}.${timestamp}.`, 'utf8'), // "{webhook-id}.{webhook-timestamp}." rawBody, // los bytes CRUDOS del body, nunca un JSON re-serializado ]) const expected = crypto .createHmac('sha256', decodeKey(SECRET)) .update(signedContent) .digest('base64') // base64, no hex // Durante una rotación el header trae varios "v1," separados por espacio: // acepta si ALGUNO hace match. const candidates = signatureHeader .split(' ') .filter((tok) => tok.startsWith('v1,')) .map((tok) => tok.slice(3)) return candidates.some((sig) => { try { return crypto.timingSafeEqual(Buffer.from(expected, 'base64'), Buffer.from(sig, 'base64')) } catch { return false } }) } ``` ## Las dos cohortes: v3 y clásica (v2legacy) Cada endpoint de webhook lleva una versión de contrato (`apiVersion`) que decide el **formato completo del cable** —cuerpo, cabeceras y esquema de firma. Hoy existen dos: | | `2026-07-11` (v3) | `v2legacy` (clásica) | | --- | --- | --- | | **Cuerpo** | Sobre `camelCase` (el de esta página) | `snake_case`, congelado byte a byte | | **Cabeceras** | `webhook-id` / `webhook-timestamp` / `webhook-signature` + `AllSign-Event` / `AllSign-Delivery-Id` | `X-AllSign-Event` / `X-AllSign-Event-Id` / `X-AllSign-Timestamp` | | **Firma** | Standard Webhooks: HMAC-SHA256 en **base64** sobre `"{id}.{timestamp}." + body`; llave = el secreto **sin** `whsec_`, decodificado de base64url | `X-AllSign-Signature` (solo si el endpoint tiene HMAC habilitado): HMAC-SHA256 en **hex** sobre `"{timestamp}.{body}"`, con el ISO-8601 de `X-AllSign-Timestamp`; llave = los **bytes literales** del secreto | | **Entorno** | `livemode` en el sobre + cabecera `AllSign-Livemode` | Solo la cabecera `AllSign-Livemode` (el cuerpo congelado no trae el campo) | No existe ningún header llamado `AllSign-Signature` a secas: la cohorte clásica firma con `X-AllSign-Signature` y la v3 con `webhook-signature`. ### ¿En qué cohorte nace un endpoint? - **[`POST /v3/webhooks`](#create-endpoint) siempre crea v3** (`2026-07-11`): fuerza un secreto `whsec_` y la firma Standard Webhooks. Quien llama esta API pidió v3 explícitamente. - **Desde el dashboard, el endpoint hereda el formato de tu cuenta:** si ya tienes destinos clásicos, el nuevo nace `v2legacy` —lo más probable es que apunte al handler que ya tienes, y nacer en v3 te lo rompería sin que pidieras nada. Una cuenta que estrena integración nace en v3. Las dos cohortes nunca se mezclan en una entrega: un evento con ambas audiencias se despacha por separado a cada endpoint, cada uno con su formato, y ningún endpoint recibe el mismo evento dos veces. **Cambiar un endpoint de cohorte** hoy solo se puede **desde el dashboard**: el update por API (`PATCH /v3/webhooks/{id}`) no expone `apiVersion`. El cambio aplica a partir del siguiente intento de entrega. ## Reintentos y entregas fallidas Cada intento de entrega tiene un **timeout de 10 s** —tu endpoint debe responder `2xx` dentro de esa ventana (por eso: encola y procesa en background). El cuerpo máximo de una entrega es **50 MiB**; un payload que lo exceda se marca fallido sin intentar el `POST`. | Tu respuesta | Qué hace AllSign | | --- | --- | | `2xx` | Entrega `SENT`. Fin. | | `4xx` | Fallo **permanente**: reintentar el mismo payload no va a ayudar, la entrega queda `FAILED` de inmediato. | | `5xx`, timeout, error de conexión | Fallo **transitorio**: se reintenta con backoff. | El calendario de reintentos depende de la [cohorte](#las-dos-cohortes): - **v3:** backoff exponencial desde **2 s** (se duplica en cada intento) con tope de **6 h** entre intentos, durante hasta **3 días**. Si en 3 días tu endpoint no respondió `2xx`, la entrega queda `FAILED`. - **Clásica (`v2legacy`):** 8 intentos con backoff exponencial (2 s, 4 s, 8 s… 256 s — unos 8 minutos en total) y después `FAILED`. Cada reintento reusa el mismo `webhook-id` (y el mismo `eventId` del sobre) con un `webhook-timestamp` nuevo — la firma se recalcula por intento. El historial de intentos se consulta con [`GET /v3/webhooks/{id}/deliveries`](#list-deliveries). ## Catálogo de eventos Los eventos v3 son un catálogo **congelado y con versión con fecha**. `document.*` cubre el ciclo de vida del documento; `nom151.constancia.issued` avisa cuando se emite la constancia de conservación. `signer.declined` **ya dispara en sandbox** —lo emite el firmante mágico `signer-declined@sandbox.allsign.io` (ver [Entornos](https://allsign.io/developers/docs/environments))—; en `live` todavía no, porque la acción de rechazo del firmante aún no está disponible ahí (por eso el catálogo del contrato lo sigue etiquetando `reserved`). `document.fill_started` y `signer.fill_completed` están **obsoletos**: los firmantes ya no llenan variables. `document.fill_started` se dispara cuando un documento entra a `LLENANDO_DATOS` (variables de rol sin llenar de un documento anterior a este cambio); `signer.fill_completed` lo emiten `my-fill-submit` y el llenado del invitado en sesiones abiertas antes de este cambio. Siguen en el catálogo para no romper los webhooks que ya los tienen. Lo que captura el firmante ahora va en campos del formulario PDF (guía [Formularios PDF](https://allsign.io/developers/docs/guides/pdf-forms)). | Evento | Categoría | Estado | Qué representa | | --- | --- | --- | --- | | `document.created` | Documents | `active` | Se creó un documento vía la API. | | `document.sent` | Documents | `active` | El documento salió de creación y entró al ciclo de firma (primeras invitaciones despachadas). | | `document.completed` | Documents | `active` | Todas las partes firmaron y el PDF de evidencia está listo (se entrega por URL, no inline en base64). | | `document.voided` | Documents | `active` | El documento se anuló. La retención NOM-151 conserva el registro. | | `document.expired` | Documents | `active` | El documento llegó a su fecha límite sin completarse. Trae quién sí alcanzó a firmar. | | `document.owner_transferred` | Documents | `active` | A document changed owner inside its workspace (manual transfer, bulk transfer or member offboarding). Carries the previous and the new owner. | | `document.fill_started` | Documents | `active` | **Obsoleto.** Se dispara cuando un documento entra a `LLENANDO_DATOS`: variables de rol sin llenar de un documento anterior a este cambio, al invitar o iniciar la firma. Los documentos nuevos ya no pueden quedar así. | | `document.ready_to_sign` | Documents | `active` | The PDF was materialized with the filled data and is ready to sign. | | `signer.turn_started` | Signers | `active` | Sequential signing: a later stage opened because the previous one closed, and the signer's invitation was dispatched. Not emitted for the first stage on send — use document.sent for that. | | `document.stage_advanced` | Documents | `active` | Sequential signing: every signer of a stage signed and the next stage was invited. | | `signer.signed` | Signers | `active` | One signer completed their signature. Carries the running progress. | | `signer.fill_completed` | Signers | `active` | **Obsoleto.** Lo emiten `my-fill-submit` (el llenado propio del dueño) y el llenado del invitado en sesiones abiertas antes de este cambio. Los firmantes ya no llenan variables. | | `signer.reminder_sent` | Signers | `active` | A signing reminder was sent to a signer (email or WhatsApp). | | `signer.delivery_failed` | Signers | `active` | A signing invitation could not be delivered to a signer (WhatsApp failure or email bounce). | | `signer.declined` | Signers | `reserved` | Un firmante rechazó firmar. **Ya dispara en sandbox** (vía el firmante mágico `signer-declined@sandbox.allsign.io`); en `live` aún no hay acción de rechazo. | | `document.observer_added` | Documents | `active` | An observer was added to a document (or its capability/events changed). Observers never sign. | | `document.observer_removed` | Documents | `active` | An observer was removed from a document; its read-only links were revoked. | | `nom151.constancia.issued` | Compliance | `active` | Se emitió la constancia de conservación NOM-151 de un documento completado. | ### Payload: `document.created` ``` { "eventId": "evt_7c9e6679742540de944be07fc1f90ae7", "eventType": "document.created", "apiVersion": "2026-07-11", "occurredAt": "2026-07-11T18:04:00.000Z", "tenantId": "550e8400-e29b-41d4-a716-446655440000", "livemode": true, "data": { "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG", "name": "Contrato de arrendamiento 2026.pdf", "status": "draft", "createdAt": "2026-07-11T18:04:00Z", "createdViaApi": true, "signers": [ { "signerId": "sgr_63db6fa927094f689ea7bc640194bade", "name": "Juan Pérez", "email": "juan@empresa.com", "phone": null, "status": "waiting_for_signature" } ] } } ``` ### Payload: `document.sent` ``` { "eventId": "evt_a1b2c3d4e5f67890abcdef1234567890", "eventType": "document.sent", "apiVersion": "2026-07-11", "occurredAt": "2026-07-11T18:10:00.000Z", "tenantId": "550e8400-e29b-41d4-a716-446655440000", "livemode": true, "data": { "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG", "name": "Contrato de arrendamiento 2026.pdf", "status": "awaiting_signatures", "sentAt": "2026-07-11T18:10:00Z", "signers": [ { "signerId": "sgr_63db6fa927094f689ea7bc640194bade", "email": "juan@empresa.com", "phone": null, "invitationChannel": "email", "invitedAt": "2026-07-11T18:10:00Z" } ] } } ``` ### Payload: `document.completed` El PDF de evidencia se entrega **por URL** (el endpoint estable de la API, `GET /v3/documents/{id}/evidence`, que acuña una URL prefirmada fresca cada vez que lo pides con tu API key), **nunca** en base64 inline. `nom151.constanciaUrl` apunta al mismo endpoint: su respuesta trae la constancia en `nom151`. ``` { "eventId": "evt_b2c3d4e5f6a78901bcdef12345678901", "eventType": "document.completed", "apiVersion": "2026-07-11", "occurredAt": "2026-07-11T19:03:00.000Z", "tenantId": "550e8400-e29b-41d4-a716-446655440000", "livemode": true, "data": { "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG", "name": "Contrato de arrendamiento 2026.pdf", "status": "completed", "completedAt": "2026-07-11T19:03:00Z", "evidencePdf": { "url": "https://api.allsign.io/v3/documents/doc_5Qr9tA3fZwLZmp3D1bCdEfG/evidence", "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "sizeBytes": 204800, "mimeType": "application/pdf" }, "nom151": { "constanciaUrl": "https://api.allsign.io/v3/documents/doc_5Qr9tA3fZwLZmp3D1bCdEfG/evidence", "serialNumber": "12345", "issuedAt": "2026-07-11T19:03:30Z" }, "signers": [ { "signerId": "sgr_63db6fa927094f689ea7bc640194bade", "name": "Juan Pérez", "email": "juan@empresa.com", "signedAt": "2026-07-11T19:02:00Z", "authMethod": "FIRMA_ELECTRONICA_SIMPLE" } ] } } ``` **`authMethod` es un valor crudo, no un enum cerrado.** Viaja tal cual quedó registrado en la firma. Los valores que existen hoy: `POR_DEFINIR` (el flujo no registró un método específico — el más común), `FIRMA_ELECTRONICA_SIMPLE` y `FIRMA_ELECTRONICA_AVANZADA_SAT` (firma con e.firma del SAT). También puede venir `null`. Trátalo como cadena informativa: no hagas un *match* estricto ni un `switch` exhaustivo — pueden aparecer valores nuevos sin cambio de versión. Aplica igual en `signer.signed`. ### Payload: `document.voided` ``` { "eventId": "evt_c3d4e5f6a7b89012cdef123456789012", "eventType": "document.voided", "apiVersion": "2026-07-11", "occurredAt": "2026-07-11T20:00:00.000Z", "tenantId": "550e8400-e29b-41d4-a716-446655440000", "livemode": true, "data": { "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG", "status": "voided", "voidedAt": "2026-07-11T20:00:00Z", "reason": "Reemplazado por una versión corregida", "previousStatus": "awaiting_signatures", "cancelledSignatures": 1, "voidedBy": { "actorType": "user", "actorId": "usr_1a2b3c4d5e6f7g8h" } } } ``` ### Payload: `document.expired` Fíjate en `signers[].signedAt`: en `null` marca a quien no alcanzó a firmar. Un vencimiento con 2 de 3 firmas se resuelve distinto a uno con cero, así que el evento trae el corte completo en vez de obligarte a pedirlo aparte. `expiresAt` es la fecha que venció y `expiredAt` el momento en que el sistema lo marcó —no coinciden, porque el barrido corre periódicamente. ``` { "eventId": "evt_e5f6a7b8c9d01234ef12345678901234", "eventType": "document.expired", "apiVersion": "2026-07-11", "occurredAt": "2026-07-12T00:05:00.000Z", "tenantId": "550e8400-e29b-41d4-a716-446655440000", "livemode": true, "data": { "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG", "name": "Contrato de arrendamiento", "status": "expired", "expiresAt": "2026-07-11T23:59:59Z", "expiredAt": "2026-07-12T00:05:00Z", "signedCount": 1, "totalSigners": 2, "signers": [ { "signerId": "sgr_9f8e7d6c5b4a3210", "name": "Ana Ruiz", "email": "ana@ejemplo.mx", "signedAt": "2026-07-10T16:20:00Z" }, { "signerId": "sgr_1a2b3c4d5e6f7080", "name": "Beto Lara", "email": "beto@ejemplo.mx", "signedAt": null } ] } } ``` ### Payload: `signer.signed` El evento de avance: dispara cada vez que **un** firmante completa su firma, con el corte de cuántos van. Si solo te importa el final, usa `document.completed`; si quieres seguir el progreso, éste es el que buscas. ``` { "eventId": "evt_a1b2c3d4e5f60789ab12cd34ef567890", "eventType": "signer.signed", "apiVersion": "2026-07-11", "occurredAt": "2026-07-12T18:41:02.000Z", "tenantId": "550e8400-e29b-41d4-a716-446655440000", "livemode": true, "data": { "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG", "name": "Contrato de arrendamiento", "signer": { "signerId": "sgr_9f8e7d6c5b4a3210", "name": "Ana Ruiz", "email": "ana@ejemplo.mx", "phone": null, "signedAt": "2026-07-12T18:41:02Z", "authMethod": "FIRMA_ELECTRONICA_SIMPLE" }, "signedCount": 1, "totalSigners": 3 } } ``` ### Payload: `document.ready_to_sign` El PDF se materializó con los valores de las variables y quedó congelado. A partir de aquí los firmantes ven el documento final, nunca `{{variables}}` sin resolver. ``` { "eventId": "evt_d4e5f6a7b8c90123de45f6789abcdef0", "eventType": "document.ready_to_sign", "apiVersion": "2026-07-11", "occurredAt": "2026-07-12T16:21:03.000Z", "tenantId": "550e8400-e29b-41d4-a716-446655440000", "livemode": true, "data": { "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG", "name": "Contrato de arrendamiento", "status": "awaiting_signatures", "pdfs": [ { "pdfId": "3f2e1d0c-9b8a-4765-a432-10fedcba9876", "pdfHash": "9c1185a5c5e9fc54612808977ee8f548b2258d31" } ] } } ``` ### Payload: `signer.reminder_sent` Se envió un recordatorio a un firmante que aún no firma. `daysRemaining` son los días que le quedan antes de que el documento venza. En la API v2 este evento se llama `signature.reminder_sent`. En v3 se nombra por el recurso al que se refiere; si migras un endpoint de v2 a v3, el nombre cambia solo. ``` { "eventId": "evt_e5f6a7b8c9d01234ef56789abcdef012", "eventType": "signer.reminder_sent", "apiVersion": "2026-07-11", "occurredAt": "2026-07-14T09:00:00.000Z", "tenantId": "550e8400-e29b-41d4-a716-446655440000", "livemode": true, "data": { "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG", "signerId": "sgr_1a2b3c4d5e6f7080", "channel": "whatsapp", "daysRemaining": 3, "recipient": "+521234567890", "sentAt": "2026-07-14T09:00:00Z" } } ``` ### Payload: `signer.declined` **Ya se emite en sandbox:** el firmante mágico `signer-declined@sandbox.allsign.io` lo dispara, así que puedes probar tu manejador de punta a punta hoy. En `live` todavía no llega, porque la acción de rechazo del firmante aún no está disponible en producción — suscribirte desde ya no rompe nada. ``` { "eventId": "evt_f6a7b8c9d0e12345f6789abcdef01234", "eventType": "signer.declined", "apiVersion": "2026-07-11", "occurredAt": "2026-07-12T19:15:00.000Z", "tenantId": "550e8400-e29b-41d4-a716-446655440000", "livemode": true, "data": { "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG", "signer": { "signerId": "sgr_1a2b3c4d5e6f7080", "name": "Beto Lara", "email": "beto@ejemplo.mx" }, "declinedAt": "2026-07-12T19:15:00Z", "reason": "El monto no corresponde a lo acordado" } } ``` ### Payload: `nom151.constancia.issued` ``` { "eventId": "evt_d4e5f6a7b8c90123def1234567890123", "eventType": "nom151.constancia.issued", "apiVersion": "2026-07-11", "occurredAt": "2026-07-11T19:03:30.000Z", "tenantId": "550e8400-e29b-41d4-a716-446655440000", "livemode": true, "data": { "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG", "constancia": { "url": "https://api.allsign.io/v3/documents/doc_5Qr9tA3fZwLZmp3D1bCdEfG/evidence", "serialNumber": "12345", "issuedAt": "2026-07-11T19:03:30Z", "algorithm": "SHA256" }, "evidenceSha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" } } ``` El SDK `@allsign/sdk` (beta —aún no publicado en npm) trae helpers para verificar la firma y tipar el sobre del evento. ## Buenas prácticas - **Verifica la firma** en cada entrega antes de procesar (código arriba). - **Deduplica por `eventId`** —puedes recibir el mismo evento más de una vez. - **Responde `2xx` rápido** —el timeout por intento es de 10 s; procesa en background. Un fallo transitorio se reintenta (ver [Reintentos](#reintentos-y-entregas-fallidas)). - **Ramifica por `livemode`** —trata los eventos `test` y `live` por caminos separados; nunca mezcles datos de prueba con producción. - **Usa HTTPS** —AllSign solo entrega a URLs `https://`. - **Rota el secreto** periódicamente con [rotate-secret](#rotate-secret); la ventana de 24 h evita downtime. --- Fuente: https://allsign.io/developers/docs/endpoints/documents.md # Documents Un documento representa un archivo listo para firma electrónica con biometría, anti-deepfake y NOM-151. En la API v3 un **solo endpoint** (`POST /v3/documents`) crea el documento a partir de una **plantilla** o de un **PDF subido en base64** — tú eliges la fuente con el campo `source`. Todas las respuestas usan **camelCase** en el wire, ids opacos con prefijo (`doc_`, `tmpl_`, `fld_`, `sgr_`, `evt_`) y `livemode` para distinguir entorno `live` de `test`. Los errores siguen **problem+json** (RFC 9457) con un campo `code` en `UPPER_SNAKE`. Las listas paginan por cursor (`startingAfter` / `endingBefore` + `hasMore`) y las respuestas traen headers `RateLimit-*`. Si el documento nace de una plantilla PDF con formulario, sus campos viven en `GET …/fields`: quién los llena (`role`, `signerId`), qué valor tienen, si el emisor los prellenó y bloqueó (`values`/`readOnly` al crear, `filledBy: "owner"`) y quién puso cada valor (`filledBy`, `filledAt`). Guía: [Formularios PDF](https://allsign.io/developers/docs/guides/pdf-forms). Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). [Descargar OpenAPI](https://allsign.io/developers/docs/openapi.public.json)[Colección de Postman](https://allsign.io/developers/docs/allsign-api-v3.postman_collection.json) Endpoints - [GET`/documents`](#list-documents) - [GET`/documents/stats`](#get-aggregate-statistics) - [POST`/documents`](#create-document) - [GET`/documents/{document_id}`](#retrieve-document) - [PATCH`/documents/{document_id}`](#update-document) - [POST`/documents/{document_id}/send`](#send-document) - [POST`/documents/{document_id}/void`](#void-document) - [GET`/documents/{document_id}/signers`](#list-signers) - [PATCH`/documents/{document_id}/signers/{signer_id}`](#change-a-signer-s-stage) - [GET`/documents/{document_id}/fields`](#list-document-fields) - [POST`/documents/{document_id}/fields`](#create-document-field) - [PATCH`/documents/{document_id}/fields/{field_id}`](#update-document-field) - [DELETE`/documents/{document_id}/fields/{field_id}`](#delete-document-field) - [POST`/documents/{document_id}/fields:assign-role`](#assign-fields-to-a-role) - [GET`/documents/{document_id}/roles`](#list-document-roles) - [DELETE`/documents/{document_id}/roles/{role_id}`](#delete-a-document-role) - [POST`/documents/{document_id}/fields:from-document`](#import-signing-fields-from-a-document) - [GET`/documents/{document_id}/file`](#download-document-file) - [GET`/documents/{document_id}/values`](#get-document-values) - [PATCH`/documents/{document_id}/values`](#update-document-values) - [POST`/documents/{document_id}/save-to-template`](#save-the-document-as-a-template-version) - [GET`/documents/{document_id}/events`](#list-events) - [GET`/documents/{document_id}/evidence`](#get-evidence-bundle) - [POST`/documents/{document_id}/annexes`](#attach-an-annex) - [GET`/documents/{document_id}/family`](#get-a-document-s-family) - [POST`/documents/{document_id}/signers/{signer_id}/remind`](#remind-signer) - [POST`/documents/{document_id}/signers/remind-all`](#remind-all-pending-signers) - [POST`/documents/{document_id}/signers/{signer_id}/reassign`](#reassign-signer) - [POST`/sandbox/signers/{signer_id}/sign`](#sign-as-a-signer-sandbox-only) - [GET`/documents/{document_id}/observers`](#list-document-observers) - [POST`/documents/{document_id}/observers`](#add-document-observer) - [DELETE`/documents/{document_id}/observers/{observer_id}`](#remove-document-observer) - [POST`/documents/{document_id}/transfer-owner`](#transfer-document-owner) - [POST`/documents/transfer-owner`](#transfer-documents-owner) - [DELETE`/documents/bulk`](#bulk-delete-documents) - [POST`/documents/bulk-sends`](#create-bulk-send) - [GET`/documents/bulk-sends/{batch_id}`](#get-bulk-send) ## El objeto Document #### Atributos - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido ID del documento (`doc_…`). - `object` string, siempre "document" Siempre `"document"`. - `name` string requerido Nombre visible del documento. - `status` enum requerido Estado del ciclo de vida: `draft`, `collecting_data`, `awaiting_signatures`, `correcting`, `processing`, `completed`, `expired`, `voided`, `error`. Valores: `draft` `collecting_data` `awaiting_signatures` `correcting` `processing` `completed` `expired` `voided` `error` - `documentType` string requerido Token opaco del tipo de documento. - `signerCount` integer requerido Número total de firmantes. - `signedCount` integer requerido Cuántos firmantes ya firmaron. - `ownerId` string requerido Dueño del documento (`usr_…`). - `orgId` string puede ser null Organización del documento (`org_…`). - `folderId` string puede ser null Carpeta que contiene el documento (`fld_…`). Es `null` si el documento no está en ninguna carpeta que esta credencial pueda abrir con `GET /v3/folders/{id}`. - `expiresAt` string (fecha-hora ISO 8601) puede ser null Fecha límite de firma (ISO 8601). - `signingOrder` enum `parallel` o `sequential` (firmantes por etapas con `routingOrder`). Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. Valores: `parallel` `sequential` - `currentStage` integer puede ser null Etapa activa en un documento `sequential` mientras espera firmas; `null` en paralelo o al terminar. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. - `expirationReminders` array de integer puede ser null Horas antes del vencimiento en las que se envían recordatorios. - `templateId` string puede ser null Plantilla de la que nació el documento (`tmpl_…`), o `null`. - `templateVersionId` string puede ser null Versión de esa plantilla cuyo layout se aplicó (`tv_…`), o `null`. - `parentDocumentId` string puede ser null Documento del que este es un anexo (`doc_…`). `null` si es un documento raíz. Ver `POST /documents/{id}/annexes` y `GET /documents/{id}/family`. - `createdAt` string (fecha-hora ISO 8601) requerido Fecha de creación (ISO 8601). - `updatedAt` string (fecha-hora ISO 8601) requerido Última actualización (ISO 8601). **El objeto Document** ``` { "livemode": false, "id": "doc_fdde14eeff0b4444acec4d6b10736068", "object": "document", "name": "Mi primer contrato (sandbox)", "status": "completed", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 1, "ownerId": "usr_08f394a003014001b429ca03db05b7f7", "orgId": "f4d8bad5-5bb0-451c-a917-2ae6b489d38f", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:01:02.144000Z", "updatedAt": "2026-07-11T18:01:05.421000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:19 (hora CDMX) · test `docs-checks-v3/tests/recetas/primer-documento-a-firma.receta.spec.ts` ## List documents GET `/documents` Lista tus documentos con paginación por cursor y filtros. Para la página siguiente manda el `nextCursor` de la respuesta como `startingAfter`; para la anterior, el `previousCursor` como `endingBefore`. Los cursores son opacos: el `id` de un documento no sirve como cursor (`400 INVALID_CURSOR`). #### Parámetros - `limit` integer query Resultados por página (1–100, default `20`). default 20 · entre 1 y 100 - `startingAfter` string query Cursor opaco: el `nextCursor` de la página anterior. Devuelve la página que sigue. - `endingBefore` string query Cursor opaco: el `previousCursor` de la página actual. Devuelve la página anterior. - `status` enum query Filtra por estado: `draft`, `collecting_data`, `awaiting_signatures`, `correcting`, `processing`, `completed`, `expired`, `voided`. Valores: `draft` `collecting_data` `awaiting_signatures` `correcting` `processing` `completed` `expired` `voided` `error` - `status[not]` array de enum query Statuses to hide. Repeat the parameter to hide several, e.g. `?status[not]=voided&status[not]=expired`. Omit to return every status. Documents with no signature state are never hidden. Mutually exclusive with `status`. - `sort` enum query Orden. Solo `createdAt` / `updatedAt`, ascendente o descendente con el prefijo `-`. Valores: `createdAt`, `-createdAt`, `updatedAt`, `-updatedAt` (default `-createdAt`). Otro valor es un `422 VALIDATION_ERROR`. Valores: `createdAt` `-createdAt` `updatedAt` `-updatedAt` - `scope` enum query Alcance: `owner` (default), `org`, `tenant`, `accessible`. Valores: `owner` `org` `tenant` `accessible` - `folderId` string query Filtra por carpeta (`fld_…`). - `templateId` string query Keep only documents created from this template (`tmpl_…`). - `search` string query Búsqueda por texto libre en el nombre (1–255 caracteres). de 1 a 255 caracteres - `createdAt[gte]` string (fecha-hora ISO 8601) query Solo documentos creados en o después de esta fecha (ISO 8601). - `createdAt[lte]` string (fecha-hora ISO 8601) query Solo documentos creados en o antes de esta fecha (ISO 8601). - `includeTotal` boolean query Si es `true`, la respuesta incluye `totalCount`. Default `false` (más rápido). default false - `includeFiledDocuments` boolean query When false, hides documents the caller has filed into one of their own folders. The auto-created inbox folder does not count as filing. Ignored when `folderId` selects a folder. default true - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Sobre de paginación por cursor (`object: "list"`) con objetos Document. Ver los 7 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Arreglo de objetos Document. Ver 20 atributos hijos - `data[].livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `data[].id` string requerido ID del documento (`doc_…`). - `data[].object` string, siempre "document" Siempre `"document"`. - `data[].name` string requerido Nombre visible del documento. - `data[].status` enum requerido Estado del ciclo de vida: `draft`, `collecting_data`, `awaiting_signatures`, `correcting`, `processing`, `completed`, `expired`, `voided`, `error`. Valores: `draft` `collecting_data` `awaiting_signatures` `correcting` `processing` `completed` `expired` `voided` `error` - `data[].documentType` string requerido Token opaco del tipo de documento. - `data[].signerCount` integer requerido Número total de firmantes. - `data[].signedCount` integer requerido Cuántos firmantes ya firmaron. - `data[].ownerId` string requerido Dueño del documento (`usr_…`). - `data[].orgId` string puede ser null Organización del documento (`org_…`). - `data[].folderId` string puede ser null Carpeta que contiene el documento (`fld_…`). Es `null` si el documento no está en ninguna carpeta que esta credencial pueda abrir con `GET /v3/folders/{id}`. - `data[].expiresAt` string (fecha-hora ISO 8601) puede ser null Fecha límite de firma (ISO 8601). - `data[].signingOrder` enum `parallel` o `sequential` (firmantes por etapas con `routingOrder`). Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. Valores: `parallel` `sequential` - `data[].currentStage` integer puede ser null Etapa activa en un documento `sequential` mientras espera firmas; `null` en paralelo o al terminar. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. - `data[].expirationReminders` array de integer puede ser null Horas antes del vencimiento en las que se envían recordatorios. - `data[].templateId` string puede ser null Plantilla de la que nació el documento (`tmpl_…`), o `null`. - `data[].templateVersionId` string puede ser null Versión de esa plantilla cuyo layout se aplicó (`tv_…`), o `null`. - `data[].parentDocumentId` string puede ser null Documento del que este es un anexo (`doc_…`). `null` si es un documento raíz. Ver `POST /documents/{id}/annexes` y `GET /documents/{id}/family`. - `data[].createdAt` string (fecha-hora ISO 8601) requerido Fecha de creación (ISO 8601). - `data[].updatedAt` string (fecha-hora ISO 8601) requerido Última actualización (ISO 8601). - `hasMore` boolean requerido `true` si hay más resultados después de esta página. - `nextCursor` string puede ser null Cursor para la siguiente página (pásalo como `startingAfter`). - `previousCursor` string puede ser null Cursor para la página anterior (pásalo como `endingBefore`). - `limit` integer requerido El límite aplicado a esta página. - `totalCount` integer puede ser null Total de coincidencias. Solo se llena cuando pides `includeTotal=true`. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents?limit=2' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents?limit=2', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents?limit=2", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "doc_82fa050abbc44b9587b71c8f84e8f923", "object": "document", "name": "v3audit-ejemplos renombrado", "status": "draft", "documentType": "EDITABLE", "signerCount": 0, "signedCount": 0, "ownerId": "usr_0f24c8f44ca14ab387cf439de4bab66f", "orgId": "d5894966-ea17-4641-85c6-43eab7203145", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:01.278000Z" }, { "livemode": false, "id": "doc_8a984914f25548b880c312a7bc855763", "object": "document", "name": "v3audit-ejemplos anexo", "status": "draft", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_0f24c8f44ca14ab387cf439de4bab66f", "orgId": "d5894966-ea17-4641-85c6-43eab7203145", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": "doc_9feee6be06a5489c92590d3bf830768c", "createdAt": "2026-07-11T17:59:59.387000Z", "updatedAt": "2026-07-11T17:59:59.387000Z" } ], "hasMore": true, "nextCursor": "", "previousCursor": null, "limit": 2, "totalCount": null } ``` **Respuesta · 401 · AUTHENTICATION_REQUIRED** ``` { "type": "https://allsign.io/developers/docs/errors#AUTHENTICATION_REQUIRED", "title": "Authentication required", "status": 401, "detail": "Provide an API key via Authorization: Bearer $ALLSIGN_API_KEY…", "instance": "/v3/documents", "code": "AUTHENTICATION_REQUIRED", "requestId": "req_b18d2960e68d4135ad139055bdf24f98" } ``` **Respuesta · 400 · UNSUPPORTED_API_VERSION** ``` { "type": "https://allsign.io/developers/docs/errors#UNSUPPORTED_API_VERSION", "title": "Unsupported API version", "status": 400, "detail": "Unknown AllSign-Version '2020-01-01'. Supported: 2026-07-11.", "instance": "/v3/documents", "code": "UNSUPPORTED_API_VERSION", "requestId": "req_bef341c69ac84957ad49e9fc8f4f5a4d" } ``` **Respuesta · 400 · INVALID_CURSOR** ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_CURSOR", "title": "Bad Request", "status": 400, "detail": "The pagination cursor is malformed.", "instance": "/v3/documents", "code": "INVALID_CURSOR", "requestId": "req_631464956abd4a51be5d9580e50e14b8" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos.ejemplos.spec.ts` ## Get aggregate statistics GET `/documents/stats` Conteos agregados en el mismo `scope` que `GET /documents`. Sin rango de fechas, usa los últimos 365 días por default; `recentCount` siempre son los últimos 7 días **dentro** de esa ventana, no de todo el histórico. No es un objeto recurso (sin `livemode`) — es un resumen, como `List documents` pero contado en vez de listado. #### Parámetros - `scope` enum query Alcance: `owner` (default), `org`, `tenant`, `accessible`. Valores: `owner` `org` `tenant` `accessible` - `createdAt[gte]` string (fecha-hora ISO 8601) query Solo cuenta documentos creados en o después de esta fecha (ISO 8601). - `createdAt[lte]` string (fecha-hora ISO 8601) query Solo cuenta documentos creados en o antes de esta fecha (ISO 8601). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Los 5 conteos agregados. Ver los 6 atributos de la respuesta - `totalDocuments` integer requerido Total de documentos en el scope pedido. - `totalCompleted` integer requerido Documentos con flujo de firma completado. - `totalPending` integer requerido Documentos en `awaiting_signatures` o `collecting_data`. No cuenta borradores, anulados ni expirados. - `totalConfiguring` integer requerido Documentos en configuración, antes de enviarse. - `totalError` integer requerido Documentos cuyo estado de firma es `error` — algo falló procesándolos después de enviarse. No están incluidos en `totalPending`. - `recentCount` integer requerido Documentos creados en los últimos 7 días. Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/stats' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/stats', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/stats", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "totalDocuments": 119, "totalCompleted": 112, "totalPending": 2, "totalConfiguring": 2, "totalError": 0, "recentCount": 119 } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos.ejemplos.spec.ts` ## Create document POST `/documents` Crea un documento a partir de **una plantilla** (`source: "template"`) **o** de un **archivo subido en base64** (`source: "file"`) — nunca ambos. El campo `source` es un discriminador explícito; si lo omites, se infiere de cuál de `templateId` / `file` mandaste, pero cuando lo incluyes **debe coincidir** con el campo presente. Efectos en entorno `live`: consume 1 o más créditos, arranca un workflow de Temporal y sube el archivo a S3. Con una key `test` el documento es `livemode: false` y no factura. Cada firmante toma un **rol** de la plantilla con `roleName` y hereda sus campos; `values` prellena campos del formulario PDF por nombre y `readOnly` lista los que el firmante no puede cambiar (guía [Formularios PDF](https://allsign.io/developers/docs/guides/pdf-forms)). `signingOrder: "sequential"` + `routingOrder` por firmante invita por etapas. #### Parámetros - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `source` enum puede ser null Discriminador: `template`, `file` o `compound`. Opcional (se infiere), pero si lo mandas debe concordar con el campo presente. Valores: `template` `file` `compound` - `templateId` string puede ser null ID de una plantilla existente (`tmpl_…`). Requerido cuando `source` es `template`. - `templateValues` objeto (llave → valor) puede ser null Mapa de valores para las variables de la plantilla, con llaves naturales del negocio (ej. `nombre_completo`). Nunca se camelizan. - `values` objeto (llave → valor) puede ser null Prellenado de campos del formulario PDF por nombre de campo, para cualquier rol. Los `values` de cada firmante tienen prioridad sobre estos. - `readOnly` array de string puede ser null Nombres de campos prellenados que ningún firmante puede cambiar. - `file` objeto puede ser null Archivo inline cuando `source` es `file`. Mutuamente exclusivo con `templateId`/`documents`. Ver 3 atributos hijos - `file.content` string requerido Contenido del archivo codificado en Base64. Máximo 10 MB decodificados. - `file.fileType` string Tipo de archivo. Default `pdf`. default "pdf" - `file.name` string puede ser null Nombre del archivo (ej. `contrato.pdf`). Se valida contra path traversal. - `documents` array de objetos puede ser null Expediente compound: 1..N piezas (archivo o plantilla). Mutuamente exclusivo con el `file`/`templateId` de un solo origen. Mismo cerebro que `POST /v2/documents/` con `documents[]`. Ver 7 atributos hijos - `documents[].file` objeto puede ser null Archivo inline (PDF/DOCX en base64). Mutuamente exclusivo con `templateId`. Ver 3 atributos hijos - `documents[].file.content` string requerido Contenido del archivo codificado en Base64. Máximo 10 MB decodificados. - `documents[].file.fileType` string Tipo de archivo. Default `pdf`. default "pdf" - `documents[].file.name` string puede ser null Nombre del archivo (ej. `contrato.pdf`). Se valida contra path traversal. - `documents[].templateId` string puede ser null ID de plantilla (`tmpl_…`). Mutuamente exclusivo con `file`. - `documents[].templateValues` objeto (llave → valor) puede ser null Valores owner-fixed para variables de este item (llaves naturales, no camelCase). - `documents[].name` string puede ser null Nombre visible de esta pieza dentro del expediente. máx. 255 caracteres - `documents[].kind` enum Rol de la pieza: `main`, `attachment` o `cover`. Valores: `main` `attachment` `cover` default "main" - `documents[].position` integer puede ser null Orden 1-based de concatenación. Si se omite, se asigna 1..N por índice. mín. 1 - `documents[].dedupeScope` enum `document` comparte variables entre piezas; `pdf` las aísla a este item. Valores: `document` `pdf` default "document" - `fields` array de objetos puede ser null Campos de firma a colocar al crear (`anchorString` o `position`). Cada `email`/`phone` debe coincidir con un `signers[]`. En compound usa `documentPdfPosition` para anclar a una pieza. Ver 10 atributos hijos - `fields[].email` string puede ser null Correo del firmante (debe coincidir con un `signers[].email`). - `fields[].phone` string puede ser null WhatsApp del firmante (debe coincidir con un `signers[].phone`). - `fields[].pageNumber` integer puede ser null Página 1-based. En anclas, acota la búsqueda a esa página. - `fields[].documentPdfPosition` integer puede ser null Compound: posición 1-based del PDF de `documents[]` al que pertenece este campo. - `fields[].anchorString` string puede ser null Texto ancla en el PDF. Mutuamente exclusivo con `position`. - `fields[].anchorHorizontalAlignment` enum Alineación horizontal sobre el ancla. Ignorado en modo coordenadas. Valores: `left` `center` `right` default "left" - `fields[].anchorVerticalAlignment` enum Alineación vertical sobre el ancla. Ignorado en modo coordenadas. Valores: `top` `center` `bottom` default "center" - `fields[].position` objeto puede ser null Coordenadas {x,y} en puntos PDF. Mutuamente exclusivo con `anchorString`. Ver 2 atributos hijos - `fields[].position.x` number requerido - `fields[].position.y` number requerido - `fields[].height` number Alto del campo en puntos. El ancho es 2×. default 100 · mayor que 0 - `fields[].includeInAllPages` boolean Solo modo coordenadas: repetir el campo en todas las páginas. default false - `sendInvitations` boolean Si es true, dispara invitaciones al crear (equivale a v2 `config.sendInvitations`). Fuerza `startAtStep=3`. default false - `startAtStep` integer Paso inicial: 1=borrador, 2=campos, 3=esperando firmas (cobra al crear). default 1 · entre 1 y 3 - `folderId` string puede ser null Carpeta destino (`fld_…`). - `ownerEmail` string puede ser null Dueño del documento (email de un user del tenant). Equivale a v2 `permissions.ownerEmail`. - `name` string puede ser null Nombre visible del documento. Si se omite, se deriva de la plantilla o del archivo. - `signers` array de objetos puede ser null Firmantes a adjuntar al crear el documento. Ver 10 atributos hijos - `signers[].email` string puede ser null Correo del firmante. Manda `email` o `phone`, no los dos: cada firmante recibe su invitación por un solo canal (422 `VALIDATION_ERROR` si llegan ambos). - `signers[].phone` string puede ser null Teléfono del firmante (para invitación por WhatsApp). Excluye a `email`: un firmante lleva un solo canal. máx. 32 caracteres - `signers[].name` string puede ser null Nombre del firmante. máx. 255 caracteres - `signers[].roleName` string puede ser null Rol semántico del firmante (ej. `proveedor`), usado para auto-asignar variables de plantilla marcadas con ese rol. Opcional — si se omite, las variables deben asignarse manualmente. máx. 255 caracteres - `signers[].values` objeto (llave → valor) puede ser null Prellenado de campos del formulario PDF por nombre de campo (texto o casilla). Sólo aplica a plantillas PDF con campos; las llaves nunca se camelizan. - `signers[].readOnly` array de string puede ser null Nombres de campos prellenados que el firmante no puede cambiar. - `signers[].routingOrder` integer puede ser null Etapa de firma (1 = primera). Firmantes con el mismo número firman en paralelo. Solo aplica con `signingOrder: "sequential"`. Con `signingOrder: "sequential"` es **obligatorio en todos** los firmantes: si le falta a alguno, la creación se rechaza con 422 `VALIDATION_ERROR` diciendo en cuál falta, en vez de repartir etapas por su cuenta. Dentro del producto un firmante sin número cuenta como etapa 1, pero la API v3 no asume ese default: un `sequential` a medio numerar sería un documento que firma todo el mundo a la vez sin decirlo. Después de crear, la etapa se cambia con `PATCH /v3/documents/{documentId}/signers/{signerId}`. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. mín. 1 - `signers[].kind` enum `signer` (default) firma. `carbon_copy` es un observador: nunca firma ni recibe campos, solo los avisos de `events` y, con `capability: notify_and_view`, un enlace de solo lectura. Un observador necesita `email` y no lleva `roleName`, `routingOrder`, `values` ni `readOnly`. Después de crear se administran con `/v3/documents/{documentId}/observers`. Valores: `signer` `carbon_copy` default "signer" - `signers[].capability` enum puede ser null Solo observadores: `notify` (default) o `notify_and_view`. Valores: `notify` `notify_and_view` - `signers[].events` array de string puede ser null Solo observadores: avisos que recibe. `document.completed` (default), `document.expired`, `document.voided`. máx. 10 elementos - `signatureValidation` objeto puede ser null Nivel de validez legal (autógrafa/NOM-151/FEA/biometría/videofirma). Por default: solo autógrafa. Ver 6 atributos hijos - `signatureValidation.autografa` boolean Firma autógrafa (trazo en pantalla). default true - `signatureValidation.nom151` boolean Constancia de conservación NOM-151. default false - `signatureValidation.fea` boolean Firma Electrónica Avanzada (FEA/e.firma SAT). default false - `signatureValidation.biometricSignature` boolean Verificación biométrica (selfie vs. identificación, anti-deepfake). default false - `signatureValidation.idScan` boolean Escaneo de identificación oficial (INE, pasaporte). default false - `signatureValidation.videofirma` boolean Graba video del firmante durante el proceso de firma. default false - `expiresAt` string (fecha-hora ISO 8601) puede ser null Fecha límite de firma (ISO 8601). - `mode` enum puede ser null Pasa `draft` para crear el documento sin cobrar ni enviar invitaciones (fuerza `startAtStep=1`). Si se omite, se conserva el comportamiento actual. Valores: `draft` - `signingOrder` enum puede ser null `parallel` (default): todos los firmantes reciben su invitación a la vez. `sequential`: cada firmante trae `routingOrder` y solo la etapa activa puede firmar; la siguiente se invita cuando la anterior termina. `sequential` exige `routingOrder` en **cada** firmante (422 `VALIDATION_ERROR` si falta alguno) y `parallel` no lo admite en ninguno. El modo no se puede cambiar después; las etapas sí, con `PATCH /v3/documents/{documentId}/signers/{signerId}`. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. Valores: `parallel` `sequential` #### Devuelve **201** Documento creado (mismo shape que Retrieve document). — [el objeto Document](#objeto-document) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [402](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [413](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Contrato de servicios (sandbox)", "file": { "content": "'"$(base64 < contrato.pdf | tr -d '\n')"'", "fileType": "pdf", "name": "contrato.pdf" }, "signers": [ { "email": "signer-success@sandbox.allsign.io", "name": "Ana Torres" } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/documents', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Contrato de servicios (sandbox)', file: { content: readFileSync('contrato.pdf').toString('base64'), fileType: 'pdf', name: 'contrato.pdf', }, signers: [ { email: 'signer-success@sandbox.allsign.io', name: 'Ana Torres', }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/documents", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "Contrato de servicios (sandbox)", "file": { "content": base64.b64encode(Path("contrato.pdf").read_bytes()).decode(), "fileType": "pdf", "name": "contrato.pdf", }, "signers": [ { "email": "signer-success@sandbox.allsign.io", "name": "Ana Torres", }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "doc_c18e3aaf505d4e8aa839591f6d38f7b9", "object": "document", "name": "Contrato de servicios (sandbox)", "status": "draft", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_d6f2ef7bc72d4754940f12e71ad1964f", "orgId": "cc2dd4db-c027-496a-924f-bcdbb9b51387", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` **Respuesta · 422 · VALIDATION_ERROR** ``` { "type": "https://allsign.io/developers/docs/errors#VALIDATION_ERROR", "title": "Validation failed", "status": 422, "detail": "The values are not valid. Fix the fields listed in `errors`. You can test a set of values for free with POST /v3/templates/{id}/validate-values.", "instance": "/v3/documents", "code": "VALIDATION_ERROR", "requestId": "req_71750d833b65413ea2c9794e81405461", "errors": [ { "field": "templateValues.ciudad_celebracion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.fecha_celebracion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.folio_acuerdo", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.jurisdiccion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.monto_pena", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.monto_pena_letra", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.objeto_relacion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__domicilio", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__email", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__representante", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__rfc", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__telefono", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__domicilio", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__email", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__nombre", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__representante", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__rfc", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__telefono", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.tipo_informacion_confidencial", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.vigencia", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.vigencia_post_terminacion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." } ] } ``` **Respuesta · 404 · TEMPLATE_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#TEMPLATE_NOT_FOUND", "title": "Template not found", "status": 404, "detail": "Template not found or has been deleted.", "instance": "/v3/documents", "code": "TEMPLATE_NOT_FOUND", "requestId": "req_252364b6627a443285dda69be2eb4213" } ``` **Respuesta · 409 · IDEMPOTENCY_KEY_REUSED** ``` { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_REUSED", "title": "Idempotency key reused", "status": 409, "detail": "This Idempotency-Key was already used with a different request.", "instance": "/v3/documents", "code": "IDEMPOTENCY_KEY_REUSED", "requestId": "req_7a71eb8164e14a01a6760499df342f6f" } ``` **Respuesta · 413 · DOCUMENT_TOO_LARGE** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_TOO_LARGE", "title": "Request Entity Too Large", "status": 413, "detail": "The file (10.0 MB decoded) exceeds the 10 MB limit for source=file.", "instance": "/v3/documents", "code": "DOCUMENT_TOO_LARGE", "requestId": "req_96b273bd7e82482aa0d45651aeb4631c" } ``` **Respuesta · 422 · TEMPLATE_ENDPOINT_REQUIRED** ``` { "type": "https://allsign.io/developers/docs/errors#TEMPLATE_ENDPOINT_REQUIRED", "title": "Template endpoint required", "status": 422, "detail": "Esta plantilla firma en orden y esta ruta no aplica su orden de firma: el documento nacería con todos los firmantes a la vez. Créalo con POST /v3/templates/tmpl_30cc50acf8d447c285146e8992ba3dc4/documents. No se creó ningún documento.", "instance": "/v3/documents", "code": "TEMPLATE_ENDPOINT_REQUIRED", "requestId": "req_9e0b14b183204b34b6c451acc11c8f23", "templateId": "tmpl_30cc50acf8d447c285146e8992ba3dc4" } ``` **Respuesta · 409 · IDEMPOTENCY_KEY_IN_PROGRESS** ``` { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_IN_PROGRESS", "title": "Conflict", "status": 409, "detail": "A request with this Idempotency-Key is still in progress.", "instance": "/v3/documents", "code": "IDEMPOTENCY_KEY_IN_PROGRESS", "requestId": "req_2e344a0ea69a46418d8a96c1de58ba64", "retryAfter": 2 } ``` **Respuesta · 400 · IDEMPOTENCY_KEY_REQUIRED** ``` { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_REQUIRED", "title": "Bad Request", "status": 400, "detail": "POST requests that create or charge require a unique Idempotency-Key (UUID v4).", "instance": "/v3/documents", "code": "IDEMPOTENCY_KEY_REQUIRED", "requestId": "req_48ab6b50c363414c8d0e41850d05d5f5" } ``` **Respuesta · 400 · IDEMPOTENCY_KEY_INVALID** ``` { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_INVALID", "title": "Bad Request", "status": 400, "detail": "Idempotency-Key must be a UUID v4.", "instance": "/v3/documents", "code": "IDEMPOTENCY_KEY_INVALID", "requestId": "req_e289ae908d284f7bbbdfc6abc2178d32" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:14 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/errores.ejemplos.spec.ts` ## Retrieve document GET `/documents/{document_id}` Consulta un documento por su `id`. `folderId` es la carpeta (`fld_…`) donde está el documento, y es `null` si no está en una carpeta que tu key pueda abrir con `GET /v3/folders/{id}`. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `expand` string query Lista separada por comas de sub-recursos a expandir en línea. Si se omite, la respuesta trae solo los campos del propio documento. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El objeto Document. — [el objeto Document](#objeto-document) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_fdde14eeff0b4444acec4d6b10736068", "object": "document", "name": "Mi primer contrato (sandbox)", "status": "completed", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 1, "ownerId": "usr_08f394a003014001b429ca03db05b7f7", "orgId": "f4d8bad5-5bb0-451c-a917-2ae6b489d38f", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:01:02.144000Z", "updatedAt": "2026-07-11T18:01:05.421000Z" } ``` **Respuesta · 400 · INVALID_ID** ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_ID", "title": "Malformed identifier", "status": 400, "detail": "Invalid document id.", "instance": "/v3/documents/doc_contrato-de-renta", "code": "INVALID_ID", "requestId": "req_4b48db5236a44765a71ce07ab8cf59ac" } ``` **Respuesta · 400 · INVALID_EXPAND** ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_EXPAND", "title": "Bad Request", "status": 400, "detail": "Unknown expand path(s): firmantes.", "instance": "/v3/documents/doc_8434c2beca964c7b959f96e5bbc4d1b3", "code": "INVALID_EXPAND", "requestId": "req_2042308d311a44c693926d1a9b56dea3" } ``` **Respuesta · 400 · EXPAND_DEPTH_EXCEEDED** ``` { "type": "https://allsign.io/developers/docs/errors#EXPAND_DEPTH_EXCEEDED", "title": "Bad Request", "status": 400, "detail": "At most 3 expand paths are allowed.", "instance": "/v3/documents/doc_8434c2beca964c7b959f96e5bbc4d1b3", "code": "EXPAND_DEPTH_EXCEEDED", "requestId": "req_df05e47a00434d8db363f5efdbd30f97" } ``` **Respuesta · 404 · DOCUMENT_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND", "title": "Document not found", "status": 404, "detail": "No document was found with that id.", "instance": "/v3/documents/doc_dd56d81575b84eb790373c6dff92cb95", "code": "DOCUMENT_NOT_FOUND", "requestId": "req_c72bfc8e0f1f4fc69bcc8111e77dc52d" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:14 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/errores.ejemplos.spec.ts` ## Update document PATCH `/documents/{document_id}` Merge-patch parcial: solo se modifican los campos que envías. Los únicos campos mutables son `name` y `folderId`. `folderId: "fld_…"` mueve el documento a esa carpeta; `folderId: null` lo saca de su carpeta. Enviar cualquier otro campo (`status`, `ownerId`, `id`, `createdAt`, …) se rechaza al parsear con **422 `VALIDATION_ERROR`** nombrando el campo ofensor — así se protege un campo inmutable. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string puede ser null Nuevo nombre visible del documento. - `folderId` string puede ser null Mover a otra carpeta (`fld_…`), o `null` para sacarlo de la carpeta. #### Devuelve **200** El objeto Document actualizado. — [el objeto Document](#objeto-document) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X PATCH 'https://api.allsign.io/v3/documents/doc_ec50be6599c04f82a3d8bba51b5cbb24' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "folderId": "fld_c1c3fb2ea6634f929addda83f073a00c" }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_ec50be6599c04f82a3d8bba51b5cbb24', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ folderId: 'fld_c1c3fb2ea6634f929addda83f073a00c', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.patch( "https://api.allsign.io/v3/documents/doc_ec50be6599c04f82a3d8bba51b5cbb24", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "folderId": "fld_c1c3fb2ea6634f929addda83f073a00c", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_ec50be6599c04f82a3d8bba51b5cbb24", "object": "document", "name": "v3audit-ejemplos documento en carpeta", "status": "draft", "documentType": "EDITABLE", "signerCount": 0, "signedCount": 0, "ownerId": "usr_beadaffd52b943749b179fded8733b31", "orgId": "6332dfb0-7654-42fb-8d21-16dcecdee536", "folderId": "fld_c1c3fb2ea6634f929addda83f073a00c", "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T17:59:58.914000Z", "updatedAt": "2026-07-11T18:00:01.675000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/carpetas.ejemplos.spec.ts` ## Send document POST `/documents/{document_id}/send` Cobra los créditos, avanza el estado del documento y despacha las invitaciones de firma (email o WhatsApp vía Temporal) a los firmantes que adjuntaste al crearlo. `recipients` es opcional y solo sirve para invitar a **algunos** de esos firmantes, nombrándolos por su `email` o su `phone`: no agrega personas. Si alguno no es firmante del documento, la respuesta es `422 RECIPIENT_NOT_SIGNER` y no se cobra ni se invita a nadie. `Idempotency-Key` es obligatoria: repetir con la misma llave devuelve la respuesta original sin cobrar ni invitar otra vez. Un `send` nuevo sobre un documento que ya está en firma no vuelve a cobrar y solo invita a quien no tiene una invitación vigente (y, en orden secuencial, a quien ya le toca); para insistirle a quien ya la tiene usa Remind signer (uno cada 4 horas). Un documento anulado o expirado responde `409 DOCUMENT_NOT_SENDABLE`. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `recipients` array de objetos puede ser null Firmantes del documento a quienes invitar, por su correo o su teléfono. Cada uno tiene que ser un firmante ya adjunto: si alguno no lo es, la respuesta es 422 `RECIPIENT_NOT_SIGNER` y no se cobra ni se invita a nadie. Si se omite, se usan los firmantes ya adjuntos que siguen pendientes de firma. En un documento en `collecting_data` o `awaiting_signatures`, repetir `send` solo invita a quien no tiene una liga vigente (sin revocar ni vencida); para recordarle a quien ya la tiene usa `remindSigner`. En `correcting`, `send` vuelve a invitar a todos los pendientes. Ver 3 atributos hijos - `recipients[].email` string puede ser null Correo del destinatario. - `recipients[].phone` string puede ser null Teléfono del destinatario (invitación por WhatsApp). - `recipients[].name` string puede ser null Nombre del destinatario. #### Devuelve **200** El Document; su `status` avanza (típicamente a `awaiting_signatures`). — [el objeto Document](#objeto-document) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [402](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) [503](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/send' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/send', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/send", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={}, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_c18e3aaf505d4e8aa839591f6d38f7b9", "object": "document", "name": "Contrato de servicios (sandbox)", "status": "completed", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 1, "ownerId": "usr_d6f2ef7bc72d4754940f12e71ad1964f", "orgId": "cc2dd4db-c027-496a-924f-bcdbb9b51387", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:01.978000Z" } ``` **Respuesta · 409 · DOCUMENT_NOT_SENDABLE** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_SENDABLE", "title": "Conflict", "status": 409, "detail": "The document requires handwritten signatures (signatureValidation.autografa=true) but has no signature fields placed, so it cannot be sent for signing. Place at least one signature field before sending.", "instance": "/v3/documents/doc_82fa050abbc44b9587b71c8f84e8f923/send", "code": "DOCUMENT_NOT_SENDABLE", "requestId": "req_fdb94dfd9e8e41809ba7db813e2097e4", "reason": "The document requires handwritten signatures (signatureValidation.autografa=true) but has no signature fields placed, so it cannot be sent for signing. Place at least one signature field before sending." } ``` **Respuesta · 422 · SIGNING_ORDER_INCOMPLETE** ``` { "type": "https://allsign.io/developers/docs/errors#SIGNING_ORDER_INCOMPLETE", "title": "Unprocessable Entity", "status": 422, "detail": "Every signer is in the same stage, so signing in order would behave exactly like signing all at once. Use signingOrder 'parallel' or spread the signers across different stages.", "instance": "/v3/documents/doc_2d02df9682eb4daa89e1dcf73635d684/send", "code": "SIGNING_ORDER_INCOMPLETE", "requestId": "req_977bab702af14b0790961d843440acd6", "problems": [ "Every signer is in the same stage, so signing in order would behave exactly like signing all at once. Use signingOrder 'parallel' or spread the signers across different stages." ], "reason": "single_stage" } ``` **Respuesta · 422 · RECIPIENT_NOT_SIGNER** ``` { "type": "https://allsign.io/developers/docs/errors#RECIPIENT_NOT_SIGNER", "title": "Unprocessable Entity", "status": 422, "detail": "These recipients are not signers of this document: maria@ejemplo.com. Attach signers with `signers[]` when you create the document.", "instance": "/v3/documents/doc_966231febee54daeb77ff684f988b07b/send", "code": "RECIPIENT_NOT_SIGNER", "requestId": "req_381f6083e0434eebb1fba906d993129d", "errors": [ { "field": "recipients[0].email", "code": "NOT_A_SIGNER", "detail": "maria@ejemplo.com is not a signer of this document." } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos.ejemplos.spec.ts` ## Void document POST `/documents/{document_id}/void` Anula (invalida) un documento. **No es un DELETE**: la retención NOM-151 conserva el registro, por eso anular es una operación explícita que deja el documento en `voided`. Puedes incluir una `reason` opcional. Una anulación legítima puede cancelar cero firmas — el resultado se decide por el `status`, no por cuántas firmas se cancelaron. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `reason` string puede ser null Motivo de la anulación (queda en la bitácora del documento). #### Devuelve **200** El Document; su `status` queda en `voided`. — [el objeto Document](#objeto-document) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/void' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "reason": "Se firmará otra versión con el monto corregido" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/void', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ reason: 'Se firmará otra versión con el monto corregido', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/void", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "reason": "Se firmará otra versión con el monto corregido", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_d0a1551c68d6457294e702fb25d07e4e", "object": "document", "name": "Contrato de arrendamiento — Ana Torres", "status": "voided", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_024a2896938b4a6d81723e882bd556a9", "orgId": "f2cfe411-e1ac-4881-bf0c-a252d08e423a", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T17:59:57.129000Z", "updatedAt": "2026-07-11T18:00:17.507000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/recetas/anula-recuerda-reasigna.receta.spec.ts` ## List signers GET `/documents/{document_id}/signers` Lista los firmantes de un documento. Es una colección acotada (no paginada por cursor). #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada (`object: "list"`, `hasMore` siempre `false`) con los firmantes. Ver los 6 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Arreglo de firmantes del documento. Ver 11 atributos hijos - `data[].livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `data[].id` string requerido ID del firmante (`sgr_…`). - `data[].object` string, siempre "signer" Siempre `"signer"`. - `data[].documentId` string requerido Documento al que pertenece (`doc_…`). - `data[].email` string puede ser null Correo del firmante. - `data[].phone` string puede ser null Teléfono del firmante. - `data[].name` string puede ser null Nombre del firmante. - `data[].status` enum requerido Estado por firmante: `pending`, `sent`, `signed`, `cancelled`, `waiting_turn`. Valores: `pending` `sent` `signed` `cancelled` `waiting_turn` - `data[].signedAt` string (fecha-hora ISO 8601) puede ser null Momento de la firma (ISO 8601), o `null` si aún no firma. - `data[].routingOrder` integer puede ser null Etapa de firma en un documento `sequential`; `null` en paralelo. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. - `data[].delivery` objeto puede ser null Entrega de la última invitación vigente (no revocada). `null` si no hay nada rastreado: invitaciones por correo sin rebote, anteriores al seguimiento, o un firmante recién reasignado. Ver 5 atributos hijos - `data[].delivery.channel` enum requerido Canal de la última invitación: `whatsapp` o `email`. Valores: `whatsapp` `email` - `data[].delivery.status` enum requerido Estado de entrega según el proveedor: `sent`, `delivered`, `read` o `failed`. En correo solo se informa `failed` (rebote). Valores: `sent` `delivered` `read` `failed` - `data[].delivery.reason` enum puede ser null Por qué falló, en un catálogo estable de AllSign: `provider_payment_issue`, `undeliverable`, `bounced` u `other`. `null` si no falló. Valores: `provider_payment_issue` `undeliverable` `bounced` `other` - `data[].delivery.providerCode` integer puede ser null Código de error original del proveedor (p. ej. de WhatsApp), o `null`. - `data[].delivery.updatedAt` string (fecha-hora ISO 8601) requerido Momento del último cambio de estado (ISO 8601). - `hasMore` boolean Siempre `false` en esta colección acotada. default false - `nextCursor` null Siempre `null` — esta colección no pagina. - `previousCursor` null Siempre `null` — esta colección no pagina. - `limit` null Siempre `null` — sin parámetro `limit` en este endpoint. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "sgr_80189c3a228a438cbb36757ed5f4738c", "object": "signer", "documentId": "doc_d0a1551c68d6457294e702fb25d07e4e", "email": "signer-pending@sandbox.allsign.io", "phone": null, "name": "Ana Torres", "status": "sent", "signedAt": null, "routingOrder": null, "delivery": null } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": null } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/recetas/anula-recuerda-reasigna.receta.spec.ts` ## Change a signer's stage PATCH `/documents/{document_id}/signers/{signer_id}` Mueve a un firmante de etapa en un documento `sequential`, sin tocar su identidad ni sus campos: mismo `sgr_…`, mismo contacto, mismos campos colocados — sólo cambia cuándo le toca firmar. La etapa activa es la más baja que todavía tiene firmantes pendientes, y se recalcula en el momento: adelantar a alguien a la etapa activa lo invita de inmediato, y sacar de la etapa activa al último pendiente cierra esa etapa e invita a la siguiente. Por eso la respuesta trae el `status` ya recalculado (`waiting_turn` si quedó detrás de la etapa activa). Se rechaza con **409** en tres casos, cada uno con su `reason`: el firmante ya firmó y su etapa es historia (`already_signed`); el documento firma en paralelo (`document_not_sequential` — `signingOrder` no se puede cambiar después de crear); o ese firmante no tiene un rol propio al que colgarle la etapa y cuenta siempre como etapa 1 (`signer_without_role`, que se resuelve volviendo a crear el documento con todos los firmantes numerados). Disponible sólo si el orden de firma está habilitado en el entorno; si no, sobre un documento en paralelo responde 403 `SEQUENTIAL_NOT_AVAILABLE` en vez del 409 `document_not_sequential`. Para cambiar a la persona detrás del lugar, usa `POST /v3/documents/{documentId}/signers/{signerId}/reassign`. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `signer_id` string ruta requerido ID del firmante (`sgr_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `routingOrder` integer requerido Nueva etapa de firma del firmante (1 = primera). Los firmantes que comparten número firman a la vez; la siguiente etapa se invita sola cuando la anterior termina de firmar, así que mover a alguien puede abrir o cerrar una etapa al instante. Sólo aplica a documentos creados con `signingOrder: "sequential"` (en uno `parallel` la petición se rechaza con **409**, o con 403 `SEQUENTIAL_NOT_AVAILABLE` si el orden de firma no está habilitado en el entorno), y sólo mientras ese firmante no haya firmado (**409** si ya firmó). El número es obligatorio: la v3 no acepta "quitarle" la etapa a un firmante. Dentro del producto un firmante sin número cuenta como etapa 1, pero en esta API el orden siempre va escrito. mín. 1 #### Devuelve **200** El firmante con su etapa nueva y su `status` ya recalculado (`waiting_turn` si quedó detrás de la etapa activa). Ver los 11 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido ID del firmante (`sgr_…`). - `object` string, siempre "signer" Siempre `"signer"`. - `documentId` string requerido Documento al que pertenece (`doc_…`). - `email` string puede ser null Correo del firmante. - `phone` string puede ser null Teléfono del firmante. - `name` string puede ser null Nombre del firmante. - `status` enum requerido Estado por firmante: `pending`, `sent`, `signed`, `cancelled`, `waiting_turn`. Valores: `pending` `sent` `signed` `cancelled` `waiting_turn` - `signedAt` string (fecha-hora ISO 8601) puede ser null Momento de la firma (ISO 8601), o `null` si aún no firma. - `routingOrder` integer puede ser null Etapa de firma en un documento `sequential`; `null` en paralelo. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. - `delivery` objeto puede ser null Entrega de la última invitación vigente (no revocada). `null` si no hay nada rastreado: invitaciones por correo sin rebote, anteriores al seguimiento, o un firmante recién reasignado. Ver 5 atributos hijos - `delivery.channel` enum requerido Canal de la última invitación: `whatsapp` o `email`. Valores: `whatsapp` `email` - `delivery.status` enum requerido Estado de entrega según el proveedor: `sent`, `delivered`, `read` o `failed`. En correo solo se informa `failed` (rebote). Valores: `sent` `delivered` `read` `failed` - `delivery.reason` enum puede ser null Por qué falló, en un catálogo estable de AllSign: `provider_payment_issue`, `undeliverable`, `bounced` u `other`. `null` si no falló. Valores: `provider_payment_issue` `undeliverable` `bounced` `other` - `delivery.providerCode` integer puede ser null Código de error original del proveedor (p. ej. de WhatsApp), o `null`. - `delivery.updatedAt` string (fecha-hora ISO 8601) requerido Momento del último cambio de estado (ISO 8601). Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X PATCH 'https://api.allsign.io/v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/signers/sgr_0dcad5b836254b2791fab1bb6e6995f2' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "routingOrder": 3 }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/signers/sgr_0dcad5b836254b2791fab1bb6e6995f2', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ routingOrder: 3, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.patch( "https://api.allsign.io/v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/signers/sgr_0dcad5b836254b2791fab1bb6e6995f2", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "routingOrder": 3, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "sgr_0dcad5b836254b2791fab1bb6e6995f2", "object": "signer", "documentId": "doc_cb8538f977be47d597896fd6a71f8f17", "email": "ana@ejemplo.com", "phone": null, "name": "Ana Torres", "status": "waiting_turn", "signedAt": null, "routingOrder": 3, "delivery": null } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos.ejemplos.spec.ts` ## List document fields GET `/documents/{document_id}/fields` El estado de cada campo del formulario de un documento: `role` y `signerId` (quién debe llenarlo), `value` (texto o casilla), `readOnly` (prellenado que el firmante no puede cambiar), `status` (`pending` hasta que su firmante firma), y la procedencia del valor: `filledBy` (`owner` si lo puso el emisor por API o desde el panel, `signer` si lo puso el firmante dueño) y `filledAt`. Es la llamada para volcar lo capturado a tu sistema cuando el documento se completa. #### Parámetros - `document_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada con todos los campos del documento y `unassignedCount`. Ver los 6 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "list" Siempre `"list"`. - `documentId` string requerido El documento dueño de los campos (`doc_…`). - `data` array de objetos requerido Campos del documento. Ver 22 atributos hijos - `data[].id` string requerido ID del campo (`fie_…`). - `data[].object` string, siempre "document\_field" Siempre `"document_field"`. - `data[].name` string requerido Nombre del campo (igual al widget del PDF). - `data[].type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `data[].role` string puede ser null Rol dueño, o `null` si nadie lo ha reclamado. - `data[].signerId` string puede ser null Firmante (`sgr_…`) que lo llena, si el rol ya tiene firmante. - `data[].required` boolean Si debe llenarse antes de firmar. default true - `data[].label` string puede ser null Etiqueta que ve el firmante. - `data[].options` array de string puede ser null Opciones de `select` / `radio`. - `data[].group` string puede ser null Grupo de exclusión mutua. - `data[].source` enum puede ser null De dónde nació el campo. Valores: `acroform` `drawn` `template` - `data[].readOnly` boolean Prellenado por el emisor y bloqueado para el firmante. default false - `data[].fixedWidth` boolean Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra (mínimo 6 pt). default false - `data[].placeholder` string puede ser null Texto de ejemplo que ve el firmante en el campo vacío, o `null` si no tiene. - `data[].maxLength` integer puede ser null Cuántos caracteres admite el campo, o `null` si no tiene límite. - `data[].page` integer requerido Página 1-based. mín. 1 - `data[].rect` objeto requerido Rectángulo en % del papel. Ver 4 atributos hijos - `data[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `data[].position` integer Posición del PDF dentro del documento (compuestos). default 1 · mín. 1 - `data[].status` enum requerido `pending` hasta que su firmante firma. Valores: `pending` `signed` - `data[].value` objeto requerido Valor actual del campo. Ver 2 atributos hijos - `data[].value.text` string puede ser null Valor de texto / fecha (`YYYY-MM-DD`) / opción elegida. - `data[].value.checked` boolean puede ser null Estado de una casilla. - `data[].filledBy` enum puede ser null Quién puso el valor: `owner` (el emisor, por API o dashboard) o `signer` (el firmante dueño, ver `signerId`). `null` si nadie lo ha llenado. Valores: `owner` `signer` - `data[].filledAt` string (fecha-hora ISO 8601) puede ser null Cuándo se puso el valor (ISO 8601); `null` si nadie lo ha llenado. - `unassignedCount` integer requerido Cuántos campos no tienen rol todavía. mín. 0 - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "list", "documentId": "doc_6c14cce88de246d4b322520d14f4b07f", "data": [ { "id": "fie_c0d65ee73b7344c1a476f6a5ddbcf075", "object": "document_field", "name": "nombre", "type": "text", "role": "Cliente", "signerId": "sgr_0bc65682c186474ca97666dfdba23652", "required": true, "label": "Nombre", "options": null, "group": null, "source": "template", "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 8.1699, "y": 10.101, "width": 40.8497, "height": 3.0303 }, "position": 1, "status": "pending", "value": { "text": null, "checked": null }, "filledBy": null, "filledAt": null }, { "id": "fie_3ed740f6c7c04e19b014c89aea51fb9d", "object": "document_field", "name": "rfc", "type": "text", "role": "Cliente", "signerId": "sgr_0bc65682c186474ca97666dfdba23652", "required": true, "label": "Rfc", "options": null, "group": null, "source": "template", "readOnly": true, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 8.1699, "y": 15.1515, "width": 40.8497, "height": 3.0303 }, "position": 1, "status": "pending", "value": { "text": "XAXX010101000", "checked": null }, "filledBy": "owner", "filledAt": "2026-07-11T18:00:00.000000Z" }, { "id": "fie_c23f9b44ff2e46ee8201519babdc80ba", "object": "document_field", "name": "fecha", "type": "date", "role": "Cliente", "signerId": "sgr_0bc65682c186474ca97666dfdba23652", "required": true, "label": "Fecha", "options": null, "group": null, "source": "template", "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 8.1699, "y": 20.202, "width": 40.8497, "height": 3.0303 }, "position": 1, "status": "pending", "value": { "text": null, "checked": null }, "filledBy": null, "filledAt": null }, { "id": "fie_911a1cdb1f7e422da4cd55c8ae1e200a", "object": "document_field", "name": "acepta", "type": "checkbox", "role": "Cliente", "signerId": "sgr_0bc65682c186474ca97666dfdba23652", "required": false, "label": "Acepta", "options": null, "group": null, "source": "template", "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 8.1699, "y": 25.2525, "width": 3.5948, "height": 2.7778 }, "position": 1, "status": "pending", "value": { "text": null, "checked": null }, "filledBy": null, "filledAt": null }, { "id": "fie_10706806e45d4618804a9e5979cc904d", "object": "document_field", "name": "firma_cliente", "type": "signature", "role": "Cliente", "signerId": "sgr_0bc65682c186474ca97666dfdba23652", "required": true, "label": null, "options": null, "group": null, "source": "template", "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 8.1699, "y": 39.1414, "width": 40.8497, "height": 7.5758 }, "position": 1, "status": "pending", "value": { "text": null, "checked": null }, "filledBy": null, "filledAt": null } ], "unassignedCount": 0, "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## Create document field POST `/documents/{document_id}/fields` Agrega un campo a un documento que todavía no se envía. Igual que en plantillas: `type`, `page`, `rect` (en % del papel), `role` (se crea si no existe), `required`, `label`, `options`, `group`. Puedes mandar tu propio `id` (`fie_…`) para crearlo de forma idempotente, o `aliasOf` para que comparta valor con otro campo del mismo nombre. #### Parámetros - `document_id` string ruta requerido - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `id` string puede ser null ID (`fie_…`) elegido por el cliente para crear de forma idempotente; si falta, lo asigna AllSign. - `type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `page` integer requerido Página 1-based dentro del PDF elegido. mín. 1 - `rect` objeto requerido Rectángulo en % del papel. Ver 4 atributos hijos - `rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `documentPdfId` string puede ser null El PDF del documento que recibe el campo (compuestos). Forma preferida: es estable aunque el documento se reordene. Requerido si el documento tiene más de un PDF y no mandas `position`. - `position` integer puede ser null Alias heredado de `documentPdfId`: posición 1-based del PDF dentro del documento. Frágil — cambia si el documento se reordena. Usa `documentPdfId` cuando puedas. mín. 1 - `name` string puede ser null Nombre del campo; se genera si falta. máx. 120 caracteres - `aliasOf` string puede ser null Nombre de un campo existente: este hueco comparte su valor. máx. 120 caracteres - `role` string puede ser null Rol dueño (se crea si no existe). máx. 120 caracteres - `signer` string puede ser null Firmante dueño (`sgr_…`). El campo hereda el rol de ese firmante; no lo combines con `role`. - `required` boolean Si es obligatorio. default true - `label` string puede ser null Etiqueta para el firmante. máx. 200 caracteres - `options` array de string puede ser null Opciones de `select` / `radio`. - `group` string puede ser null Grupo de exclusión mutua. máx. 120 caracteres - `fixedWidth` boolean puede ser null Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra (mínimo 6 pt). - `placeholder` string puede ser null Texto de ejemplo que ve el firmante en el campo vacío. Solo campos de texto; en fecha, casilla y firma se ignora. máx. 200 caracteres - `maxLength` integer puede ser null Cuántos caracteres admite el campo (1 a 500). Solo campos de texto; en fecha, casilla y firma se ignora. entre 1 y 500 #### Devuelve **201** El campo creado. Ver los 22 atributos de la respuesta - `id` string requerido ID del campo (`fie_…`). - `object` string, siempre "document\_field" Siempre `"document_field"`. - `name` string requerido Nombre del campo (igual al widget del PDF). - `type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `role` string puede ser null Rol dueño, o `null` si nadie lo ha reclamado. - `signerId` string puede ser null Firmante (`sgr_…`) que lo llena, si el rol ya tiene firmante. - `required` boolean Si debe llenarse antes de firmar. default true - `label` string puede ser null Etiqueta que ve el firmante. - `options` array de string puede ser null Opciones de `select` / `radio`. - `group` string puede ser null Grupo de exclusión mutua. - `source` enum puede ser null De dónde nació el campo. Valores: `acroform` `drawn` `template` - `readOnly` boolean Prellenado por el emisor y bloqueado para el firmante. default false - `fixedWidth` boolean Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra (mínimo 6 pt). default false - `placeholder` string puede ser null Texto de ejemplo que ve el firmante en el campo vacío, o `null` si no tiene. - `maxLength` integer puede ser null Cuántos caracteres admite el campo, o `null` si no tiene límite. - `page` integer requerido Página 1-based. mín. 1 - `rect` objeto requerido Rectángulo en % del papel. Ver 4 atributos hijos - `rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `position` integer Posición del PDF dentro del documento (compuestos). default 1 · mín. 1 - `status` enum requerido `pending` hasta que su firmante firma. Valores: `pending` `signed` - `value` objeto requerido Valor actual del campo. Ver 2 atributos hijos - `value.text` string puede ser null Valor de texto / fecha (`YYYY-MM-DD`) / opción elegida. - `value.checked` boolean puede ser null Estado de una casilla. - `filledBy` enum puede ser null Quién puso el valor: `owner` (el emisor, por API o dashboard) o `signer` (el firmante dueño, ver `signerId`). `null` si nadie lo ha llenado. Valores: `owner` `signer` - `filledAt` string (fecha-hora ISO 8601) puede ser null Cuándo se puso el valor (ISO 8601); `null` si nadie lo ha llenado. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "telefono", "type": "text", "page": 1, "rect": { "x": 10, "y": 80, "width": 30, "height": 4 }, "role": "Cliente", "required": false }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'telefono', type: 'text', page: 1, rect: { x: 10, y: 80, width: 30, height: 4, }, role: 'Cliente', required: false, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "telefono", "type": "text", "page": 1, "rect": { "x": 10, "y": 80, "width": 30, "height": 4, }, "role": "Cliente", "required": False, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "id": "fie_5670d7b1d4b343209afb35164c4de9ce", "object": "document_field", "name": "telefono", "type": "text", "role": "Cliente", "signerId": "sgr_0bc65682c186474ca97666dfdba23652", "required": false, "label": null, "options": null, "group": null, "source": "drawn", "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 10, "y": 80, "width": 30, "height": 4 }, "position": 1, "status": "pending", "value": { "text": null, "checked": null }, "filledBy": null, "filledAt": null } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## Update document field PATCH `/documents/{document_id}/fields/{field_id}` Mueve, renombra, reasigna o configura un campo — y **prellena**: `value` (texto, fecha `YYYY-MM-DD` o `true`/`false` para casillas) deja el valor puesto por el emisor (`filledBy: "owner"`); `readOnly: true` lo bloquea para que el firmante lo vea pero no lo cambie. Un campo ya firmado no se puede tocar: responde 409 `FIELD_CONFLICT`. #### Parámetros - `document_id` string ruta requerido - `field_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string puede ser null Nuevo nombre (renombra sus huecos hermanos). de 1 a 120 caracteres - `page` integer puede ser null Nueva página 1-based. mín. 1 - `rect` objeto puede ser null Nuevo rectángulo. Ver 4 atributos hijos - `rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `role` string puede ser null Nuevo rol (`null` lo deja sin rol). máx. 120 caracteres - `signer` string puede ser null Nuevo firmante dueño (`sgr_…`); el campo hereda su rol. `null` lo deja sin dueño. No lo combines con `role`. - `required` boolean puede ser null Si es obligatorio. - `label` string puede ser null Etiqueta para el firmante. máx. 200 caracteres - `options` array de string puede ser null Opciones. - `group` string puede ser null Grupo. máx. 120 caracteres - `value` string o boolean puede ser null Prellenar (texto o casilla). - `readOnly` boolean puede ser null Bloquear el valor prellenado para el firmante. - `fixedWidth` boolean puede ser null Ancho fijo (`true`) o que el campo se alargue con el texto (`false`). - `placeholder` string puede ser null Texto de ejemplo, solo en campos de texto; se comparte con los huecos del mismo nombre. Vacío o `null` lo quita. máx. 200 caracteres - `maxLength` integer puede ser null Cuántos caracteres admite (1 a 500), solo en campos de texto; se comparte con los huecos del mismo nombre. `null` quita el límite. Un valor ya guardado más largo no se recorta. entre 1 y 500 #### Devuelve **200** El campo ya actualizado, con `value`, `filledBy` y `filledAt`. Ver los 22 atributos de la respuesta - `id` string requerido ID del campo (`fie_…`). - `object` string, siempre "document\_field" Siempre `"document_field"`. - `name` string requerido Nombre del campo (igual al widget del PDF). - `type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `role` string puede ser null Rol dueño, o `null` si nadie lo ha reclamado. - `signerId` string puede ser null Firmante (`sgr_…`) que lo llena, si el rol ya tiene firmante. - `required` boolean Si debe llenarse antes de firmar. default true - `label` string puede ser null Etiqueta que ve el firmante. - `options` array de string puede ser null Opciones de `select` / `radio`. - `group` string puede ser null Grupo de exclusión mutua. - `source` enum puede ser null De dónde nació el campo. Valores: `acroform` `drawn` `template` - `readOnly` boolean Prellenado por el emisor y bloqueado para el firmante. default false - `fixedWidth` boolean Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra (mínimo 6 pt). default false - `placeholder` string puede ser null Texto de ejemplo que ve el firmante en el campo vacío, o `null` si no tiene. - `maxLength` integer puede ser null Cuántos caracteres admite el campo, o `null` si no tiene límite. - `page` integer requerido Página 1-based. mín. 1 - `rect` objeto requerido Rectángulo en % del papel. Ver 4 atributos hijos - `rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `position` integer Posición del PDF dentro del documento (compuestos). default 1 · mín. 1 - `status` enum requerido `pending` hasta que su firmante firma. Valores: `pending` `signed` - `value` objeto requerido Valor actual del campo. Ver 2 atributos hijos - `value.text` string puede ser null Valor de texto / fecha (`YYYY-MM-DD`) / opción elegida. - `value.checked` boolean puede ser null Estado de una casilla. - `filledBy` enum puede ser null Quién puso el valor: `owner` (el emisor, por API o dashboard) o `signer` (el firmante dueño, ver `signerId`). `null` si nadie lo ha llenado. Valores: `owner` `signer` - `filledAt` string (fecha-hora ISO 8601) puede ser null Cuándo se puso el valor (ISO 8601); `null` si nadie lo ha llenado. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X PATCH 'https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields/fie_5670d7b1d4b343209afb35164c4de9ce' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "value": "5555550101", "readOnly": true }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields/fie_5670d7b1d4b343209afb35164c4de9ce', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ value: '5555550101', readOnly: true, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.patch( "https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields/fie_5670d7b1d4b343209afb35164c4de9ce", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "value": "5555550101", "readOnly": True, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "id": "fie_5670d7b1d4b343209afb35164c4de9ce", "object": "document_field", "name": "telefono", "type": "text", "role": "Cliente", "signerId": "sgr_0bc65682c186474ca97666dfdba23652", "required": false, "label": null, "options": null, "group": null, "source": "drawn", "readOnly": true, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 10, "y": 80, "width": 30, "height": 4 }, "position": 1, "status": "pending", "value": { "text": "5555550101", "checked": null }, "filledBy": "owner", "filledAt": "2026-07-11T18:00:01.615000Z" } ``` **Respuesta · 409 · DOCUMENT_CONFLICT** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_CONFLICT", "title": "Conflict", "status": 409, "detail": "Este documento ya terminó (ANULADO): sus campos ya no se pueden crear, mover, reasignar ni borrar.", "instance": "/v3/documents/doc_6b7fc763d2bc430980ae397679f7a465/fields/fie_fb20c7c439fc4902b6b6bbc9bbca55cc", "code": "DOCUMENT_CONFLICT", "requestId": "req_55b09c55654746338f3f811e0ced82a7", "reason": "document_terminal" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## Delete document field DELETE `/documents/{document_id}/fields/{field_id}` Quita un campo del documento. Un campo ya firmado no se puede borrar (409 `FIELD_CONFLICT`). #### Parámetros - `document_id` string ruta requerido - `field_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **204** Campo eliminado. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X DELETE 'https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields/fie_5670d7b1d4b343209afb35164c4de9ce' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields/fie_5670d7b1d4b343209afb35164c4de9ce', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status); ``` **Python** ``` import os import requests respuesta = requests.delete( "https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields/fie_5670d7b1d4b343209afb35164c4de9ce", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code) ``` **Respuesta · 204** *Sin cuerpo.* Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## Assign fields to a role POST `/documents/{document_id}/fields:assign-role` Asigna varios campos a un rol de una sola vez (o `role: null` para dejarlos sin rol). Si el rol ya tiene firmante, los campos pasan a ese firmante en el mismo paso. Un campo ya firmado responde 409. #### Parámetros - `document_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `fieldIds` array de string requerido Campos (`fie_…`) a reasignar. mín. 1 elementos - `role` string puede ser null Rol destino; `null` los deja sin rol. máx. 120 caracteres - `signer` string puede ser null Firmante destino (`sgr_…`); los campos heredan su rol. Manda `role` o `signer`, no ambos. #### Devuelve **200** Cuántos campos cambiaron y cómo quedaron. Ver los 5 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "document\_fields\_assignment" - `documentId` string requerido El documento (`doc_…`). - `updated` integer requerido Cuántos campos cambiaron de rol. mín. 0 - `data` array de objetos requerido Los campos ya reasignados. Ver 22 atributos hijos - `data[].id` string requerido ID del campo (`fie_…`). - `data[].object` string, siempre "document\_field" Siempre `"document_field"`. - `data[].name` string requerido Nombre del campo (igual al widget del PDF). - `data[].type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `data[].role` string puede ser null Rol dueño, o `null` si nadie lo ha reclamado. - `data[].signerId` string puede ser null Firmante (`sgr_…`) que lo llena, si el rol ya tiene firmante. - `data[].required` boolean Si debe llenarse antes de firmar. default true - `data[].label` string puede ser null Etiqueta que ve el firmante. - `data[].options` array de string puede ser null Opciones de `select` / `radio`. - `data[].group` string puede ser null Grupo de exclusión mutua. - `data[].source` enum puede ser null De dónde nació el campo. Valores: `acroform` `drawn` `template` - `data[].readOnly` boolean Prellenado por el emisor y bloqueado para el firmante. default false - `data[].fixedWidth` boolean Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra (mínimo 6 pt). default false - `data[].placeholder` string puede ser null Texto de ejemplo que ve el firmante en el campo vacío, o `null` si no tiene. - `data[].maxLength` integer puede ser null Cuántos caracteres admite el campo, o `null` si no tiene límite. - `data[].page` integer requerido Página 1-based. mín. 1 - `data[].rect` objeto requerido Rectángulo en % del papel. Ver 4 atributos hijos - `data[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `data[].position` integer Posición del PDF dentro del documento (compuestos). default 1 · mín. 1 - `data[].status` enum requerido `pending` hasta que su firmante firma. Valores: `pending` `signed` - `data[].value` objeto requerido Valor actual del campo. Ver 2 atributos hijos - `data[].value.text` string puede ser null Valor de texto / fecha (`YYYY-MM-DD`) / opción elegida. - `data[].value.checked` boolean puede ser null Estado de una casilla. - `data[].filledBy` enum puede ser null Quién puso el valor: `owner` (el emisor, por API o dashboard) o `signer` (el firmante dueño, ver `signerId`). `null` si nadie lo ha llenado. Valores: `owner` `signer` - `data[].filledAt` string (fecha-hora ISO 8601) puede ser null Cuándo se puso el valor (ISO 8601); `null` si nadie lo ha llenado. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields:assign-role' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "fieldIds": [ "fie_5670d7b1d4b343209afb35164c4de9ce" ], "role": null }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields:assign-role', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ fieldIds: [ 'fie_5670d7b1d4b343209afb35164c4de9ce', ], role: null, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_6c14cce88de246d4b322520d14f4b07f/fields:assign-role", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "fieldIds": [ "fie_5670d7b1d4b343209afb35164c4de9ce", ], "role": None, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "document_fields_assignment", "documentId": "doc_6c14cce88de246d4b322520d14f4b07f", "updated": 1, "data": [ { "id": "fie_5670d7b1d4b343209afb35164c4de9ce", "object": "document_field", "name": "telefono", "type": "text", "role": null, "signerId": null, "required": false, "label": null, "options": null, "group": null, "source": "drawn", "readOnly": true, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 10, "y": 80, "width": 30, "height": 4 }, "position": 1, "status": "pending", "value": { "text": "5555550101", "checked": null }, "filledBy": "owner", "filledAt": "2026-07-11T18:00:01.615000Z" } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## List document roles GET `/documents/{document_id}/roles` Los roles del documento, en orden, con cuántos campos le pertenecen a cada uno (`fieldCount`) y quién lo ocupa (`signerId`, o `null` si nadie lo ha tomado). En un documento `sequential` cada rol trae su etapa (`stage`); en paralelo, `null`. El `id` (`rol_…`) es estable aunque renombres el rol, y es el que usa `DELETE /documents/{id}/roles/{roleId}`. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Los roles del documento. Ver los 5 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "list" Siempre `"list"`. - `documentId` string requerido El documento dueño de los roles (`doc_…`). - `data` array de objetos requerido Roles del documento, en orden. Ver 7 atributos hijos - `data[].object` string, siempre "document\_role" Siempre `"document_role"`. - `data[].id` string requerido Id del rol (`rol_…`), estable aunque lo renombres. - `data[].name` string requerido Nombre del rol, tal como lo traen los campos en su `role`. - `data[].kind` string Papel del participante (token opaco): `signer`, `witness`, `notary`, `carbon_copy`, `approver` o `in_person_signer`. default "signer" - `data[].stage` integer puede ser null Etapa de firma (1 = primera). `null` en orden paralelo. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. mín. 1 - `data[].fieldCount` integer requerido Cuántos campos del documento le pertenecen. mín. 0 - `data[].signerId` string puede ser null Firmante que lo ocupa (`sgr_…`), o `null` si nadie lo ha tomado. - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/roles' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/roles', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/roles", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "list", "documentId": "doc_cb8538f977be47d597896fd6a71f8f17", "data": [ { "object": "document_role", "id": "rol_187ecb9317994c59ac5b7c4b53483bd5", "name": "Cliente", "kind": "signer", "stage": 1, "fieldCount": 1, "signerId": "sgr_f2f4eea4da414a38a7aeec6c22ba4bf1" }, { "object": "document_role", "id": "rol_ba9ece92ec6045afab273dd78ee02d76", "name": "Director", "kind": "signer", "stage": 2, "fieldCount": 1, "signerId": "sgr_0dcad5b836254b2791fab1bb6e6995f2" } ], "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos.ejemplos.spec.ts` ## Delete a document role DELETE `/documents/{document_id}/roles/{role_id}` Borra un rol que **nadie ocupa**. Sus campos y variables no se borran: quedan sin rol, y la respuesta los lista en `unassignedFields` y `unassignedVariables` para que los asignes antes de enviar a firmar. Si el rol ya tiene firmante responde 409 `ROLE_OCCUPIED`: quita primero al firmante y luego el rol. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `role_id` string ruta requerido ID del rol (`rol_…`), tal como lo devuelve `GET /documents/{id}/roles`. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El rol borrado, con los campos y variables que quedaron sin rol. Ver los 7 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "document\_role" Siempre `"document_role"`. - `id` string requerido Id del rol borrado (`rol_…`). - `name` string requerido Nombre que tenía el rol. - `deleted` string, siempre "true" Siempre `true`. - `unassignedFields` integer requerido Campos que quedaron sin rol; asígnalos antes de enviar a firmar. mín. 0 - `unassignedVariables` integer requerido Variables que quedaron sin rol. mín. 0 Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X DELETE 'https://api.allsign.io/v3/documents/doc_2abcccc1b53249348a14e6a17c3ebe28/roles/rol_518f1d58084948bfbff538c43b72e996' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_2abcccc1b53249348a14e6a17c3ebe28/roles/rol_518f1d58084948bfbff538c43b72e996', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.delete( "https://api.allsign.io/v3/documents/doc_2abcccc1b53249348a14e6a17c3ebe28/roles/rol_518f1d58084948bfbff538c43b72e996", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "document_role", "id": "rol_518f1d58084948bfbff538c43b72e996", "name": "Cliente", "deleted": true, "unassignedFields": 1, "unassignedVariables": 0 } ``` **Respuesta · 404 · ROLE_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#ROLE_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Role not found in this document.", "instance": "/v3/documents/doc_d60c6183f829411b8d033f2fc4c9477d/roles/rol_1d2abe482f0045a8b0fc16c94d49005f", "code": "ROLE_NOT_FOUND", "requestId": "req_6724d101e4b248a7bd87d2f7ffd6e6ac" } ``` **Respuesta · 409 · ROLE_OCCUPIED** ``` { "type": "https://allsign.io/developers/docs/errors#ROLE_OCCUPIED", "title": "Conflict", "status": 409, "detail": "Role 'Arrendador' is held by a signer. Remove the signer first, then delete the role.", "instance": "/v3/documents/doc_d60c6183f829411b8d033f2fc4c9477d/roles/rol_00823ecb9d294d13869402400226758e", "code": "ROLE_OCCUPIED", "requestId": "req_8704c255f6cf49999ae7d48dfa1ace21" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:14 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/errores.ejemplos.spec.ts` ## Import signing fields from a document POST `/documents/{document_id}/fields:from-document` Copia las cajas de firma (posiciones por rol) de otro documento ya firmado a este documento. Sirve para repetir un layout sin plantilla de por medio. Solo viaja la geometría, convertida a la hoja de este documento; ningún valor del documento origen. #### Parámetros - `document_id` string ruta requerido - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `documentId` string puede ser null Documento (`doc_…`) del que se copian los campos de firma. - `consolidate` boolean Si es `true`, consolida campos de documentos comparables al documento destino (mismo origen/plantilla, mismo archivo o mismo hash) en vez de copiar uno solo. default false #### Devuelve **200** Los campos del documento después de la copia. Ver los 7 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "document\_signing\_fields\_import" Siempre `"document_signing_fields_import"`. - `documentId` string requerido El documento destino (`doc_…`). - `matchedCount` integer requerido Campos que encajaron al calcular la disposición. mín. 0 - `totalCount` integer requerido Campos considerados. mín. 0 - `unmatched` array de objetos Campos que no encajaron, con su rol/tipo/página. Ver 3 atributos hijos - `unmatched[].roleName` string requerido Rol del campo que no encajó. - `unmatched[].kind` string requerido Tipo del campo que no encajó. - `unmatched[].page` integer requerido Página 1-based del campo que no encajó. mín. 1 - `fields` array de objetos Campos de firma creados en el documento destino (nuevos; los ya existentes no se repiten). Ver 5 atributos hijos - `fields[].id` string requerido ID del campo de firma creado (`fie_…`). - `fields[].roleName` string requerido Rol al que pertenece el campo. - `fields[].kind` string requerido Tipo de campo: `signature`, `initials`, `name`, `date` o `vobo`. - `fields[].page` integer requerido Página 1-based. mín. 1 - `fields[].rect` objeto requerido Rectángulo del campo, en % del papel. Ver 4 atributos hijos - `fields[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `fields[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `fields[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `fields[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) [503](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_f4334b6d04314ba99d7c3e974dfd273f/fields:from-document' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "documentId": "doc_b723b46d90a64d309c8a5a0bb747a9a9" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_f4334b6d04314ba99d7c3e974dfd273f/fields:from-document', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ documentId: 'doc_b723b46d90a64d309c8a5a0bb747a9a9', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_f4334b6d04314ba99d7c3e974dfd273f/fields:from-document", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "documentId": "doc_b723b46d90a64d309c8a5a0bb747a9a9", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "document_signing_fields_import", "documentId": "doc_f4334b6d04314ba99d7c3e974dfd273f", "matchedCount": 1, "totalCount": 1, "unmatched": [], "fields": [ { "id": "fie_2e744958f0da493c9d63927e1a9c1fae", "roleName": "Cliente", "kind": "signature", "page": 1, "rect": { "x": 13.0719, "y": 78.2828, "width": 32.6797, "height": 12.6263 } } ] } ``` **Respuesta · 409 · DOCUMENT_NOT_EDITABLE** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_EDITABLE", "title": "Conflict", "status": 409, "detail": "This document is already in signing; its fields can no longer be imported.", "instance": "/v3/documents/doc_19deea649a6e421585db7e09bdc1faa4/fields:from-document", "code": "DOCUMENT_NOT_EDITABLE", "requestId": "req_1cd7885fff374f8bb522d76be322ad0e" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos.ejemplos.spec.ts` ## Download document file GET `/documents/{document_id}/file` El PDF del documento **antes de enviarlo**: con `format=form` conserva los widgets vivos con lo prellenado (para que el emisor lo revise en Acrobat); con `format=flat` lo hornea con los valores actuales. Después de la firma, el PDF con validez legal es la evidencia (`GET /documents/{id}/evidence`). #### Parámetros - `document_id` string ruta requerido - `format` enum query `form` (default) conserva los widgets; `flat` los hornea con lo prellenado. Valores: `form` `flat` default "form" - `position` integer query Posición del PDF dentro de un expediente compuesto (default 1). default 1 · mín. 1 - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El PDF (`application/pdf`, `Content-Disposition: attachment`). Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/file' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` import { writeFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/file', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); writeFileSync('descarga.pdf', Buffer.from(await respuesta.arrayBuffer())); ``` **Python** ``` import os from pathlib import Path import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/file", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) Path("descarga.pdf").write_bytes(respuesta.content) ``` **Respuesta · 200** ``` ``` **Respuesta · 404 · DOCUMENT_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND", "title": "Document not found", "status": 404, "detail": "Document not found", "instance": "/v3/documents/doc_9c9f2f6749794c8c8717b0fd5458f074/file", "code": "DOCUMENT_NOT_FOUND", "requestId": "req_9099fea09e7e4822a33e00dde0d7dc14" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/descarga-el-pdf-firmado-y-la-evidencia.receta.spec.ts` ## Get document values GET `/documents/{document_id}/values` Los valores de las **variables** `{{ }}` de un documento creado desde una plantilla DOCX: los `templateValues` con los que se creó y lo que llenaste después con `PATCH …/values`. Todas las variables las llena el remitente. Para los campos del formulario PDF, que sí llena el firmante, usa `GET …/fields`. #### Parámetros - `document_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Mapa `nombre → valor` con quién lo llenó. Ver los 4 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "document\_values" Siempre `"document_values"`. - `documentId` string requerido El documento (`doc_…`). - `data` array de objetos Valores actuales del documento. Ver 2 atributos hijos - `data[].name` string requerido Nombre de la variable — llave natural de `templateValues`, nunca camelizada. - `data[].value` string puede ser null Valor actual, o `null` si aún no se llena. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_1ce40ec34cf7454e83862318509bef29/values' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_1ce40ec34cf7454e83862318509bef29/values', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_1ce40ec34cf7454e83862318509bef29/values", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "document_values", "documentId": "doc_1ce40ec34cf7454e83862318509bef29", "data": [ { "name": "comprador_nombre", "value": null }, { "name": "comprador_rfc", "value": null }, { "name": "fecha", "value": null }, { "name": "monto", "value": null }, { "name": "vendedor_nombre", "value": "Ana Martínez" } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Update document values PATCH `/documents/{document_id}/values` Escribe valores de variables `{{ }}` en un documento que todavía no se envía (`draft`) y regenera su PDF. Todas las variables las llena el remitente; los valores propuestos al firmante (`proposals`) se retiraron. Una vez enviado, los valores quedan fijos: responde `409 DOCUMENT_CONFLICT` y no cambia nada — excepto en `collecting_data` (variables obligatorias ligadas a un rol de un documento anterior a este cambio, que los firmantes todavía están llenando): ahí solo acepta variables de un firmante (las del dueño responden `409` con `lockedVariables`), y cuando ya están todas el documento pasa a firma. Solo variables; los campos del formulario PDF se prellenan con `PATCH …/fields/{id}` o con `values` al crear. #### Parámetros - `document_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `values` objeto (llave → valor) Mapa nombre→valor a fusionar. Las llaves son los `name` de las variables y conservan su forma natural (`nombre_completo`): nunca se camelizan. Un valor de un dato con `slots` se escribe en todos los huecos. #### Devuelve **200** Los valores ya mezclados. Ver los 4 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "document\_values" Siempre `"document_values"`. - `documentId` string requerido El documento (`doc_…`). - `data` array de objetos Valores actuales del documento. Ver 2 atributos hijos - `data[].name` string requerido Nombre de la variable — llave natural de `templateValues`, nunca camelizada. - `data[].value` string puede ser null Valor actual, o `null` si aún no se llena. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X PATCH 'https://api.allsign.io/v3/documents/doc_1ce40ec34cf7454e83862318509bef29/values' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "values": { "comprador_nombre": "Luis Pérez", "comprador_rfc": "XAXX010101000" } }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_1ce40ec34cf7454e83862318509bef29/values', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ values: { comprador_nombre: 'Luis Pérez', comprador_rfc: 'XAXX010101000', }, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.patch( "https://api.allsign.io/v3/documents/doc_1ce40ec34cf7454e83862318509bef29/values", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "values": { "comprador_nombre": "Luis Pérez", "comprador_rfc": "XAXX010101000", }, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "document_values", "documentId": "doc_1ce40ec34cf7454e83862318509bef29", "data": [ { "name": "comprador_nombre", "value": "Luis Pérez" }, { "name": "comprador_rfc", "value": "XAXX010101000" }, { "name": "fecha", "value": null }, { "name": "monto", "value": null }, { "name": "vendedor_nombre", "value": "Ana Martínez" } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Save the document as a template version POST `/documents/{document_id}/save-to-template` Guarda el documento tal como está —campos, roles, variables, orden de firma y ajustes— como una versión nueva de la plantilla. Con `templateId` se agrega una versión a esa plantilla (`created: false`); sin él se crea una plantilla nueva a partir del documento (`created: true`). Las personas se guardan **sólo** con `rememberSigners: true`: sin eso el layout conserva los roles («Parte 1», «Parte 2»…) pero no quién los ocupó. Los valores que llenó el DUEÑO se guardan **sólo** con `rememberFieldValues: true`; lo que llenó un firmante nunca se guarda, tenga este flag el valor que tenga. Un documento con al menos una firma responde 409 `DOCUMENT_ALREADY_SIGNED`. `formView` (`fill` o `edit`) es la vista con la que abrirá el formulario de los documentos que nazcan de la versión, y queda en `layout.settings.formView`. Si se omite, la versión hereda la de la última versión con layout de la plantilla. El documento queda ligado a la plantilla y a la versión guardada, así que `GET /documents/{id}` ya responde con `templateId` y `templateVersionId`. #### Parámetros - `document_id` string ruta requerido ID del documento a guardar (`doc_…`). - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `templateId` string puede ser null Plantilla a la que se guarda una versión nueva (`tmpl_…`). Si se omite, se crea una plantilla nueva a partir del documento. - `name` string puede ser null Nombre de la plantilla nueva. Si se omite, se toma el del documento. - `rememberSigners` boolean Si es `true`, el layout guarda a las personas que hoy tienen cada rol para que el siguiente documento nazca con ellas. default false - `rememberFieldValues` boolean Si es `true`, la plantilla guarda los valores que el DUEÑO llenó (campos y variables) como default para el siguiente documento. Nunca guarda lo que llenó un firmante, tenga este flag el valor que tenga. default false - `formView` enum puede ser null Vista del formulario que guarda la versión en `layout.settings.formView`: `fill` (Llenar) o `edit` (Editar campos). Si se omite o es `null`, la versión hereda la de la última versión con layout de la plantilla; si no hay de dónde heredar, queda `null` y el formulario abre en `edit`. Valores: `fill` `edit` #### Devuelve **201** La plantilla y la versión guardada, con `created` para saber si la plantilla es nueva. Ver los 5 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "template\_snapshot" Siempre `"template_snapshot"`. - `template` objeto requerido La plantilla destino. Ver 2 atributos hijos - `template.id` string requerido La plantilla (`tmpl_…`). - `template.name` string requerido Nombre de la plantilla. - `version` objeto requerido La versión que se acaba de guardar. Ver 2 atributos hijos - `version.id` string requerido La versión guardada (`tv_…`). - `version.versionNumber` integer requerido Número de versión guardada. mín. 1 - `created` boolean requerido `true` si la plantilla se creó en esta llamada; `false` si ya existía. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_b723b46d90a64d309c8a5a0bb747a9a9/save-to-template' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "v3audit-ejemplos plantilla guardada" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_b723b46d90a64d309c8a5a0bb747a9a9/save-to-template', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'v3audit-ejemplos plantilla guardada', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_b723b46d90a64d309c8a5a0bb747a9a9/save-to-template", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "v3audit-ejemplos plantilla guardada", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "object": "template_snapshot", "template": { "id": "tmpl_2822486b40e747a5b8110d665fb4454e", "name": "v3audit-ejemplos plantilla guardada" }, "version": { "id": "tv_2e7ac3c7970443b6ab43e446ca7527e4", "versionNumber": 1 }, "created": true } ``` **Respuesta · 409 · DOCUMENT_ALREADY_SIGNED** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_ALREADY_SIGNED", "title": "Document already signed", "status": 409, "detail": "A document with signatures already on it cannot be saved as a template.", "instance": "/v3/documents/doc_f225e42012ba44dd853a06e460b02674/save-to-template", "code": "DOCUMENT_ALREADY_SIGNED", "requestId": "req_6d22adad6f034dcb857a752eed5af7a9" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos-firmados.ejemplos.spec.ts` ## List events GET `/documents/{document_id}/events` Lista la bitácora de eventos de un documento (creación, envío, firmas, etc.), paginada por cursor. `type` es un token del catálogo de eventos con namespace punteado `recurso.enPasado` (ej. `document.created`). #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `limit` integer query Resultados por página (1–100, default `20`). default 20 · entre 1 y 100 - `startingAfter` string query Cursor: eventos después de este `id` (`evt_…`). - `endingBefore` string query Cursor: eventos antes de este `id` (`evt_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Sobre de paginación por cursor con la bitácora de eventos del documento. Ver los 6 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Arreglo de eventos del documento, más recientes primero. Ver 11 atributos hijos - `data[].livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `data[].id` string requerido ID del evento (`evt_…`). - `data[].object` string, siempre "event" Siempre `"event"`. - `data[].type` string requerido Tipo de evento (ej. `document.created`, `signer.signed`). - `data[].documentId` string requerido Documento asociado (`doc_…`). - `data[].success` boolean requerido Si el evento fue exitoso. - `data[].message` string puede ser null Mensaje legible del evento. - `data[].actorType` string puede ser null Tipo de actor que originó el evento. - `data[].signatureId` string puede ser null Firma asociada (`sgr_…`), si aplica. - `data[].data` objeto (llave → valor) Payload del evento. Solo las llaves de primer nivel se camelizan; los datos anidados quedan intactos. - `data[].createdAt` string (fecha-hora ISO 8601) requerido Momento del evento (ISO 8601). - `hasMore` boolean requerido `true` si hay más eventos después de esta página. - `nextCursor` string puede ser null Cursor para la siguiente página (pásalo como `startingAfter`). - `previousCursor` string puede ser null Cursor para la página anterior (pásalo como `endingBefore`). - `limit` integer requerido El límite aplicado a esta página. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_e15d0f67a79b4ea788fb7bcbd1a09607/events' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_e15d0f67a79b4ea788fb7bcbd1a09607/events', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_e15d0f67a79b4ea788fb7bcbd1a09607/events", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "evt_a7820888aca541eba86deff6a273b819", "object": "event", "type": "signature.requested", "documentId": "doc_e15d0f67a79b4ea788fb7bcbd1a09607", "success": true, "message": "Invitación enviada a sig***@s***.io", "actorType": "user", "signatureId": null, "data": { "channel": "email", "isReminder": false, "phoneNumber": null, "signerEmail": "signer-success@sandbox.allsign.io" }, "createdAt": "2026-07-11T18:00:03.608000Z" }, { "livemode": false, "id": "evt_c7b0605e3d124e6f95eb01a305af1108", "object": "event", "type": "signature.consent_given", "documentId": "doc_e15d0f67a79b4ea788fb7bcbd1a09607", "success": true, "message": "sig***@s***.io consintió firmar electrónicamente", "actorType": "user", "signatureId": "sgr_b256e83390914aba9d8d6ef0c2f3560a", "data": { "channel": "autografa", "userAgent": "allsign-sandbox-autosign", "fieldsUpdated": 0, "signaturesAdded": 0 }, "createdAt": "2026-07-11T17:59:59.966000Z" }, { "livemode": false, "id": "evt_8a915752dcc04f46b43306adbdb5193f", "object": "event", "type": "signature.signed", "documentId": "doc_e15d0f67a79b4ea788fb7bcbd1a09607", "success": true, "message": "Documento firmado por sig***@s***.io", "actorType": "user", "signatureId": "sgr_b256e83390914aba9d8d6ef0c2f3560a", "data": { "channel": "autografa", "userAgent": "allsign-sandbox-autosign", "fieldsUpdated": 0, "signaturesAdded": 0 }, "createdAt": "2026-07-11T17:59:59.966000Z" }, { "livemode": false, "id": "evt_2022c6518f414336a06b576876d7611a", "object": "event", "type": "document.created", "documentId": "doc_e15d0f67a79b4ea788fb7bcbd1a09607", "success": true, "message": "Documento creado por ana@ejemplo.com desde API", "actorType": "api", "signatureId": null, "data": { "channel": "API", "tenantId": "898959ba-7619-4e3b-bc85-04964b7400a3", "apiKeyId": "2f5e7098-fc3d-48cc-aa98-50dbf92123ed", "userEmail": "ana@ejemplo.com" }, "createdAt": "2026-07-11T17:59:55.661000Z" } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": 20 } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos-firmados.ejemplos.spec.ts` ## Get evidence bundle GET `/documents/{document_id}/evidence` Los 2 archivos de respaldo de un documento: el PDF sellado con todas las firmas (`evidencePdf`) y la constancia de conservación NOM-151 (`nom151`, `null` si el documento no es `livemode`). Ambos son `null` hasta que **todos** los firmantes completan — el workflow de Temporal que los genera termina unos segundos después de la última firma, así que haz *poll* de `available` en vez de asumir que ya existen justo al completarse el flujo. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El bundle de evidencia — `available` indica si ya están listos los archivos. Ver los 8 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `documentId` string requerido ID del documento (`doc_…`). - `available` boolean requerido `true` una vez que todos los firmantes completaron. - `evidencePdf` objeto puede ser null PDF sellado con todas las firmas y la bitácora de auditoría. Ver 2 atributos hijos - `evidencePdf.url` string requerido URL firmada para descargar el archivo (vigente 24 horas). - `evidencePdf.sha256` string puede ser null Hash SHA-256 del archivo, si ya se calculó. - `nom151` objeto puede ser null Constancia de conservación NOM-151 (timestamp + cadena de hash). `null` si el documento no es `livemode`. Ver 2 atributos hijos - `nom151.url` string requerido URL firmada para descargar el archivo (vigente 24 horas). - `nom151.sha256` string puede ser null Hash SHA-256 del archivo, si ya se calculó. - `reason` enum puede ser null Por qué `available` es false: `document_not_signed` (falta firma; no sirve poll-ear), `evidence_generating` (todos firmaron, el PDF se está armando; sí poll-ea), `presigned_url_failed` (el archivo existe pero no se pudo firmar la URL; reintenta). Valores: `document_not_signed` `evidence_generating` `presigned_url_failed` - `signedCount` integer Cuántos firmantes ya firmaron. default 0 - `totalSigners` integer Firmantes del documento (incluye pendientes). default 0 Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/evidence' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/evidence', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/evidence", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "documentId": "doc_c18e3aaf505d4e8aa839591f6d38f7b9", "available": true, "evidencePdf": { "url": "", "sha256": "8a6bf4a711f5204bcce37981400b4a4030d969b33d24e7f2ad78748ca1e68725" }, "nom151": null, "reason": null, "signedCount": 1, "totalSigners": 1 } ``` **Respuesta · 400 · INVALID_ID** ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_ID", "title": "Malformed identifier", "status": 400, "detail": "Invalid document id.", "instance": "/v3/documents/doc_123/evidence", "code": "INVALID_ID", "requestId": "req_8a1bc74e22d945a6ad9358d2f8b27ec2" } ``` **Respuesta · 404 · DOCUMENT_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND", "title": "Document not found", "status": 404, "detail": "No document was found with that id.", "instance": "/v3/documents/doc_9c9f2f6749794c8c8717b0fd5458f074/evidence", "code": "DOCUMENT_NOT_FOUND", "requestId": "req_f004c5b00c5e41eba0d4a03491fdf499" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/descarga-el-pdf-firmado-y-la-evidencia.receta.spec.ts` ## Attach an annex POST `/documents/{document_id}/annexes` Crea un documento **nuevo** —un anexo— colgado de un documento que ya firmaron todos. El cuerpo es el mismo de `POST /documents` (archivo o plantilla, firmantes, campos…) y el anexo tiene su propio ciclo de firma; el original no se toca. La respuesta es el anexo, con `parentDocumentId` apuntando al original. Solo se anexa al documento **raíz**: anexar a un anexo responde 409 `ANNEX_OF_ANNEX_NOT_ALLOWED`, y un documento al que todavía le faltan firmas, 409 `DOCUMENT_NOT_FULLY_SIGNED`. `Idempotency-Key` obligatoria: un reintento con la misma llave devuelve el mismo anexo en vez de crear otro. Para ver el original con todos sus anexos usa `GET /documents/{id}/family`. #### Parámetros - `document_id` string ruta requerido ID del documento original, ya firmado por todos (`doc_…`). - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `source` enum puede ser null Discriminador: `template`, `file` o `compound`. Opcional (se infiere), pero si lo mandas debe concordar con el campo presente. Valores: `template` `file` `compound` - `templateId` string puede ser null ID de una plantilla existente (`tmpl_…`). Requerido cuando `source` es `template`. - `templateValues` objeto (llave → valor) puede ser null Mapa de valores para las variables de la plantilla, con llaves naturales del negocio (ej. `nombre_completo`). Nunca se camelizan. - `values` objeto (llave → valor) puede ser null Prellenado de campos del formulario PDF por nombre de campo, para cualquier rol. Los `values` de cada firmante tienen prioridad sobre estos. - `readOnly` array de string puede ser null Nombres de campos prellenados que ningún firmante puede cambiar. - `file` objeto puede ser null Archivo inline cuando `source` es `file`. Mutuamente exclusivo con `templateId`/`documents`. Ver 3 atributos hijos - `file.content` string requerido Contenido del archivo codificado en Base64. Máximo 10 MB decodificados. - `file.fileType` string Tipo de archivo. Default `pdf`. default "pdf" - `file.name` string puede ser null Nombre del archivo (ej. `contrato.pdf`). Se valida contra path traversal. - `documents` array de objetos puede ser null Expediente compound: 1..N piezas (archivo o plantilla). Mutuamente exclusivo con el `file`/`templateId` de un solo origen. Mismo cerebro que `POST /v2/documents/` con `documents[]`. Ver 7 atributos hijos - `documents[].file` objeto puede ser null Archivo inline (PDF/DOCX en base64). Mutuamente exclusivo con `templateId`. Ver 3 atributos hijos - `documents[].file.content` string requerido Contenido del archivo codificado en Base64. Máximo 10 MB decodificados. - `documents[].file.fileType` string Tipo de archivo. Default `pdf`. default "pdf" - `documents[].file.name` string puede ser null Nombre del archivo (ej. `contrato.pdf`). Se valida contra path traversal. - `documents[].templateId` string puede ser null ID de plantilla (`tmpl_…`). Mutuamente exclusivo con `file`. - `documents[].templateValues` objeto (llave → valor) puede ser null Valores owner-fixed para variables de este item (llaves naturales, no camelCase). - `documents[].name` string puede ser null Nombre visible de esta pieza dentro del expediente. máx. 255 caracteres - `documents[].kind` enum Rol de la pieza: `main`, `attachment` o `cover`. Valores: `main` `attachment` `cover` default "main" - `documents[].position` integer puede ser null Orden 1-based de concatenación. Si se omite, se asigna 1..N por índice. mín. 1 - `documents[].dedupeScope` enum `document` comparte variables entre piezas; `pdf` las aísla a este item. Valores: `document` `pdf` default "document" - `fields` array de objetos puede ser null Campos de firma a colocar al crear (`anchorString` o `position`). Cada `email`/`phone` debe coincidir con un `signers[]`. En compound usa `documentPdfPosition` para anclar a una pieza. Ver 10 atributos hijos - `fields[].email` string puede ser null Correo del firmante (debe coincidir con un `signers[].email`). - `fields[].phone` string puede ser null WhatsApp del firmante (debe coincidir con un `signers[].phone`). - `fields[].pageNumber` integer puede ser null Página 1-based. En anclas, acota la búsqueda a esa página. - `fields[].documentPdfPosition` integer puede ser null Compound: posición 1-based del PDF de `documents[]` al que pertenece este campo. - `fields[].anchorString` string puede ser null Texto ancla en el PDF. Mutuamente exclusivo con `position`. - `fields[].anchorHorizontalAlignment` enum Alineación horizontal sobre el ancla. Ignorado en modo coordenadas. Valores: `left` `center` `right` default "left" - `fields[].anchorVerticalAlignment` enum Alineación vertical sobre el ancla. Ignorado en modo coordenadas. Valores: `top` `center` `bottom` default "center" - `fields[].position` objeto puede ser null Coordenadas {x,y} en puntos PDF. Mutuamente exclusivo con `anchorString`. Ver 2 atributos hijos - `fields[].position.x` number requerido - `fields[].position.y` number requerido - `fields[].height` number Alto del campo en puntos. El ancho es 2×. default 100 · mayor que 0 - `fields[].includeInAllPages` boolean Solo modo coordenadas: repetir el campo en todas las páginas. default false - `sendInvitations` boolean Si es true, dispara invitaciones al crear (equivale a v2 `config.sendInvitations`). Fuerza `startAtStep=3`. default false - `startAtStep` integer Paso inicial: 1=borrador, 2=campos, 3=esperando firmas (cobra al crear). default 1 · entre 1 y 3 - `folderId` string puede ser null Carpeta destino (`fld_…`). - `ownerEmail` string puede ser null Dueño del documento (email de un user del tenant). Equivale a v2 `permissions.ownerEmail`. - `name` string puede ser null Nombre visible del documento. Si se omite, se deriva de la plantilla o del archivo. - `signers` array de objetos puede ser null Firmantes a adjuntar al crear el documento. Ver 10 atributos hijos - `signers[].email` string puede ser null Correo del firmante. Manda `email` o `phone`, no los dos: cada firmante recibe su invitación por un solo canal (422 `VALIDATION_ERROR` si llegan ambos). - `signers[].phone` string puede ser null Teléfono del firmante (para invitación por WhatsApp). Excluye a `email`: un firmante lleva un solo canal. máx. 32 caracteres - `signers[].name` string puede ser null Nombre del firmante. máx. 255 caracteres - `signers[].roleName` string puede ser null Rol semántico del firmante (ej. `proveedor`), usado para auto-asignar variables de plantilla marcadas con ese rol. Opcional — si se omite, las variables deben asignarse manualmente. máx. 255 caracteres - `signers[].values` objeto (llave → valor) puede ser null Prellenado de campos del formulario PDF por nombre de campo (texto o casilla). Sólo aplica a plantillas PDF con campos; las llaves nunca se camelizan. - `signers[].readOnly` array de string puede ser null Nombres de campos prellenados que el firmante no puede cambiar. - `signers[].routingOrder` integer puede ser null Etapa de firma (1 = primera). Firmantes con el mismo número firman en paralelo. Solo aplica con `signingOrder: "sequential"`. Con `signingOrder: "sequential"` es **obligatorio en todos** los firmantes: si le falta a alguno, la creación se rechaza con 422 `VALIDATION_ERROR` diciendo en cuál falta, en vez de repartir etapas por su cuenta. Dentro del producto un firmante sin número cuenta como etapa 1, pero la API v3 no asume ese default: un `sequential` a medio numerar sería un documento que firma todo el mundo a la vez sin decirlo. Después de crear, la etapa se cambia con `PATCH /v3/documents/{documentId}/signers/{signerId}`. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. mín. 1 - `signers[].kind` enum `signer` (default) firma. `carbon_copy` es un observador: nunca firma ni recibe campos, solo los avisos de `events` y, con `capability: notify_and_view`, un enlace de solo lectura. Un observador necesita `email` y no lleva `roleName`, `routingOrder`, `values` ni `readOnly`. Después de crear se administran con `/v3/documents/{documentId}/observers`. Valores: `signer` `carbon_copy` default "signer" - `signers[].capability` enum puede ser null Solo observadores: `notify` (default) o `notify_and_view`. Valores: `notify` `notify_and_view` - `signers[].events` array de string puede ser null Solo observadores: avisos que recibe. `document.completed` (default), `document.expired`, `document.voided`. máx. 10 elementos - `signatureValidation` objeto puede ser null Nivel de validez legal (autógrafa/NOM-151/FEA/biometría/videofirma). Por default: solo autógrafa. Ver 6 atributos hijos - `signatureValidation.autografa` boolean Firma autógrafa (trazo en pantalla). default true - `signatureValidation.nom151` boolean Constancia de conservación NOM-151. default false - `signatureValidation.fea` boolean Firma Electrónica Avanzada (FEA/e.firma SAT). default false - `signatureValidation.biometricSignature` boolean Verificación biométrica (selfie vs. identificación, anti-deepfake). default false - `signatureValidation.idScan` boolean Escaneo de identificación oficial (INE, pasaporte). default false - `signatureValidation.videofirma` boolean Graba video del firmante durante el proceso de firma. default false - `expiresAt` string (fecha-hora ISO 8601) puede ser null Fecha límite de firma (ISO 8601). - `mode` enum puede ser null Pasa `draft` para crear el documento sin cobrar ni enviar invitaciones (fuerza `startAtStep=1`). Si se omite, se conserva el comportamiento actual. Valores: `draft` - `signingOrder` enum puede ser null `parallel` (default): todos los firmantes reciben su invitación a la vez. `sequential`: cada firmante trae `routingOrder` y solo la etapa activa puede firmar; la siguiente se invita cuando la anterior termina. `sequential` exige `routingOrder` en **cada** firmante (422 `VALIDATION_ERROR` si falta alguno) y `parallel` no lo admite en ninguno. El modo no se puede cambiar después; las etapas sí, con `PATCH /v3/documents/{documentId}/signers/{signerId}`. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. Valores: `parallel` `sequential` #### Devuelve **201** El anexo creado (mismo shape que Retrieve document), con `parentDocumentId`. — [el objeto Document](#objeto-document) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [402](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [413](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_f225e42012ba44dd853a06e460b02674/annexes' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "v3audit-ejemplos anexo", "file": { "content": "'"$(base64 < contrato.pdf | tr -d '\n')"'", "fileType": "pdf", "name": "contrato.pdf" }, "signers": [ { "email": "signer-success@sandbox.allsign.io", "name": "Ana Torres" } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_f225e42012ba44dd853a06e460b02674/annexes', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'v3audit-ejemplos anexo', file: { content: readFileSync('contrato.pdf').toString('base64'), fileType: 'pdf', name: 'contrato.pdf', }, signers: [ { email: 'signer-success@sandbox.allsign.io', name: 'Ana Torres', }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_f225e42012ba44dd853a06e460b02674/annexes", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "v3audit-ejemplos anexo", "file": { "content": base64.b64encode(Path("contrato.pdf").read_bytes()).decode(), "fileType": "pdf", "name": "contrato.pdf", }, "signers": [ { "email": "signer-success@sandbox.allsign.io", "name": "Ana Torres", }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "doc_c4115305932549ef94df72521eaf2d20", "object": "document", "name": "v3audit-ejemplos anexo", "status": "draft", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_1aad26c4fef446eb95197ba6c5edaa0a", "orgId": "39335bc4-0297-48dc-914f-76c58bbe2921", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": "doc_f225e42012ba44dd853a06e460b02674", "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` **Respuesta · 409 · DOCUMENT_NOT_FULLY_SIGNED** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FULLY_SIGNED", "title": "Conflict", "status": 409, "detail": "Annexes can only be attached to a fully signed document. This document's current status does not allow it.", "instance": "/v3/documents/doc_78e3621115ea4fbc88605ae1c3118ec6/annexes", "code": "DOCUMENT_NOT_FULLY_SIGNED", "requestId": "req_3f16b52d15ec400c8cc87067adfef964" } ``` **Respuesta · 409 · ANNEX_OF_ANNEX_NOT_ALLOWED** ``` { "type": "https://allsign.io/developers/docs/errors#ANNEX_OF_ANNEX_NOT_ALLOWED", "title": "Conflict", "status": 409, "detail": "This document is itself an annex. Attach the new annex to the root document instead.", "instance": "/v3/documents/doc_aa07ee47bbea45edb879fdc2ac9db8e7/annexes", "code": "ANNEX_OF_ANNEX_NOT_ALLOWED", "requestId": "req_afd05ccadf4e4d4e8c22a38f25e3d8c7" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos-firmados.ejemplos.spec.ts` ## Get a document's family GET `/documents/{document_id}/family` El documento original y todos sus anexos, en orden de creación. Puedes pedirla con el id de cualquier miembro: `rootId` siempre es el original. Cada miembro trae su `status`, si es `original` o `annex` (`kind`) y de quién es anexo (`parentDocumentId`). #### Parámetros - `document_id` string ruta requerido ID de cualquier documento de la familia (`doc_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El original y sus anexos. Ver los 4 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "document\_family" Siempre `"document_family"`. - `rootId` string requerido El documento original de la familia (`doc_…`). - `data` array de objetos requerido El original y todos sus anexos, en orden cronológico ascendente. Ver 5 atributos hijos - `data[].id` string requerido ID del documento (`doc_…`). - `data[].status` enum requerido Estado del ciclo de vida de este documento. Valores: `draft` `collecting_data` `awaiting_signatures` `correcting` `processing` `completed` `expired` `voided` `error` - `data[].kind` enum requerido `original` (sin padre) o `annex` (tiene `parentDocumentId`). Valores: `original` `annex` - `data[].parentDocumentId` string puede ser null Documento del que este es anexo (`doc_…`), o `null` si es el original. - `data[].createdAt` string (fecha-hora ISO 8601) requerido Fecha de creación (ISO 8601). Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_f225e42012ba44dd853a06e460b02674/family' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_f225e42012ba44dd853a06e460b02674/family', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_f225e42012ba44dd853a06e460b02674/family", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "document_family", "rootId": "doc_f225e42012ba44dd853a06e460b02674", "data": [ { "id": "doc_f225e42012ba44dd853a06e460b02674", "status": "completed", "kind": "original", "parentDocumentId": null, "createdAt": "2026-07-11T17:59:52.015000Z" }, { "id": "doc_c4115305932549ef94df72521eaf2d20", "status": "draft", "kind": "annex", "parentDocumentId": "doc_f225e42012ba44dd853a06e460b02674", "createdAt": "2026-07-11T18:00:00.000000Z" } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos-firmados.ejemplos.spec.ts` ## Remind signer POST `/documents/{document_id}/signers/{signer_id}/remind` Reenvía la invitación (email o WhatsApp, según cómo se agregó el firmante) a UN firmante que todavía no completa. Exige `Idempotency-Key`: un reintento de red con la misma llave devuelve la primera respuesta y no manda un segundo correo. Además hay un freno de un recordatorio cada 4 horas por firmante. La respuesta `200` trae `nextAllowedAt`: guárdala y no vuelvas a recordar antes de esa hora. Si lo haces, la respuesta es `429 RATE_LIMITED` con la hora solo dentro de `detail` y sin `Retry-After`, así que no la reintentes con backoff. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `signer_id` string ruta requerido ID del firmante a recordar (`sgr_…`). - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Confirmación del recordatorio — incluye `nextAllowedAt`. Ver los 6 atributos de la respuesta - `documentId` string requerido ID del documento (`doc_…`). - `signerId` string requerido ID del firmante recordado (`sgr_…`). - `sentAt` string (fecha-hora ISO 8601) requerido Cuándo se procesó el recordatorio. - `nextAllowedAt` string (fecha-hora ISO 8601) requerido Antes de esta fecha, un nuevo recordatorio a este firmante responde 429. - `channel` string requerido Canal de entrega: `email` o `whatsapp`. - `delivered` boolean requerido `true` si el envío tuvo éxito. `false` no falla la petición — el remitente puede reintentar. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/remind' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/remind', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/remind", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={}, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "documentId": "doc_d0a1551c68d6457294e702fb25d07e4e", "signerId": "sgr_80189c3a228a438cbb36757ed5f4738c", "sentAt": "2026-07-11T18:00:00.000000Z", "nextAllowedAt": "2026-07-11T18:00:00.000000Z", "channel": "email", "delivered": false } ``` **Respuesta · 404 · SIGNER_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#SIGNER_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Signer '8bc0129e-c73d-4d49-b5d2-c0335a209b8d' not found in this document.", "instance": "/v3/documents/doc_ca783e90f0444172a7fbe75400ab5ebc/signers/sgr_8bc0129ec73d4d49b5d2c0335a209b8d/remind", "code": "SIGNER_NOT_FOUND", "requestId": "req_aa688e8a2b7e46ab88bb8e67a281ba61" } ``` **Respuesta · 409 · INVALID_STATE_TRANSITION** ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_STATE_TRANSITION", "title": "Conflict", "status": 409, "detail": "Document is in terminal state 'ANULADO' — cannot remind.", "instance": "/v3/documents/doc_ca783e90f0444172a7fbe75400ab5ebc/signers/sgr_f70f3376d8fc450a97111874e086f6b6/remind", "code": "INVALID_STATE_TRANSITION", "requestId": "req_18baf385999e4893975db73d70c38511", "reason": "document_terminal" } ``` **Respuesta · 409 · NOT_YOUR_TURN** ``` { "type": "https://allsign.io/developers/docs/errors#NOT_YOUR_TURN", "title": "Conflict", "status": 409, "detail": "Signer belongs to stage 3 and stage 1 has not finished — nothing to remind yet.", "instance": "/v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/signers/sgr_0dcad5b836254b2791fab1bb6e6995f2/remind", "code": "NOT_YOUR_TURN", "requestId": "req_fda92aa97e3743a9805cbc1eaee1292c", "reason": "not_your_turn" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos.ejemplos.spec.ts` ## Remind all pending signers POST `/documents/{document_id}/signers/remind-all` Reenvía el recordatorio a TODOS los firmantes pendientes del documento de una sola llamada — la versión masiva de `POST .../remind`. Cada firmante conserva su propio enfriamiento de 4h y su propio turno de orden de firma: uno en cooldown, o que todavía no le toca, nunca bloquea a los demás — aparece en `results[]` con su propio `status` (`sent` | `failed` | `rate_limited` | `skipped`) en vez de fallar toda la petición. Solo falla como un todo si el DOCUMENTO no existe (**404**) o está en un estado terminal (**409**, `reason: document_terminal`) — ahí no hay nada que recordarle a nadie. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Un resultado por firmante pendiente en `results[]`, cada uno con su `status` (`sent`, `failed`, `rate_limited` o `skipped`); `sentCount` cuenta solo los recordatorios que sí salieron. Ver los 3 atributos de la respuesta - `documentId` string requerido ID del documento (`doc_…`). - `sentCount` integer requerido Cuántos firmantes SÍ recibieron el recordatorio. - `results` array de objetos requerido Ver 7 atributos hijos - `results[].signerId` string requerido ID del firmante (`sgr_…`). - `results[].signerName` string puede ser null Nombre para mostrar del firmante, si se conoce. - `results[].status` string requerido `sent` | `failed` | `rate_limited` | `skipped`. - `results[].delivered` boolean `true` si el envío tuvo éxito (solo aplica cuando status=sent). default false - `results[].channel` string puede ser null Canal de entrega, cuando status=sent. - `results[].nextAllowedAt` string (fecha-hora ISO 8601) puede ser null Cuándo se puede volver a recordar a este firmante. - `results[].detail` string puede ser null Motivo legible cuando status != sent. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/signers/remind-all' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/signers/remind-all', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_cb8538f977be47d597896fd6a71f8f17/signers/remind-all", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={}, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "documentId": "doc_cb8538f977be47d597896fd6a71f8f17", "sentCount": 0, "results": [ { "signerId": "sgr_0dcad5b836254b2791fab1bb6e6995f2", "signerName": "Ana Torres", "status": "skipped", "delivered": false, "channel": null, "nextAllowedAt": null, "detail": "Signer belongs to stage 3 and stage 1 has not finished — nothing to remind yet." }, { "signerId": "sgr_f2f4eea4da414a38a7aeec6c22ba4bf1", "signerName": "Luis Ramírez", "status": "rate_limited", "delivered": false, "channel": null, "nextAllowedAt": null, "detail": "Reminder rate-limited. Next allowed at 2026-07-11T21:59:59.909000+00:00." } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos.ejemplos.spec.ts` ## Reassign signer POST `/documents/{document_id}/signers/{signer_id}/reassign` Cambia a la **persona** detrás de un firmante sin perder su lugar: el `sgr_…` es el mismo, sus campos y su etapa en el orden de firma sobreviven, y su rol conserva nombre y cajas. Manda exactamente uno de `email` o `phone` (correo → WhatsApp y de regreso están soportados, una llamada a la vez). Lo que el firmante anterior había capturado se descarta — sus campos vuelven a vacío, su verificación de identidad no se hereda y sus ligas vivas se revocan; lo que prellenó el emisor se conserva. Si ya había capturado algo, la API exige `confirm: true` (409 `INVALID_STATE_TRANSITION` con `reason: "confirmation_required"` si falta). Si el documento ya está enviado y es el turno de ese firmante, la invitación sale sola al nuevo contacto; al anterior no se le avisa. Un firmante que ya firmó no se puede reasignar (409). `Idempotency-Key` obligatorio: un reintento con la misma llave devuelve la misma respuesta sin volver a mover nada. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `signer_id` string ruta requerido ID del firmante a reasignar (`sgr_…`). Es el que se conserva. - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `email` string puede ser null Nuevo correo del firmante. - `phone` string puede ser null Nuevo teléfono (E.164) del firmante. - `name` string puede ser null Nombre a mostrar mientras no haya IDV. - `reason` string puede ser null Motivo — queda en el historial del documento. máx. 500 caracteres - `confirm` boolean El firmante anterior ya capturó algo (campos, verificación de identidad). Pásalo en `true` para confirmar que se descarta. default false #### Devuelve **200** El firmante ya con la persona nueva: mismo `id`, `email`/`phone`/`name` nuevos, `status` reinicia a `pending` o `sent` según si la invitación salió. Ver los 11 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido ID del firmante (`sgr_…`). - `object` string, siempre "signer" Siempre `"signer"`. - `documentId` string requerido Documento al que pertenece (`doc_…`). - `email` string puede ser null Correo del firmante. - `phone` string puede ser null Teléfono del firmante. - `name` string puede ser null Nombre del firmante. - `status` enum requerido Estado por firmante: `pending`, `sent`, `signed`, `cancelled`, `waiting_turn`. Valores: `pending` `sent` `signed` `cancelled` `waiting_turn` - `signedAt` string (fecha-hora ISO 8601) puede ser null Momento de la firma (ISO 8601), o `null` si aún no firma. - `routingOrder` integer puede ser null Etapa de firma en un documento `sequential`; `null` en paralelo. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. - `delivery` objeto puede ser null Entrega de la última invitación vigente (no revocada). `null` si no hay nada rastreado: invitaciones por correo sin rebote, anteriores al seguimiento, o un firmante recién reasignado. Ver 5 atributos hijos - `delivery.channel` enum requerido Canal de la última invitación: `whatsapp` o `email`. Valores: `whatsapp` `email` - `delivery.status` enum requerido Estado de entrega según el proveedor: `sent`, `delivered`, `read` o `failed`. En correo solo se informa `failed` (rebote). Valores: `sent` `delivered` `read` `failed` - `delivery.reason` enum puede ser null Por qué falló, en un catálogo estable de AllSign: `provider_payment_issue`, `undeliverable`, `bounced` u `other`. `null` si no falló. Valores: `provider_payment_issue` `undeliverable` `bounced` `other` - `delivery.providerCode` integer puede ser null Código de error original del proveedor (p. ej. de WhatsApp), o `null`. - `delivery.updatedAt` string (fecha-hora ISO 8601) requerido Momento del último cambio de estado (ISO 8601). Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/reassign' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "email": "signer-pending+relevo@sandbox.allsign.io", "name": "Luis Ramírez", "reason": "Cambió el representante legal" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/reassign', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ email: 'signer-pending+relevo@sandbox.allsign.io', name: 'Luis Ramírez', reason: 'Cambió el representante legal', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/reassign", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "email": "signer-pending+relevo@sandbox.allsign.io", "name": "Luis Ramírez", "reason": "Cambió el representante legal", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "sgr_80189c3a228a438cbb36757ed5f4738c", "object": "signer", "documentId": "doc_d0a1551c68d6457294e702fb25d07e4e", "email": "signer-pending+relevo@sandbox.allsign.io", "phone": null, "name": "Luis Ramírez", "status": "pending", "signedAt": null, "routingOrder": null, "delivery": null } ``` **Respuesta · 409 · INVALID_STATE_TRANSITION** ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_STATE_TRANSITION", "title": "Conflict", "status": 409, "detail": "No se puede reasignar a un firmante que ya firmó.", "instance": "/v3/documents/doc_b7b43220a55940578a9feaef6c618cf5/signers/sgr_7d4f43e179d1487aab88d212de9def03/reassign", "code": "INVALID_STATE_TRANSITION", "requestId": "req_560c08b2d31f4128b8762eeec30fbf4e", "reason": "already_signed" } ``` **Respuesta · 404 · SIGNER_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#SIGNER_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Firmante no encontrado.", "instance": "/v3/documents/doc_1dc79b7f4dd64d08ba9d3ed7a5626977/signers/sgr_bea9964ea0ca474592e3bd980042cc9e/reassign", "code": "SIGNER_NOT_FOUND", "requestId": "req_49cdadde120a49b9973885cb14f8c49e" } ``` **Respuesta · 422 · SIGNER_INVALID** ``` { "type": "https://allsign.io/developers/docs/errors#SIGNER_INVALID", "title": "Signer invalid", "status": 422, "detail": "Es la misma persona; no hay nada que reasignar.", "instance": "/v3/documents/doc_1dc79b7f4dd64d08ba9d3ed7a5626977/signers/sgr_ea59f1a241c543029768ecb7f38a65bb/reassign", "code": "SIGNER_INVALID", "requestId": "req_922f9a91fb0c491495d387fe0f98bc12", "reason": "same_person" } ``` **Respuesta · 422 · DUPLICATE_SIGNER** ``` { "type": "https://allsign.io/developers/docs/errors#DUPLICATE_SIGNER", "title": "Duplicate signer", "status": 422, "detail": "Esa persona ya es firmante de este documento.", "instance": "/v3/documents/doc_1dc79b7f4dd64d08ba9d3ed7a5626977/signers/sgr_ea59f1a241c543029768ecb7f38a65bb/reassign", "code": "DUPLICATE_SIGNER", "requestId": "req_3851c69baf2940bf874d02b256d8021a" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:14 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/errores.ejemplos.spec.ts` ## Sign as a signer (sandbox only) POST `/sandbox/signers/{signer_id}/sign` Firma por un firmante en **sandbox**, como si hubiera abierto su liga: estampa su firma en sus cajas y lo deja `signed`. Responde **200** en el momento, con el firmante ya firmado; si era el último, en la misma llamada el documento queda `completed` con su PDF de evidencia (en sandbox, con una constancia NOM-151 simulada, sin validez legal). Emite los mismos webhooks que una firma real: `signer.signed` y, si era el último, `document.completed`. Úsalo con `signer-pending@sandbox.allsign.io`, que nunca firma solo y no recibe correo, para recorrer a tu ritmo el ciclo real: `awaiting_signatures` → `signer.signed` → `completed` (ver [Entornos](https://allsign.io/developers/docs/environments#firmantes-magicos)). **Solo existe en sandbox.** Con una key live responde **409** `ENVIRONMENT_MISMATCH` y no cambia nada: en producción cada firmante firma con su propia liga. **409** `INVALID_STATE_TRANSITION` cuando el estado lo impide, con `reason`: `never_invited` (el documento todavía no se envía), `document_terminal` (ya terminó, se anuló, venció o está en corrección), `signer_terminal` (ese firmante ya firmó o fue cancelado) o `variables_pending` (faltan variables requeridas de su rol). En un documento `sequential`, **409** `NOT_YOUR_TURN` si su etapa todavía no abre. Un `signerId` que no existe, de otra organización o de otro entorno responde **404** `SIGNER_NOT_FOUND`. `Idempotency-Key` es opcional. Si la mandas, un reintento con la misma llave te devuelve la respuesta original (con `Idempotency-Replayed: true`) en vez del 409 que daría volver a firmar a alguien que ya firmó. #### Parámetros - `signer_id` string ruta requerido ID del firmante (`sgr_…`), el que devuelve `GET /documents/{documentId}/signers`. - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El firmante ya firmado (`status: "signed"`). Si era el último, el documento ya está `completed`. Ver los 11 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido ID del firmante (`sgr_…`). - `object` string, siempre "signer" Siempre `"signer"`. - `documentId` string requerido Documento al que pertenece (`doc_…`). - `email` string puede ser null Correo del firmante. - `phone` string puede ser null Teléfono del firmante. - `name` string puede ser null Nombre del firmante. - `status` enum requerido Estado por firmante: `pending`, `sent`, `signed`, `cancelled`, `waiting_turn`. Valores: `pending` `sent` `signed` `cancelled` `waiting_turn` - `signedAt` string (fecha-hora ISO 8601) puede ser null Momento de la firma (ISO 8601), o `null` si aún no firma. - `routingOrder` integer puede ser null Etapa de firma en un documento `sequential`; `null` en paralelo. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. - `delivery` objeto puede ser null Entrega de la última invitación vigente (no revocada). `null` si no hay nada rastreado: invitaciones por correo sin rebote, anteriores al seguimiento, o un firmante recién reasignado. Ver 5 atributos hijos - `delivery.channel` enum requerido Canal de la última invitación: `whatsapp` o `email`. Valores: `whatsapp` `email` - `delivery.status` enum requerido Estado de entrega según el proveedor: `sent`, `delivered`, `read` o `failed`. En correo solo se informa `failed` (rebote). Valores: `sent` `delivered` `read` `failed` - `delivery.reason` enum puede ser null Por qué falló, en un catálogo estable de AllSign: `provider_payment_issue`, `undeliverable`, `bounced` u `other`. `null` si no falló. Valores: `provider_payment_issue` `undeliverable` `bounced` `other` - `delivery.providerCode` integer puede ser null Código de error original del proveedor (p. ej. de WhatsApp), o `null`. - `delivery.updatedAt` string (fecha-hora ISO 8601) requerido Momento del último cambio de estado (ISO 8601). Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/sandbox/signers/sgr_d64e1ca6bcf64eebac2d35da21d23c88/sign' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/sandbox/signers/sgr_d64e1ca6bcf64eebac2d35da21d23c88/sign', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/sandbox/signers/sgr_d64e1ca6bcf64eebac2d35da21d23c88/sign", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "sgr_d64e1ca6bcf64eebac2d35da21d23c88", "object": "signer", "documentId": "doc_fdde14eeff0b4444acec4d6b10736068", "email": "signer-pending@sandbox.allsign.io", "phone": null, "name": "Ana Torres", "status": "signed", "signedAt": "2026-07-11T18:01:06.969000Z", "routingOrder": null, "delivery": null } ``` **Respuesta · 409 · INVALID_STATE_TRANSITION** ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_STATE_TRANSITION", "title": "Conflict", "status": 409, "detail": "The document has not been sent yet. Send it first with POST /v3/documents/{documentId}/send.", "instance": "/v3/sandbox/signers/sgr_592c7816786d4e70bb676a8be0648b5b/sign", "code": "INVALID_STATE_TRANSITION", "requestId": "req_c1f762446ffa48c2a620e3c8297daa1f", "reason": "never_invited" } ``` **Respuesta · 409 · NOT_YOUR_TURN** ``` { "type": "https://allsign.io/developers/docs/errors#NOT_YOUR_TURN", "title": "Conflict", "status": 409, "detail": "Signer belongs to stage 2 and stage 1 has not finished — sign that stage first.", "instance": "/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign", "code": "NOT_YOUR_TURN", "requestId": "req_d29e50a055114b728bdde26ba79637cb", "reason": "not_your_turn" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:19 (hora CDMX) · test `docs-checks-v3/tests/recetas/primer-documento-a-firma.receta.spec.ts` ## List document observers GET `/documents/{document_id}/observers` Lista quién observa el documento: los observadores agregados a **este** documento (`origin: "document"`, con su `id` `rol_…`) y los de la organización, que observan todos los documentos (`origin: "organization"`, `id: null` y `locked: true`; esos se quitan desde Ajustes → Observadores, no por documento). Un observador **nunca firma**: recibe los avisos que eligió en `events` y, con `capability: "notify_and_view"`, un enlace de solo lectura. `status` vale `pending_verification` mientras el correo de un observador de organización no confirme la regla. Es una colección acotada: `hasMore` siempre es `false`. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Lista (`object: "list"`) con `documentId` y los observadores en `data`. Ver los 5 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "list" Siempre `"list"`. - `documentId` string requerido El documento (`doc_…`). - `data` array de objetos requerido Observadores del documento y de la organización. Ver 10 atributos hijos - `data[].object` string, siempre "document\_observer" Siempre `"document_observer"`. - `data[].id` string puede ser null Id del observador (`rol_…`). `null` en los de la organización: esos se quitan desde Ajustes → Observadores, no por documento. - `data[].origin` enum requerido `document` si se agregó a este documento, `organization` si observa todos. Valores: `document` `organization` - `data[].email` string puede ser null Correo del observador. - `data[].name` string puede ser null Nombre del observador. - `data[].capability` enum requerido `notify`: solo avisos. `notify_and_view`: avisos y un enlace de solo lectura. Valores: `notify` `notify_and_view` - `data[].events` array de string requerido Avisos que recibe: `document.completed` (default), `document.expired` y `document.voided`. - `data[].locked` boolean requerido `true` cuando viene de la organización y no se puede quitar aquí. - `data[].status` string `active`, o `pending_verification` si el correo aún no confirma la regla global. default "active" - `data[].createdAt` string (fecha-hora ISO 8601) puede ser null Cuándo se agregó. - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "list", "documentId": "doc_f2344cc1b9b24ba2954715812b3d7242", "data": [ { "object": "document_observer", "id": "rol_9f8c7b2441a646b5aacbd925b2d3b6d9", "origin": "document", "email": "luis@ejemplo.com", "name": "Luis Ramírez", "capability": "notify_and_view", "events": [ "document.completed", "document.voided" ], "locked": false, "status": "active", "createdAt": "2026-07-11T18:00:01.027000Z" } ], "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/observadores-y-transferencia.receta.spec.ts` ## Add document observer POST `/documents/{document_id}/observers` Agrega a alguien que **nunca firma** y solo recibe avisos del documento. Manda `email` (obligatorio) y, si quieres, `name`, `capability` —`notify` (default, solo avisos) o `notify_and_view` (avisos y un enlace de solo lectura)— y `events`, los avisos que recibe (default `["document.completed"]`). Es idempotente por correo: si ese correo ya observa el documento, se actualizan sus ajustes y la respuesta trae `created: false`; pasar de `notify_and_view` a `notify` revoca sus enlaces de solo lectura. Un observador nuevo recibe un correo de aviso, y los webhooks suscritos reciben `document.observer_added` cuando se agrega o cambian sus ajustes. Un firmante del documento no puede ser también observador (409 `OBSERVER_IS_SIGNER`) y un documento anulado ya no acepta observadores (409 `DOCUMENT_CONFLICT`). Requiere la función `document-observers` activa en tu cuenta; sin ella responde 403 `FEATURE_NOT_AVAILABLE` y no cambia nada. #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `email` string requerido Correo del observador. Un firmante no puede observar. máx. 320 caracteres - `name` string puede ser null Nombre del observador. máx. 255 caracteres - `capability` enum `notify` (default) o `notify_and_view` (avisos y enlace de solo lectura). Valores: `notify` `notify_and_view` - `events` array de string puede ser null Avisos que recibe: `document.completed` (default), `document.expired` y `document.voided`. máx. 10 elementos #### Devuelve **201** Siempre `201`: el observador agregado o actualizado en `observer`, y `created` en `false` si ese correo ya observaba y solo cambiaron sus ajustes. Ver los 3 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `observer` objeto requerido El observador agregado o actualizado. Ver 10 atributos hijos - `observer.object` string, siempre "document\_observer" Siempre `"document_observer"`. - `observer.id` string puede ser null Id del observador (`rol_…`). `null` en los de la organización: esos se quitan desde Ajustes → Observadores, no por documento. - `observer.origin` enum requerido `document` si se agregó a este documento, `organization` si observa todos. Valores: `document` `organization` - `observer.email` string puede ser null Correo del observador. - `observer.name` string puede ser null Nombre del observador. - `observer.capability` enum requerido `notify`: solo avisos. `notify_and_view`: avisos y un enlace de solo lectura. Valores: `notify` `notify_and_view` - `observer.events` array de string requerido Avisos que recibe: `document.completed` (default), `document.expired` y `document.voided`. - `observer.locked` boolean requerido `true` cuando viene de la organización y no se puede quitar aquí. - `observer.status` string `active`, o `pending_verification` si el correo aún no confirma la regla global. default "active" - `observer.createdAt` string (fecha-hora ISO 8601) puede ser null Cuándo se agregó. - `created` boolean requerido `false` si ya observaba y solo se actualizaron sus ajustes. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "email": "luis@ejemplo.com", "name": "Luis Ramírez", "capability": "notify_and_view", "events": [ "document.completed", "document.voided" ] }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ email: 'luis@ejemplo.com', name: 'Luis Ramírez', capability: 'notify_and_view', events: [ 'document.completed', 'document.voided', ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "email": "luis@ejemplo.com", "name": "Luis Ramírez", "capability": "notify_and_view", "events": [ "document.completed", "document.voided", ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "observer": { "object": "document_observer", "id": "rol_9f8c7b2441a646b5aacbd925b2d3b6d9", "origin": "document", "email": "luis@ejemplo.com", "name": "Luis Ramírez", "capability": "notify_and_view", "events": [ "document.completed", "document.voided" ], "locked": false, "status": "active", "createdAt": "2026-07-11T18:00:01.027000Z" }, "created": true } ``` **Respuesta · 409 · DOCUMENT_CONFLICT** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_CONFLICT", "title": "Conflict", "status": 409, "detail": "The document was voided; it no longer takes observers.", "instance": "/v3/documents/doc_09bf1cccce9b463691037861ca4eea6f/observers", "code": "DOCUMENT_CONFLICT", "requestId": "req_77042c955bec4f2b8bd00e6388216505" } ``` **Respuesta · 409 · OBSERVER_IS_SIGNER** ``` { "type": "https://allsign.io/developers/docs/errors#OBSERVER_IS_SIGNER", "title": "Conflict", "status": 409, "detail": "ana@ejemplo.com already signs this document; a signer cannot also be an observer.", "instance": "/v3/documents/doc_5dbd1cc097404878a5668c6a00774830/observers", "code": "OBSERVER_IS_SIGNER", "requestId": "req_68de996d2d6442b981652319043cc610" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:14 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/errores.ejemplos.spec.ts` ## Remove document observer DELETE `/documents/{document_id}/observers/{observer_id}` Quita un observador agregado a este documento y revoca sus enlaces de solo lectura; los webhooks suscritos reciben `document.observer_removed`. Solo quita observadores de documento: los de la organización (`origin: "organization"`) no tienen `id` y se quitan desde Ajustes → Observadores. Requiere la función `document-observers` activa en tu cuenta (403 `FEATURE_NOT_AVAILABLE`). #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `observer_id` string ruta requerido ID del observador (`rol_…`): el `id` que devuelven la lista o el alta. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** `object: "document_observer"`, el `id` quitado y `deleted: true`. Ver los 4 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "document\_observer" Siempre `"document_observer"`. - `id` string requerido Id del observador quitado (`rol_…`). - `deleted` string, siempre "true" Siempre `true`. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X DELETE 'https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers/rol_9f8c7b2441a646b5aacbd925b2d3b6d9' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers/rol_9f8c7b2441a646b5aacbd925b2d3b6d9', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.delete( "https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers/rol_9f8c7b2441a646b5aacbd925b2d3b6d9", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "document_observer", "id": "rol_9f8c7b2441a646b5aacbd925b2d3b6d9", "deleted": true } ``` **Respuesta · 404 · OBSERVER_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#OBSERVER_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Observer not found in this document. Organization observers are removed from Settings → Observers.", "instance": "/v3/documents/doc_5dbd1cc097404878a5668c6a00774830/observers/rol_245aa9e7229547b5b3e5cf30afe54094", "code": "OBSERVER_NOT_FOUND", "requestId": "req_22602288f4704ad2857ebd2b3dfa1468" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/observadores-y-transferencia.receta.spec.ts` ## Transfer document owner POST `/documents/{document_id}/transfer-owner` Pasa el documento a otro miembro del **mismo** espacio de trabajo, nunca a otro espacio. Lo puede hacer el dueño actual o un owner/admin del espacio; cualquier otro recibe 403 `PERMISSION_DENIED`. Manda exactamente uno de `ownerUserId` (`usr_…`) u `ownerEmail`. Firmas, evidencia, constancias NOM-151 y quién creó el documento no cambian. La bitácora gana un evento `document.owner_transferred` (con tu `reason`, si la mandas) y los webhooks suscritos reciben el mismo evento. Con `keepPreviousOwnerAccess` (default `true`) el dueño anterior conserva acceso de lectura; en `false` lo pierde. Si el documento pertenece a una cadena, manda `transferChain: true` (409 `DOCUMENT_CONFLICT` si falta): se traspasa la cadena completa y `documentIds` dice cuáles cambiaron. `Idempotency-Key` obligatoria. Repetir el traspaso hacia quien ya es dueño responde `transferred: false`, igual que reintentar con la misma llave. Requiere la función `document-owner-transfer` activa en tu cuenta (403 `FEATURE_NOT_AVAILABLE`). #### Parámetros - `document_id` string ruta requerido ID del documento (`doc_…`). - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `ownerUserId` string puede ser null Nuevo dueño (`usr_…`). Debe ser miembro del espacio del documento. - `ownerEmail` string puede ser null Nuevo dueño por correo. Alternativa a `ownerUserId`; manda solo uno. - `keepPreviousOwnerAccess` boolean Si es `true`, el dueño anterior conserva acceso de lectura. Si es `false`, pierde el acceso. default true - `transferChain` boolean Obligatorio en `true` si el documento pertenece a una cadena: se traspasa la cadena completa. default false - `reason` string puede ser null Motivo del traspaso. Queda en la bitácora del documento y en el webhook `document.owner_transferred`. máx. 500 caracteres #### Devuelve **200** `documentId`, `ownerId` y `previousOwnerId` (`usr_…`), `transferred` y `documentIds`, los documentos que cambiaron de dueño. Ver los 5 atributos de la respuesta - `documentId` string requerido Documento pedido (`doc_…`). - `ownerId` string requerido Dueño después del traspaso (`usr_…`). - `previousOwnerId` string requerido Dueño antes del traspaso (`usr_…`). - `transferred` boolean requerido `false` si el documento ya era de ese dueño o la misma `Idempotency-Key` ya lo había traspasado. - `documentIds` array de string Documentos que cambiaron de dueño: el pedido y, con `transferChain`, el resto de su cadena. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/transfer-owner' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "ownerUserId": "usr_b478f972e8574a739be5fb7364382c33", "reason": "Cambio de responsable de la cuenta" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/transfer-owner', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ ownerUserId: 'usr_b478f972e8574a739be5fb7364382c33', reason: 'Cambio de responsable de la cuenta', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/transfer-owner", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "ownerUserId": "usr_b478f972e8574a739be5fb7364382c33", "reason": "Cambio de responsable de la cuenta", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "documentId": "doc_f2344cc1b9b24ba2954715812b3d7242", "ownerId": "usr_b478f972e8574a739be5fb7364382c33", "previousOwnerId": "usr_550487c74d4a4061ba2af6b802dcacde", "transferred": true, "documentIds": [ "doc_f2344cc1b9b24ba2954715812b3d7242" ] } ``` **Respuesta · 422 · VALIDATION_ERROR** ``` { "type": "https://allsign.io/developers/docs/errors#VALIDATION_ERROR", "title": "Validation failed", "status": 422, "detail": "The new owner must be a member of this workspace.", "instance": "/v3/documents/doc_5dbd1cc097404878a5668c6a00774830/transfer-owner", "code": "VALIDATION_ERROR", "requestId": "req_156c01d6c0fd40f982ae571ecb18cce4" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/observadores-y-transferencia.receta.spec.ts` ## Transfer documents owner POST `/documents/transfer-owner` Traspasa varios documentos al miembro `toUserId` (`usr_…`) del mismo espacio, en una sola llamada síncrona de hasta 500 documentos. Manda exactamente uno de `documentIds` (máximo 500) o `fromUserId`, que toma todos los documentos de ese dueño en el espacio y en el entorno de tu API key. Si `fromUserId` tiene más de 500, responde 422 `VALIDATION_ERROR` y no mueve nada: pártelos en tandas con `documentIds`. **Éxito parcial por diseño**: un documento que no existe, de otro espacio o entorno, que no puedes administrar o de una cadena sin `transferChains: true` falla solo su elemento (`items[].status: "error"` con el motivo en `error`). Un `200` no significa que todo se movió: revisa `items[]` y los contadores. Todos los documentos traspasados comparten el mismo `batchId` en su bitácora y en su webhook `document.owner_transferred`. `keepPreviousOwnerAccess` y `reason` funcionan igual que en el traspaso de uno. `Idempotency-Key` obligatoria. Requiere la función `document-owner-transfer` activa en tu cuenta (403 `FEATURE_NOT_AVAILABLE`). #### Parámetros - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `fromUserId` string puede ser null Traspasa todos los documentos de este dueño (`usr_…`) en el espacio. Alternativa a `documentIds`. - `documentIds` array de string puede ser null Documentos a traspasar (`doc_…`, máximo 500 por petición). Alternativa a `fromUserId`. máx. 500 elementos · mín. 1 elementos - `toUserId` string requerido Nuevo dueño (`usr_…`). Debe ser miembro del espacio. - `keepPreviousOwnerAccess` boolean Si es `false`, cada dueño anterior pierde el acceso a lo traspasado. default true - `transferChains` boolean Permite traspasar documentos de cadenas (con la cadena completa). Sin él, esos documentos fallan. default false - `reason` string puede ser null Motivo del traspaso. Queda en la bitácora del documento y en el webhook `document.owner_transferred`. máx. 500 caracteres #### Devuelve **200** `batchId`, `totalCount`, `transferredCount`, `unchangedCount`, `errorCount` e `items[]`, con `status` `transferred`, `unchanged` o `error` por documento. Ver los 6 atributos de la respuesta - `batchId` string requerido Identificador del lote; viaja como `batchId` en la bitácora y en cada webhook. - `totalCount` integer requerido Documentos considerados. - `transferredCount` integer requerido Cuántos cambiaron de dueño. - `unchangedCount` integer requerido Cuántos ya eran del nuevo dueño. - `errorCount` integer requerido Cuántos no se pudieron traspasar. - `items` array de objetos requerido Resultado por documento. Ver 3 atributos hijos - `items[].documentId` string requerido El id tal como se pidió. - `items[].status` enum requerido Open enum: clients MUST tolerate values not in this list. Valores: `transferred` `unchanged` `error` - `items[].error` string puede ser null Motivo si `status` es `error`. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/transfer-owner' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "toUserId": "usr_550487c74d4a4061ba2af6b802dcacde", "documentIds": [ "doc_f2344cc1b9b24ba2954715812b3d7242" ], "reason": "Regresa a su responsable original" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/transfer-owner', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ toUserId: 'usr_550487c74d4a4061ba2af6b802dcacde', documentIds: [ 'doc_f2344cc1b9b24ba2954715812b3d7242', ], reason: 'Regresa a su responsable original', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/transfer-owner", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "toUserId": "usr_550487c74d4a4061ba2af6b802dcacde", "documentIds": [ "doc_f2344cc1b9b24ba2954715812b3d7242", ], "reason": "Regresa a su responsable original", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "batchId": "7dd2209a-5de4-4e64-ac28-c0d62da264b9", "totalCount": 1, "transferredCount": 1, "unchangedCount": 0, "errorCount": 0, "items": [ { "documentId": "doc_f2344cc1b9b24ba2954715812b3d7242", "status": "transferred", "error": null } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/observadores-y-transferencia.receta.spec.ts` ## Bulk delete documents DELETE `/documents/bulk` Elimina hasta 100 documentos en una sola petición. Éxito parcial por diseño (igual que v2): un id mal formado, un id de otro tenant, o un documento en un estado no eliminable (ya firmado o en progreso) falla SOLO ese elemento — nunca todo el lote. Revisa `items[].status` por cada id, nunca asumas éxito total por un `200`. #### Parámetros - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `documentIds` array de string requerido IDs de los documentos a eliminar (`doc_…`, máximo 100 por petición). máx. 100 elementos · mín. 1 elementos #### Devuelve **200** Resultado por elemento (`totalCount`/`successCount`/`errorCount`/`items[]`) — mismo vocabulario que Create bulk send. Ver los 4 atributos de la respuesta - `totalCount` integer requerido Documentos pedidos. - `successCount` integer requerido Cuántos se eliminaron. - `errorCount` integer requerido Cuántos no se pudieron eliminar. - `items` array de objetos requerido Resultado por documento, en el orden pedido. Ver 3 atributos hijos - `items[].documentId` string requerido El id tal como se envió en la petición. - `items[].status` enum requerido Per-document outcome. Open enum — same vocabulary shape as BulkSendItemStatus on purpose: bulk-delete and bulk-send speak one vocabulary for "result per item of a bulk op". Open enum: clients MUST tolerate values not in this list. Valores: `deleted` `error` - `items[].error` string puede ser null Motivo si `status` es `error` (id inválido, no encontrado, no eliminable). Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X DELETE 'https://api.allsign.io/v3/documents/bulk' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "documentIds": [ "doc_82fa050abbc44b9587b71c8f84e8f923" ] }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/bulk', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ documentIds: [ 'doc_82fa050abbc44b9587b71c8f84e8f923', ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.delete( "https://api.allsign.io/v3/documents/bulk", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "documentIds": [ "doc_82fa050abbc44b9587b71c8f84e8f923", ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "totalCount": 1, "successCount": 1, "errorCount": 0, "items": [ { "documentId": "doc_82fa050abbc44b9587b71c8f84e8f923", "status": "deleted", "error": null } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/documentos.ejemplos.spec.ts` ## Create bulk send POST `/documents/bulk-sends` Sube un PDF una sola vez y crea N documentos independientes, uno por destinatario, cada uno con su propio enlace de firma. **Asíncrono**: valida todo de forma síncrona (créditos, tamaño de archivo, forma del body; un correo de destinatario inválido es `422 VALIDATION_ERROR` y no se crea ni se cobra nada) y agenda el trabajo pesado — la respuesta es un **202** con el lote en `processing` y cada `items[].status` en `pending`. Haz *poll* de `GET /documents/bulk-sends/{id}` (Get bulk send) hasta que `status` cambie a `completed` o `partialError`. Cada documento del lote nace con una caja de firma para su destinatario, las mismas cajas automáticas que pone Create document cuando recibe `signers` sin `fields`. **Costo**: cada documento creado se cobra lo mismo que enviarlo por separado con `POST /documents/{id}/send` (los créditos de su `signatureValidation` en tu plan), aunque mandes `sendInvites: false`; no es 1 crédito por documento. El saldo de todo el lote se revisa antes de agendar (`402 INSUFFICIENT_CREDITS`). `Idempotency-Key` es **obligatorio**: un reintento con la misma key devuelve el MISMO 202 + el mismo id de lote (`Idempotency-Replayed: true`) en vez de agendar el trabajo dos veces — nunca cobra créditos ni invita dos veces por un retry. #### Parámetros - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string requerido Nombre del documento (aparece en el email de invitación de cada destinatario). de 1 a 255 caracteres - `file` objeto requerido El PDF a enviar — el mismo archivo para todos los destinatarios. Ver 3 atributos hijos - `file.content` string requerido Contenido del archivo codificado en Base64. Máximo 10 MB decodificados. - `file.fileType` string Tipo de archivo. Default `pdf`. default "pdf" - `file.name` string puede ser null Nombre del archivo (ej. `contrato.pdf`). Se valida contra path traversal. - `recipients` array de objetos requerido Destinatarios; cada uno recibe su propio documento y enlace de firma. Máximo 200. máx. 200 elementos · mín. 1 elementos Ver 3 atributos hijos - `recipients[].email` string requerido Correo del destinatario. - `recipients[].name` string puede ser null Nombre del destinatario (opcional). - `recipients[].role` string puede ser null Nombre del rol del documento al que se enlaza este destinatario. Sin él, el firmante estrena un rol propio y no hereda los campos del rol de la plantilla. - `signatureValidation` objeto puede ser null Nivel de validez legal, aplicado a cada documento del lote. Ver 6 atributos hijos - `signatureValidation.autografa` boolean Firma autógrafa (trazo en pantalla). default true - `signatureValidation.nom151` boolean Constancia de conservación NOM-151. default false - `signatureValidation.fea` boolean Firma Electrónica Avanzada (FEA/e.firma SAT). default false - `signatureValidation.biometricSignature` boolean Verificación biométrica (selfie vs. identificación, anti-deepfake). default false - `signatureValidation.idScan` boolean Escaneo de identificación oficial (INE, pasaporte). default false - `signatureValidation.videofirma` boolean Graba video del firmante durante el proceso de firma. default false - `sendInvites` boolean Si es `false`, crea los documentos sin despachar la invitación de firma todavía. default true #### Devuelve **202** El lote agendado — header `Location` apunta a Get bulk send; `status: "processing"`, cada item `pending`. Ver los 7 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido ID del lote de envío (`bat_…`). - `status` enum requerido Batch-level outcome. `createBulkSend` is asynchronous: it returns `processing` immediately and the client polls `getBulkSend` until `completed` or `partialError`. Open enum (`x-extensible-enum`) — a tolerant reader must not hard-fail on a value it doesn't recognise. Open enum: clients MUST tolerate values not in this list. Valores: `processing` `completed` `partial_error` - `totalCount` integer requerido Destinatarios pedidos. - `successCount` integer requerido Documentos creados exitosamente. - `errorCount` integer requerido Destinatarios que fallaron — ver `items[].error`. - `items` array de objetos requerido Resultado por destinatario, en el orden pedido. Ver 4 atributos hijos - `items[].recipientEmail` string requerido El correo tal como se envió en la petición. - `items[].documentId` string puede ser null ID del documento creado (`doc_…`). `null` si este destinatario falló. - `items[].status` enum requerido Per-recipient outcome. Open enum — see BulkSendStatus. Open enum: clients MUST tolerate values not in this list. Valores: `pending` `sent` `created` `error` - `items[].error` string puede ser null Motivo si `status` es `error`. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [402](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [413](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/bulk-sends' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "v3audit-ejemplos lote", "file": { "content": "'"$(base64 < contrato.pdf | tr -d '\n')"'", "fileType": "pdf", "name": "contrato.pdf" }, "recipients": [ { "email": "ana@ejemplo.com", "name": "Ana Torres" } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/documents/bulk-sends', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'v3audit-ejemplos lote', file: { content: readFileSync('contrato.pdf').toString('base64'), fileType: 'pdf', name: 'contrato.pdf', }, recipients: [ { email: 'ana@ejemplo.com', name: 'Ana Torres', }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/bulk-sends", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "v3audit-ejemplos lote", "file": { "content": base64.b64encode(Path("contrato.pdf").read_bytes()).decode(), "fileType": "pdf", "name": "contrato.pdf", }, "recipients": [ { "email": "ana@ejemplo.com", "name": "Ana Torres", }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 403 · DEV_FEATURE_RESTRICTED** ``` { "type": "https://allsign.io/developers/docs/errors#DEV_FEATURE_RESTRICTED", "title": "Forbidden", "status": 403, "detail": "The feature 'bulk_send' is not available with your current API key (environment: test).", "instance": "/v3/documents/bulk-sends", "code": "DEV_FEATURE_RESTRICTED", "requestId": "req_87e254c1249946cfbdc8516819d10deb", "featureRequested": "bulk_send", "environment": "test", "allowedFeatures": [ "firma_autografa", "firma_simple", "embedded_signing" ], "allowedFeaturesDisplay": [ "Firma Autógrafa (Wet Signature)", "Firma Simple (Click-to-sign)", "Embedded Signing Widget" ], "upgradeMessage": "This feature is only available with a production key (allsign_live_sk_…). Create a live key in your AllSign dashboard under Developers → API Keys.", "upgradeUrl": "https://allsign.io/pricing" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/sistema.ejemplos.spec.ts` ## Get bulk send GET `/documents/bulk-sends/{batch_id}` Consulta el resultado de un `createBulkSend` asíncrono. Usa el MISMO objeto que la respuesta 202 original — `status` empieza en `processing` (todos los items `pending`) y se asienta en `completed` o `partialError` una vez que cada destinatario fue intentado. #### Parámetros - `batch_id` string ruta requerido ID del lote (`bat_…`), del header `Location` de Create bulk send. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El lote — `status`/`items[]` reflejan el progreso más reciente. Ver los 7 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido ID del lote de envío (`bat_…`). - `status` enum requerido Batch-level outcome. `createBulkSend` is asynchronous: it returns `processing` immediately and the client polls `getBulkSend` until `completed` or `partialError`. Open enum (`x-extensible-enum`) — a tolerant reader must not hard-fail on a value it doesn't recognise. Open enum: clients MUST tolerate values not in this list. Valores: `processing` `completed` `partial_error` - `totalCount` integer requerido Destinatarios pedidos. - `successCount` integer requerido Documentos creados exitosamente. - `errorCount` integer requerido Destinatarios que fallaron — ver `items[].error`. - `items` array de objetos requerido Resultado por destinatario, en el orden pedido. Ver 4 atributos hijos - `items[].recipientEmail` string requerido El correo tal como se envió en la petición. - `items[].documentId` string puede ser null ID del documento creado (`doc_…`). `null` si este destinatario falló. - `items[].status` enum requerido Per-recipient outcome. Open enum — see BulkSendStatus. Open enum: clients MUST tolerate values not in this list. Valores: `pending` `sent` `created` `error` - `items[].error` string puede ser null Motivo si `status` es `error`. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/documents/bulk-sends/bat_e6282f72d60345d384d2b16cf0700624' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/bulk-sends/bat_e6282f72d60345d384d2b16cf0700624', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/bulk-sends/bat_e6282f72d60345d384d2b16cf0700624", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 404 · BATCH_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#BATCH_NOT_FOUND", "title": "Bulk-send batch not found", "status": 404, "detail": "No bulk-send batch was found with that id.", "instance": "/v3/documents/bulk-sends/bat_e6282f72d60345d384d2b16cf0700624", "code": "BATCH_NOT_FOUND", "requestId": "req_7e1406b4cbd74967a85f8b8844351fe6" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/sistema.ejemplos.spec.ts` --- Fuente: https://allsign.io/developers/docs/endpoints/templates.md # Templates Una plantilla es un PDF (o DOCX) que reutilizas para emitir documentos. Si el PDF trae un **formulario** (AcroForm), sus widgets entran como **campos** con nombre, tipo y posición; tú les pones **rol** y, al crear cada documento, el firmante que toma ese rol hereda sus campos. Los campos también se dibujan por API (`POST …/fields`), se copian desde un documento ya armado (`fields:from-document`) y se versionan como un layout declarativo (`PUT …/fields`). La emisión vive en `POST /v3/documents` con `templateId`; la guía [Formularios PDF](https://allsign.io/developers/docs/guides/pdf-forms) recorre el proceso completo. `readiness` resume el estado de la plantilla: `empty` (sin campos ni variables), `pending` (algún campo sin rol) o `ready` (todo asignado, lista para emitir). `GET …/file?format=form` devuelve el PDF con los widgets vivos para revisarlo en Acrobat; `format=flat` lo hornea. Las plantillas DOCX con variables `{{ }}` siguen funcionando (`/variables`, `validate-values`, `templateValues` al crear); las plantillas PDF con formulario usan campos y `values`. Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). [Descargar OpenAPI](https://allsign.io/developers/docs/openapi.public.json)[Colección de Postman](https://allsign.io/developers/docs/allsign-api-v3.postman_collection.json) Endpoints - [GET`/templates`](#list-templates) - [POST`/templates`](#create-template) - [GET`/templates/{template_id}`](#retrieve-template) - [PATCH`/templates/{template_id}`](#update-template) - [DELETE`/templates/{template_id}`](#delete-template) - [POST`/templates/{template_id}:duplicate`](#duplicate-template) - [POST`/templates/{template_id}/documents`](#create-a-document-from-a-template) - [GET`/templates/{template_id}/versions`](#list-template-versions) - [GET`/templates/facets`](#list-template-facets) - [GET`/templates/catalog`](#list-catalog-templates) - [POST`/templates/catalog/{slug}:copy`](#copy-catalog-template) - [GET`/templates/{template_id}/fields`](#list-template-fields) - [POST`/templates/{template_id}/fields`](#create-template-field) - [PATCH`/templates/{template_id}/fields/{name}`](#update-template-field) - [DELETE`/templates/{template_id}/fields/{name}`](#delete-template-field) - [PUT`/templates/{template_id}/fields`](#replace-template-fields) - [POST`/templates/{template_id}/fields:from-document`](#import-fields-from-a-document) - [GET`/templates/{template_id}/roles`](#list-template-roles) - [PUT`/templates/{template_id}/roles`](#replace-template-roles) - [GET`/templates/{template_id}/file`](#download-template-file) - [POST`/templates/{template_id}/file`](#replace-a-pdf-template-s-file) - [GET`/templates/{template_id}/download`](#download-original-file) - [GET`/templates/{template_id}/preview`](#get-template-preview) - [GET`/templates/{template_id}/pages/{page_number}`](#get-template-page-image) - [GET`/templates/{template_id}/layout`](#get-template-layout) - [GET`/templates/{template_id}/marked.docx`](#download-marked-word-file) - [GET`/templates/{template_id}/variables`](#list-template-variables) - [POST`/templates/{template_id}/variables`](#create-template-variable) - [PATCH`/templates/{template_id}/variables`](#update-template-variable) - [DELETE`/templates/{template_id}/variables`](#delete-template-variable) - [POST`/templates/{template_id}/variables:confirm`](#confirm-variable-candidates) - [GET`/templates/{template_id}/analysis`](#get-template-analysis) - [POST`/templates/{template_id}/validate-values`](#validate-template-values) - [GET`/templates/{template_id}/signing`](#get-signing-layout-legacy) - [PUT`/templates/{template_id}/signing`](#replace-signing-layout-legacy) - [GET`/templates/{template_id}/signing/documents`](#list-documents-to-seed-the-signing-layout-legacy) - [POST`/templates/{template_id}/signing:from-document`](#import-signing-layout-from-a-document-legacy) ## El objeto Template #### Atributos - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido ID de la plantilla (`tmpl_…`). - `object` string, siempre "template" Siempre `"template"`. - `name` string requerido Nombre de la plantilla. - `description` string puede ser null Descripción de la plantilla. - `fileType` string requerido Tipo de archivo fuente (ej. `docx`, `pdf`). - `originalFilename` string puede ser null Nombre del archivo original subido. - `aiEditable` boolean Si la edición asistida por IA está habilitada. default true - `variableCount` integer requerido Número de variables detectadas en la plantilla. - `fieldCount` integer Número de campos del formulario PDF (nombres distintos). default 0 - `unassignedFieldCount` integer Campos del formulario sin rol; `readiness` es `pending` mientras haya alguno. default 0 - `roleCount` integer Número de roles de la plantilla. default 0 - `pendingCandidates` integer Candidatos del análisis vigente que aún no se confirmaron como variable (`0` si no hay análisis). default 0 - `readiness` enum requerido `ready` (≥1 variable y 0 pendientes, o ≥1 campo y todos con rol), `pending` (≥1 candidato sin confirmar o ≥1 campo sin rol) o `empty` (0 variables y 0 campos). Valores: `ready` `pending` `empty` - `tags` array de string Etiquetas de la plantilla (arreglo vacío por default). default \[\] - `category` string puede ser null Categoría de la plantilla. - `usageCount` integer requerido Cuántas veces se ha usado la plantilla. - `lastUsedAt` string (fecha-hora ISO 8601) puede ser null Último uso (ISO 8601). - `currentVersion` integer requerido Versión actual de la plantilla. - `previewUrl` string puede ser null Ruta v3 relativa (`/templates/{id}/preview`) del PNG de la primera página. `null` si la plantilla no tiene una fuente renderizable (p.ej. DOCX). - `downloadUrl` string requerido Ruta v3 relativa (`/templates/{id}/download`) del archivo original. - `createdAt` string (fecha-hora ISO 8601) requerido Fecha de creación (ISO 8601). - `updatedAt` string (fecha-hora ISO 8601) requerido Última actualización (ISO 8601). **El objeto Template** ``` { "livemode": false, "id": "tmpl_39701d80589b412dbd39e02b48679840", "object": "template", "name": "Solicitud de alta de cliente", "description": null, "fileType": "pdf", "originalFilename": "formulario.pdf", "aiEditable": true, "variableCount": 0, "fieldCount": 5, "unassignedFieldCount": 0, "roleCount": 1, "pendingCandidates": 0, "readiness": "ready", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 1, "previewUrl": "/templates/tmpl_39701d80589b412dbd39e02b48679840/preview", "downloadUrl": "/templates/tmpl_39701d80589b412dbd39e02b48679840/download", "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/formulario-pdf-acroform.receta.spec.ts` ## List templates GET `/templates` Lista tus plantillas con paginación por cursor y filtros por tipo de archivo, categoría y texto libre. #### Parámetros - `limit` integer query Resultados por página (1–100, default `20`). default 20 · entre 1 y 100 - `startingAfter` string query Cursor: plantillas después de este `id` (`tmpl_…`). - `endingBefore` string query Cursor: plantillas antes de este `id` (`tmpl_…`). - `sort` enum query Orden por fecha de creación: `createdAt` o `-createdAt` (default `-createdAt`). Valores: `createdAt` `-createdAt` `updatedAt` `-updatedAt` `name` `-name` `usageCount` `-usageCount` - `fileType` string query Filtra por tipo de archivo (ej. `docx`, `pdf`). - `category` string query Filtra por categoría. - `tags` string query CSV de etiquetas — la plantilla debe tener TODAS. - `search` string query Búsqueda por texto libre en el nombre (1–255 caracteres). de 1 a 255 caracteres - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Sobre de paginación por cursor (`object: "list"`) con objetos Template. Ver los 6 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Arreglo de objetos Template. Ver 23 atributos hijos - `data[].livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `data[].id` string requerido ID de la plantilla (`tmpl_…`). - `data[].object` string, siempre "template" Siempre `"template"`. - `data[].name` string requerido Nombre de la plantilla. - `data[].description` string puede ser null Descripción de la plantilla. - `data[].fileType` string requerido Tipo de archivo fuente (ej. `docx`, `pdf`). - `data[].originalFilename` string puede ser null Nombre del archivo original subido. - `data[].aiEditable` boolean Si la edición asistida por IA está habilitada. default true - `data[].variableCount` integer requerido Número de variables detectadas en la plantilla. - `data[].fieldCount` integer Número de campos del formulario PDF (nombres distintos). default 0 - `data[].unassignedFieldCount` integer Campos del formulario sin rol; `readiness` es `pending` mientras haya alguno. default 0 - `data[].roleCount` integer Número de roles de la plantilla. default 0 - `data[].pendingCandidates` integer Candidatos del análisis vigente que aún no se confirmaron como variable (`0` si no hay análisis). default 0 - `data[].readiness` enum requerido `ready` (≥1 variable y 0 pendientes, o ≥1 campo y todos con rol), `pending` (≥1 candidato sin confirmar o ≥1 campo sin rol) o `empty` (0 variables y 0 campos). Valores: `ready` `pending` `empty` - `data[].tags` array de string Etiquetas de la plantilla (arreglo vacío por default). default \[\] - `data[].category` string puede ser null Categoría de la plantilla. - `data[].usageCount` integer requerido Cuántas veces se ha usado la plantilla. - `data[].lastUsedAt` string (fecha-hora ISO 8601) puede ser null Último uso (ISO 8601). - `data[].currentVersion` integer requerido Versión actual de la plantilla. - `data[].previewUrl` string puede ser null Ruta v3 relativa (`/templates/{id}/preview`) del PNG de la primera página. `null` si la plantilla no tiene una fuente renderizable (p.ej. DOCX). - `data[].downloadUrl` string requerido Ruta v3 relativa (`/templates/{id}/download`) del archivo original. - `data[].createdAt` string (fecha-hora ISO 8601) requerido Fecha de creación (ISO 8601). - `data[].updatedAt` string (fecha-hora ISO 8601) requerido Última actualización (ISO 8601). - `hasMore` boolean requerido `true` si hay más resultados después de esta página. - `nextCursor` string puede ser null Cursor para la siguiente página (pásalo como `startingAfter`). - `previousCursor` string puede ser null Cursor para la página anterior (pásalo como `endingBefore`). - `limit` integer requerido El límite aplicado a esta página. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates?limit=2&fileType=docx' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates?limit=2&fileType=docx', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates?limit=2&fileType=docx", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "tmpl_2a1bc982e2f04c979ff8d8c2a6468539", "object": "template", "name": "v3audit-ejemplos Compraventa", "description": null, "fileType": "docx", "originalFilename": "contrato.docx", "aiEditable": true, "variableCount": 5, "fieldCount": 0, "unassignedFieldCount": 0, "roleCount": 0, "pendingCandidates": 0, "readiness": "ready", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 1, "previewUrl": null, "downloadUrl": "/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/download", "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" }, { "livemode": false, "id": "tmpl_31bbe670e5184ce4ac02a705ea963ee2", "object": "template", "name": "v3audit-ejemplos Compraventa", "description": "Contrato de compraventa", "fileType": "docx", "originalFilename": "contrato.docx", "aiEditable": true, "variableCount": 5, "fieldCount": 0, "unassignedFieldCount": 0, "roleCount": 0, "pendingCandidates": 0, "readiness": "ready", "tags": [ "compraventa" ], "category": "ventas", "usageCount": 1, "lastUsedAt": "2026-07-07T09:41:56.383000Z", "currentVersion": 4, "previewUrl": null, "downloadUrl": "/templates/tmpl_31bbe670e5184ce4ac02a705ea963ee2/download", "createdAt": "2026-07-07T09:41:50.156000Z", "updatedAt": "2026-07-07T09:41:52.767000Z" } ], "hasMore": true, "nextCursor": "", "previousCursor": null, "limit": 2 } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Create template POST `/templates` Da de alta una plantilla a partir de un **PDF** o un **DOCX** en base64 (máximo 10 MB decodificados). Es el arranque del dominio: si el PDF trae formulario, sus widgets entran como campos (`GET …/fields`, guía [Formularios PDF](https://allsign.io/developers/docs/guides/pdf-forms)); si es un DOCX con `{{ variables }}`, se detectan en el mismo paso (`GET …/variables`). La respuesta trae `fieldCount`, `variableCount` y `readiness`. `Idempotency-Key` es opcional pero se honra: un reintento devuelve la misma plantilla. #### Parámetros - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string requerido Nombre visible de la plantilla. de 1 a 255 caracteres - `file` objeto requerido Archivo DOCX o PDF en base64. Máximo 10 MB decodificados. `fileType` es `docx` o `pdf`. Ver 3 atributos hijos - `file.content` string requerido Contenido del archivo codificado en Base64. Máximo 10 MB decodificados. - `file.fileType` enum Tipo de archivo fuente: `docx` o `pdf`. Valores: `docx` `pdf` default "docx" - `file.name` string puede ser null Nombre del archivo (ej. `contrato.pdf`). Se valida contra path traversal. - `description` string puede ser null Descripción libre de la plantilla. máx. 2000 caracteres - `category` string puede ser null Categoría de la plantilla (ej. `legal`). máx. 100 caracteres - `tags` array de string puede ser null Etiquetas de la plantilla. #### Devuelve **201** La plantilla recién creada. — [el objeto Template](#objeto-template) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [413](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Acuerdo de confidencialidad", "file": { "content": "'"$(base64 < acuerdo.docx | tr -d '\n')"'", "fileType": "docx", "name": "acuerdo.docx" } }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/templates', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Acuerdo de confidencialidad', file: { content: readFileSync('acuerdo.docx').toString('base64'), fileType: 'docx', name: 'acuerdo.docx', }, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/templates", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "Acuerdo de confidencialidad", "file": { "content": base64.b64encode(Path("acuerdo.docx").read_bytes()).decode(), "fileType": "docx", "name": "acuerdo.docx", }, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "tmpl_67fb98658fe548d3a71eae28087d7060", "object": "template", "name": "Acuerdo de confidencialidad", "description": null, "fileType": "docx", "originalFilename": "acuerdo.docx", "aiEditable": true, "variableCount": 22, "fieldCount": 0, "unassignedFieldCount": 0, "roleCount": 0, "pendingCandidates": 38, "readiness": "pending", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 1, "previewUrl": null, "downloadUrl": "/templates/tmpl_67fb98658fe548d3a71eae28087d7060/download", "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` **Respuesta · 422 · UNSUPPORTED_FORM_XFA** ``` { "type": "https://allsign.io/developers/docs/errors#UNSUPPORTED_FORM_XFA", "title": "Unsupported XFA form", "status": 422, "detail": "This PDF uses an XFA (LiveCycle) form, which is not supported. Export it as a standard AcroForm PDF and try again.", "instance": "/v3/templates", "code": "UNSUPPORTED_FORM_XFA", "requestId": "req_fdd1b0f5006646e5a3a2894a39851d3f" } ``` **Respuesta · 422 · PDF_ALREADY_SIGNED** ``` { "type": "https://allsign.io/developers/docs/errors#PDF_ALREADY_SIGNED", "title": "PDF already signed", "status": 422, "detail": "This PDF already carries a digital signature, and editing it would invalidate it. Upload the unsigned version or a flat PDF.", "instance": "/v3/templates", "code": "PDF_ALREADY_SIGNED", "requestId": "req_7ce35e2fb6a24801a7f2dcd25b07db59" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:14 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/errores.ejemplos.spec.ts` ## Retrieve template GET `/templates/{template_id}` Consulta una plantilla por su `id`. Un `id` inexistente responde **404** (no 500). #### Parámetros - `template_id` string ruta requerido ID de la plantilla (`tmpl_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El objeto Template. — [el objeto Template](#objeto-template) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "tmpl_39701d80589b412dbd39e02b48679840", "object": "template", "name": "Solicitud de alta de cliente", "description": null, "fileType": "pdf", "originalFilename": "formulario.pdf", "aiEditable": true, "variableCount": 0, "fieldCount": 5, "unassignedFieldCount": 0, "roleCount": 1, "pendingCandidates": 0, "readiness": "ready", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 1, "previewUrl": "/templates/tmpl_39701d80589b412dbd39e02b48679840/preview", "downloadUrl": "/templates/tmpl_39701d80589b412dbd39e02b48679840/download", "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/formulario-pdf-acroform.receta.spec.ts` ## Update template PATCH `/templates/{template_id}` Merge-patch de los metadatos: `name`, `description`, `category`, `tags`. Nunca cambia el archivo ni los campos/variables (para eso están `…/fields` y `…/variables`). #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string puede ser null Nuevo nombre de la plantilla. de 1 a 255 caracteres - `description` string puede ser null Nueva descripción. máx. 2000 caracteres - `category` string puede ser null Nueva categoría. máx. 100 caracteres - `tags` array de string puede ser null Nuevo arreglo de etiquetas — reemplaza al anterior por completo. #### Devuelve **200** La plantilla ya actualizada. — [el objeto Template](#objeto-template) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X PATCH 'https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "description": "Contrato de compraventa", "category": "ventas", "tags": [ "compraventa" ] }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ description: 'Contrato de compraventa', category: 'ventas', tags: [ 'compraventa', ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.patch( "https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "description": "Contrato de compraventa", "category": "ventas", "tags": [ "compraventa", ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "tmpl_2a1bc982e2f04c979ff8d8c2a6468539", "object": "template", "name": "v3audit-ejemplos Compraventa", "description": "Contrato de compraventa", "fileType": "docx", "originalFilename": "contrato.docx", "aiEditable": true, "variableCount": 5, "fieldCount": 0, "unassignedFieldCount": 0, "roleCount": 0, "pendingCandidates": 0, "readiness": "ready", "tags": [ "compraventa" ], "category": "ventas", "usageCount": 0, "lastUsedAt": null, "currentVersion": 4, "previewUrl": null, "downloadUrl": "/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/download", "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:01:03.667000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Delete template DELETE `/templates/{template_id}` Borrado suave: la plantilla desaparece de las listas pero se conserva para auditoría. Responde 409 `TEMPLATE_IN_USE` si algún documento activo todavía la referencia; el `detail` trae el conteo. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **204** Plantilla eliminada. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X DELETE 'https://api.allsign.io/v3/templates/tmpl_ca5ee5e61a714916bca98df0dc97598f' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_ca5ee5e61a714916bca98df0dc97598f', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status); ``` **Python** ``` import os import requests respuesta = requests.delete( "https://api.allsign.io/v3/templates/tmpl_ca5ee5e61a714916bca98df0dc97598f", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code) ``` **Respuesta · 204** *Sin cuerpo.* **Respuesta · 409 · TEMPLATE_IN_USE** ``` { "type": "https://allsign.io/developers/docs/errors#TEMPLATE_IN_USE", "title": "Conflict", "status": 409, "detail": "Template 2a1bc982-e2f0-4c97-9ff8-d8c2a6468539 is in use by 1 document(s) and 0 chain(s). Archive or complete them before deleting.", "instance": "/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539", "code": "TEMPLATE_IN_USE", "requestId": "req_b63edfb7b1c9427f8ea3e6ac34f145b3" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Duplicate template POST `/templates/{template_id}:duplicate` Crea una copia independiente (`"{name} (copia)"`) con el mismo archivo, campos, roles y variables. `Idempotency-Key` opcional: un reintento devuelve la primera copia. #### Parámetros - `template_id` string ruta requerido - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **201** La copia recién creada. — [el objeto Template](#objeto-template) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539:duplicate' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539:duplicate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539:duplicate", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "tmpl_ca5ee5e61a714916bca98df0dc97598f", "object": "template", "name": "v3audit-ejemplos Compraventa (copia)", "description": "Contrato de compraventa", "fileType": "docx", "originalFilename": "contrato.docx", "aiEditable": true, "variableCount": 5, "fieldCount": 0, "unassignedFieldCount": 0, "roleCount": 0, "pendingCandidates": 0, "readiness": "ready", "tags": [ "compraventa" ], "category": "ventas", "usageCount": 0, "lastUsedAt": null, "currentVersion": 1, "previewUrl": null, "downloadUrl": "/templates/tmpl_ca5ee5e61a714916bca98df0dc97598f/download", "createdAt": "2026-07-11T18:01:05.574000Z", "updatedAt": "2026-07-11T18:01:05.574000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Create a document from a template POST `/templates/{template_id}/documents` Crea un **borrador** a partir de la plantilla y, opcionalmente, lo deja listo en la misma llamada: fija `values`, adjunta `signers` y, con `send: true`, lo manda a firma. `{}` basta para obtener el borrador. Equivale a `POST /documents` con `templateId` y `mode: draft`, seguido de `PATCH /documents/{id}/values` y, si se pide, de `POST /documents/{id}/send` — el mismo documento que devuelve `GET /documents/{id}`. Con `send: true`, un valor obligatorio que llena el dueño y no viene en `values` es un 422 `VALIDATION_ERROR` con la lista en `missingVariables`, y no se crea nada: los valores quedan fijos al enviar. Las variables de un firmante y las opcionales no cuentan. Si con `values` el PDF queda con menos páginas y alguna de las que se pierden tiene campos colocados, el borrador se crea con los valores pero su PDF no cambia, igual que en `PATCH /documents/{id}/values`, y `POST /documents/{id}/send` responde 409 `DOCUMENT_NOT_SENDABLE` hasta que muevas esos campos o cambies los valores. Con `send: true` ese 409 llega en esta misma llamada, con `documentId`, sin cobrar ni invitar. `values` se valida contra la plantilla **antes** de crear nada. Si algo falla después de creado el borrador (un envío sin saldo, por ejemplo), el problem+json trae `documentId`: retómalo con `POST /documents/{id}/send`, no repitiendo esta llamada. `Idempotency-Key` es obligatoria, igual que en `POST /documents`: un reintento con la misma clave repite la respuesta original (el 201 o un 4xx) en lugar de crear otro borrador. Un 402, un 429 o un 5xx liberan la clave, así que repetir esta llamada después de ellos crea otro borrador. #### Parámetros - `template_id` string ruta requerido ID de la plantilla desde la que se emite (`tmpl_…`). - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string puede ser null Nombre visible del documento. Si se omite, se toma el de la plantilla. - `signers` array de objetos puede ser null Firmantes a adjuntar al borrador. Pueden venir solo con `roleName` (sin contacto) y completarse después; para `send: true` al menos uno necesita `email` o `phone`. Ver 10 atributos hijos - `signers[].email` string puede ser null Correo del firmante. Manda `email` o `phone`, no los dos: cada firmante recibe su invitación por un solo canal (422 `VALIDATION_ERROR` si llegan ambos). - `signers[].phone` string puede ser null Teléfono del firmante (para invitación por WhatsApp). Excluye a `email`: un firmante lleva un solo canal. máx. 32 caracteres - `signers[].name` string puede ser null Nombre del firmante. máx. 255 caracteres - `signers[].roleName` string puede ser null Rol semántico del firmante (ej. `proveedor`), usado para auto-asignar variables de plantilla marcadas con ese rol. Opcional — si se omite, las variables deben asignarse manualmente. máx. 255 caracteres - `signers[].values` objeto (llave → valor) puede ser null Prellenado de campos del formulario PDF por nombre de campo (texto o casilla). Sólo aplica a plantillas PDF con campos; las llaves nunca se camelizan. - `signers[].readOnly` array de string puede ser null Nombres de campos prellenados que el firmante no puede cambiar. - `signers[].routingOrder` integer puede ser null Etapa de firma (1 = primera). Firmantes con el mismo número firman en paralelo. Solo aplica con `signingOrder: "sequential"`. Con `signingOrder: "sequential"` es **obligatorio en todos** los firmantes: si le falta a alguno, la creación se rechaza con 422 `VALIDATION_ERROR` diciendo en cuál falta, en vez de repartir etapas por su cuenta. Dentro del producto un firmante sin número cuenta como etapa 1, pero la API v3 no asume ese default: un `sequential` a medio numerar sería un documento que firma todo el mundo a la vez sin decirlo. Después de crear, la etapa se cambia con `PATCH /v3/documents/{documentId}/signers/{signerId}`. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. mín. 1 - `signers[].kind` enum `signer` (default) firma. `carbon_copy` es un observador: nunca firma ni recibe campos, solo los avisos de `events` y, con `capability: notify_and_view`, un enlace de solo lectura. Un observador necesita `email` y no lleva `roleName`, `routingOrder`, `values` ni `readOnly`. Después de crear se administran con `/v3/documents/{documentId}/observers`. Valores: `signer` `carbon_copy` default "signer" - `signers[].capability` enum puede ser null Solo observadores: `notify` (default) o `notify_and_view`. Valores: `notify` `notify_and_view` - `signers[].events` array de string puede ser null Solo observadores: avisos que recibe. `document.completed` (default), `document.expired`, `document.voided`. máx. 10 elementos - `values` objeto (llave → valor) Mapa nombre→valor de las variables de la plantilla que fija el remitente. Las llaves conservan su forma natural (`nombre_completo`): nunca se camelizan. - `send` boolean Si es `true`, tras crear el borrador lo manda a firma como `POST /documents/{id}/send` (cobra créditos e invita a los firmantes pendientes). Exige al menos un firmante con `email` o `phone`. default false #### Devuelve **201** El documento creado (borrador, o ya enviado si pediste `send: true`). Ver los 20 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido ID del documento (`doc_…`). - `object` string, siempre "document" Siempre `"document"`. - `name` string requerido Nombre visible del documento. - `status` enum requerido Estado del ciclo de vida: `draft`, `collecting_data`, `awaiting_signatures`, `correcting`, `processing`, `completed`, `expired`, `voided`, `error`. Valores: `draft` `collecting_data` `awaiting_signatures` `correcting` `processing` `completed` `expired` `voided` `error` - `documentType` string requerido Token opaco del tipo de documento. - `signerCount` integer requerido Número total de firmantes. - `signedCount` integer requerido Cuántos firmantes ya firmaron. - `ownerId` string requerido Dueño del documento (`usr_…`). - `orgId` string puede ser null Organización del documento (`org_…`). - `folderId` string puede ser null Carpeta que contiene el documento (`fld_…`). Es `null` si el documento no está en ninguna carpeta que esta credencial pueda abrir con `GET /v3/folders/{id}`. - `expiresAt` string (fecha-hora ISO 8601) puede ser null Fecha límite de firma (ISO 8601). - `signingOrder` enum `parallel` o `sequential` (firmantes por etapas con `routingOrder`). Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. Valores: `parallel` `sequential` - `currentStage` integer puede ser null Etapa activa en un documento `sequential` mientras espera firmas; `null` en paralelo o al terminar. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. - `expirationReminders` array de integer puede ser null Horas antes del vencimiento en las que se envían recordatorios. - `templateId` string puede ser null Plantilla de la que nació el documento (`tmpl_…`), o `null`. - `templateVersionId` string puede ser null Versión de esa plantilla cuyo layout se aplicó (`tv_…`), o `null`. - `parentDocumentId` string puede ser null Documento del que este es un anexo (`doc_…`). `null` si es un documento raíz. Ver `POST /documents/{id}/annexes` y `GET /documents/{id}/family`. - `createdAt` string (fecha-hora ISO 8601) requerido Fecha de creación (ISO 8601). - `updatedAt` string (fecha-hora ISO 8601) requerido Última actualización (ISO 8601). Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [402](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/documents' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "v3audit-ejemplos Compraventa Luis Pérez", "values": { "vendedor_nombre": "Ana Martínez" } }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/documents', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'v3audit-ejemplos Compraventa Luis Pérez', values: { vendedor_nombre: 'Ana Martínez', }, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/documents", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "v3audit-ejemplos Compraventa Luis Pérez", "values": { "vendedor_nombre": "Ana Martínez", }, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "doc_1ce40ec34cf7454e83862318509bef29", "object": "document", "name": "v3audit-ejemplos Compraventa Luis Pérez", "status": "draft", "documentType": "EDITABLE", "signerCount": 0, "signedCount": 0, "ownerId": "usr_0f1644c205794549ad7dac61379cf5d9", "orgId": "ee0b53c7-80bc-41bd-a9e2-08c594411381", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": "tmpl_2a1bc982e2f04c979ff8d8c2a6468539", "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:01:08.922000Z", "updatedAt": "2026-07-11T18:01:08.922000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## List template versions GET `/templates/{template_id}/versions` Las versiones guardadas de la plantilla, de la más reciente a la más antigua. Cada una trae su `layout` —orden de firma, roles con su etapa, firmantes habituales y ajustes del documento— y un `summary` para comparar dos versiones de un vistazo sin bajar el archivo. `summary.fields` cuenta los campos de firma de la **plantilla** (nombres distintos): los campos no se versionan, así que ese número es el mismo en todas las filas. #### Parámetros - `template_id` string ruta requerido ID de la plantilla (`tmpl_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Las versiones, de la más reciente a la más antigua. Ver los 3 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `templateId` string requerido La plantilla a la que pertenecen (`tmpl_…`). - `data` array de objetos requerido Versiones, de la más reciente a la más antigua. Ver 6 atributos hijos - `data[].id` string requerido ID de la versión (`tv_…`). - `data[].versionNumber` integer requerido Número de versión (1, 2, 3…). mín. 1 - `data[].createdAt` string (fecha-hora ISO 8601) requerido Cuándo se guardó la versión (ISO 8601). - `data[].createdBy` string requerido Quién la creó: `user`, `ai` o `system`. - `data[].summary` objeto requerido Conteos de un vistazo. Ver 4 atributos hijos - `data[].summary.roles` integer requerido Roles que trae el layout de esta versión. mín. 0 - `data[].summary.fields` integer requerido Campos de firma de la plantilla (nombres distintos). mín. 0 - `data[].summary.variables` integer requerido Variables de esta versión. mín. 0 - `data[].summary.proposals` integer requerido Cuántas de esas variables traen valor por defecto (`defaultValue`). mín. 0 - `data[].layout` objeto puede ser null Layout guardado, o `null` en versiones anteriores a plt\_0001. Ver 3 atributos hijos - `data[].layout.signingOrderMode` enum `parallel` o `sequential`. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. Valores: `parallel` `sequential` - `data[].layout.roles` array de objetos Roles del documento, en orden. Ver 4 atributos hijos - `data[].layout.roles[].name` string requerido Nombre del rol tal como lo verá el siguiente documento. - `data[].layout.roles[].kind` string Papel del participante (token opaco): `signer`, `witness`, `notary`, `carbon_copy`, `approver` o `in_person_signer`. default "signer" - `data[].layout.roles[].stage` integer puede ser null Etapa de firma (1 = primera). `null` en orden paralelo. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. mín. 1 - `data[].layout.roles[].habitualSigner` objeto puede ser null Persona que suele tomar el rol, o `null`. Ver 3 atributos hijos - `data[].layout.roles[].habitualSigner.name` string puede ser null Nombre de la persona habitual. - `data[].layout.roles[].habitualSigner.email` string puede ser null Correo de la persona habitual. - `data[].layout.roles[].habitualSigner.phone` string puede ser null Teléfono de la persona habitual. - `data[].layout.settings` objeto Vigencia, recordatorios, mensaje y vista del formulario del documento. Ver 4 atributos hijos - `data[].layout.settings.expiresInDays` integer puede ser null Días de vigencia desde que se crea el documento, o `null` si no vence. mín. 1 - `data[].layout.settings.reminders` objeto puede ser null Configuración de recordatorios, o `null`. Ver 2 atributos hijos - `data[].layout.settings.reminders.enabled` boolean Si el documento manda recordatorios. default false - `data[].layout.settings.reminders.everyDays` integer puede ser null Cada cuántos días se recuerda. entre 1 y 365 - `data[].layout.settings.message` string puede ser null Mensaje del remitente que acompaña la invitación. - `data[].layout.settings.formView` enum puede ser null Vista con la que abre el formulario de los documentos que nacen de esta versión: `fill` (Llenar) o `edit` (Editar campos). `null` se trata como `edit`. Valores: `fill` `edit` Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/versions' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/versions', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/versions", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "templateId": "tmpl_2a1bc982e2f04c979ff8d8c2a6468539", "data": [ { "id": "tv_4e5665043f5b4d95808a4160146a73aa", "versionNumber": 4, "createdAt": "2026-07-11T18:01:02.952000Z", "createdBy": "user", "summary": { "roles": 0, "fields": 0, "variables": 5, "proposals": 0 }, "layout": null }, { "id": "tv_971e4eb6dfb34922ac38ff9e4272f108", "versionNumber": 3, "createdAt": "2026-07-11T18:00:01.893000Z", "createdBy": "user", "summary": { "roles": 0, "fields": 0, "variables": 6, "proposals": 0 }, "layout": null }, { "id": "tv_c870330f1d6f42bdae87f2e0d6812bd1", "versionNumber": 2, "createdAt": "2026-07-11T18:00:01.655000Z", "createdBy": "user", "summary": { "roles": 0, "fields": 0, "variables": 6, "proposals": 0 }, "layout": null }, { "id": "tv_4a34e8bd75df4f77939c1227da7c4df6", "versionNumber": 1, "createdAt": "2026-07-11T18:00:00.536000Z", "createdBy": "user", "summary": { "roles": 0, "fields": 0, "variables": 5, "proposals": 0 }, "layout": null } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## List template facets GET `/templates/facets` Las categorías y etiquetas que usan tus plantillas, con conteos, para construir filtros en tu propia UI (`GET /templates?category=…`). #### Parámetros - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada de facetas con su conteo. Ver los 2 atributos de la respuesta - `categories` array de objetos requerido Categorías presentes. Ver 2 atributos hijos - `categories[].name` string requerido Nombre de la categoría o etiqueta. - `categories[].count` integer requerido Cuántas plantillas visibles tienen este valor. - `tags` array de objetos requerido Etiquetas presentes. Ver 2 atributos hijos - `tags[].name` string requerido Nombre de la categoría o etiqueta. - `tags[].count` integer requerido Cuántas plantillas visibles tienen este valor. Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/facets' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/facets', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/facets", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "categories": [ { "name": "ventas", "count": 11 } ], "tags": [ { "name": "compraventa", "count": 11 } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## List catalog templates GET `/templates/catalog` El catálogo curado por AllSign (contratos y formatos mexicanos listos para usar). Cada entrada trae un `slug` estable y los planes que la incluyen; copia una a tu tenant con `POST /templates/catalog/{slug}:copy`. #### Parámetros - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada del catálogo publicado. Ver los 2 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Arreglo de objetos CatalogTemplate. Ver 10 atributos hijos - `data[].slug` string requerido Identificador URL-friendly, único. - `data[].displayName` string requerido Nombre público en la galería. - `data[].description` string puede ser null Descripción pública. - `data[].category` string requerido Categoría: legal, admin, rrhh, inmobiliaria, financiero. - `data[].icon` string puede ser null Emoji o clave de ícono para la galería. - `data[].isFeatured` boolean requerido `true` si se destaca en la galería. - `data[].variableCount` integer requerido Número de variables detectadas en la plantilla fuente. - `data[].includedInPlans` array de string Slugs de los planes que incluyen esta plantilla. - `data[].templateId` string requerido La plantilla fuente (`tmpl_…`). - `data[].displayOrder` integer requerido Orden de despliegue en la galería. Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/catalog' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/catalog', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/catalog", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "slug": "acuse-de-recibo", "displayName": "Acuse de recibo", "description": "Cada documento entregado merece dejar huella. Este Acuse de Recibo te da la constancia formal que necesitas — con folio, fecha, descripción detallada y firmas — para que cada intercambio quede registrado limpio y sin ambigüedad.\r\nRedactado conforme al Código Civil Federal mexicano. Solo llena las variables {{nombre}}, {{folio}}, {{descripción}} y tienes un documento listo para firmar en segundos. Formato .docx editable, entrega inmediata.", "category": "comercial", "icon": null, "isFeatured": false, "variableCount": 19, "includedInPlans": [ "basic" ], "templateId": "tmpl_9319df0c7ee444269cef0493fcd2314b", "displayOrder": 0 }, { "slug": "nda-acuerdo-confidencialidad", "displayName": "NDA Acuerdo Confidencialidad", "description": "Las mejores alianzas empiezan con confianza — y la confianza necesita estructura. Esta plantilla NDA bilateral te da exactamente eso: un acuerdo listo para firmar, redactado conforme al Código Civil Federal, la Ley Federal de Protección a la Propiedad Industrial y la Ley de Protección de Datos Personales, para que puedas avanzar con cualquier socio, cliente o colaborador sin frenar el momentum.\r\nSolo personaliza las variables {{nombre}}, {{RFC}}, {{propósito}} y listo — de idea a contrato firmado en minutos. Formato .docx editable, entrega inmediata.", "category": "general", "icon": null, "isFeatured": true, "variableCount": 12, "includedInPlans": [ "basic" ], "templateId": "tmpl_113e53a0f5f749789277aab40a3db75e", "displayOrder": 0 } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Copy catalog template POST `/templates/catalog/{slug}:copy` Materializa una copia de la plantilla del catálogo dentro de tu tenant, con sus campos, roles y variables. A partir de ahí es tuya: edítala y emite documentos con su `id`. #### Parámetros - `slug` string ruta requerido - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **201** Tu copia, ya como plantilla del tenant. — [el objeto Template](#objeto-template) Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/catalog/acuse-de-recibo:copy' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/templates/catalog/acuse-de-recibo:copy', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/catalog/acuse-de-recibo:copy", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "tmpl_eb3e151f0a094b0abc31c69eb9bb1b6e", "object": "template", "name": "Acuse de recibo", "description": "Cada documento entregado merece dejar huella. Este Acuse de Recibo te da la constancia formal que necesitas — con folio, fecha, descripción detallada y firmas — para que cada intercambio quede registrado limpio y sin ambigüedad.\r\nRedactado conforme al Código Civil Federal mexicano. Solo llena las variables {{nombre}}, {{folio}}, {{descripción}} y tienes un documento listo para firmar en segundos. Formato .docx editable, entrega inmediata.", "fileType": "docx", "originalFilename": "Acuse_de_Recibo.docx", "aiEditable": true, "variableCount": 19, "fieldCount": 0, "unassignedFieldCount": 0, "roleCount": 0, "pendingCandidates": 0, "readiness": "ready", "tags": [], "category": "comercial", "usageCount": 0, "lastUsedAt": null, "currentVersion": 1, "previewUrl": null, "downloadUrl": "/templates/tmpl_eb3e151f0a094b0abc31c69eb9bb1b6e/download", "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` **Respuesta · 404 · CATALOG_TEMPLATE_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#CATALOG_TEMPLATE_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "No published catalog template was found with slug 'no-existe'.", "instance": "/v3/templates/catalog/no-existe:copy", "code": "CATALOG_TEMPLATE_NOT_FOUND", "requestId": "req_97a848684ace46d8bb72f456f96c237b" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## List template fields GET `/templates/{template_id}/fields` Los campos del formulario de una plantilla PDF: los que trajo el AcroForm al subirla (`source: acroform`), los que dibujaste por API (`drawn`) y los que copiaste de un documento (`template`). Cada campo tiene un `name` estable (el del widget del PDF), un `type` (`text`, `date`, `checkbox`, `radio`, `select`, `signature`, `initials` o `stamp`), sus `areas` (una o varias por página, en % del papel) y el `role` que lo llena — o `null` si nadie lo ha reclamado. `unassignedCount` dice cuántos siguen sin rol; mientras haya uno, la plantilla queda en `readiness: "pending"`. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada (`object: "list"`, `hasMore` siempre `false`) con todos los campos. Ver los 7 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "list" Siempre `"list"`. - `templateId` string requerido La plantilla dueña de los campos (`tmpl_…`). - `pageCount` integer requerido Páginas del PDF de la plantilla. mín. 0 - `data` array de objetos requerido Campos de la plantilla. Ver 15 atributos hijos - `data[].object` string, siempre "template\_field" Siempre `"template_field"`. - `data[].name` string requerido Nombre del campo (clave estable, igual al widget del PDF). - `data[].type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `data[].role` string puede ser null Rol dueño del campo, o `null` si nadie lo ha reclamado. - `data[].required` boolean Si el firmante debe llenarlo antes de firmar. default true - `data[].label` string puede ser null Etiqueta que ve el firmante. - `data[].options` array de string puede ser null Opciones de `select` / `radio`. - `data[].group` string puede ser null Grupo de exclusión mutua (`radio`). - `data[].source` enum puede ser null De dónde nació el campo. Valores: `acroform` `drawn` `template` - `data[].value` string puede ser null Valor que el emisor dejó puesto, o `null`. En casillas, `"true"` o `"false"`. - `data[].readOnly` boolean Si el valor queda fijo: el firmante lo ve y no lo puede cambiar. Sin `value` siempre es `false`. default false - `data[].fixedWidth` boolean Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra (mínimo 6 pt). default false - `data[].placeholder` string puede ser null Texto de ejemplo que ve el firmante en el campo vacío, o `null` si no tiene. - `data[].maxLength` integer puede ser null Cuántos caracteres admite el campo, o `null` si no tiene límite. - `data[].areas` array de objetos requerido Huecos donde aparece el campo (uno o varios). Ver 2 atributos hijos - `data[].areas[].page` integer requerido Página 1-based. mín. 1 - `data[].areas[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `data[].areas[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].areas[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].areas[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].areas[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `unassignedCount` integer requerido Cuántos campos no tienen rol todavía. mín. 0 - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "list", "templateId": "tmpl_2ef548a7f8414f9abf8e0d75ca9a71df", "pageCount": 1, "data": [ { "object": "template_field", "name": "nombre", "type": "text", "role": null, "required": true, "label": "Nombre", "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 10.101, "width": 40.8497, "height": 3.0303 } } ] }, { "object": "template_field", "name": "rfc", "type": "text", "role": null, "required": true, "label": "Rfc", "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 15.1515, "width": 40.8497, "height": 3.0303 } } ] }, { "object": "template_field", "name": "fecha", "type": "date", "role": null, "required": true, "label": "Fecha", "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 20.202, "width": 40.8497, "height": 3.0303 } } ] }, { "object": "template_field", "name": "acepta", "type": "checkbox", "role": null, "required": false, "label": "Acepta", "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 25.2525, "width": 3.5948, "height": 2.7778 } } ] }, { "object": "template_field", "name": "firma_cliente", "type": "signature", "role": "Cliente", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 39.1414, "width": 40.8497, "height": 7.5758 } } ] } ], "unassignedCount": 4, "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## Create template field POST `/templates/{template_id}/fields` Dibuja un campo nuevo sobre el PDF de la plantilla. Indica `type`, `name` (único en la plantilla) y al menos un área `{page, rect}`; opcionalmente el `role` que lo llenará (se crea si no existe), `required`, `label` para el firmante, `options` para `select`/`radio` y `group` para casillas excluyentes. Nace con `source: "drawn"`. #### Parámetros - `template_id` string ruta requerido - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string requerido Nombre del campo. de 1 a 120 caracteres - `type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `role` string puede ser null Rol dueño (se crea si no existe). máx. 120 caracteres - `required` boolean Si es obligatorio. default true - `label` string puede ser null Etiqueta para el firmante. máx. 200 caracteres - `options` array de string puede ser null Opciones de `select` / `radio`. - `group` string puede ser null Grupo de exclusión mutua. máx. 120 caracteres - `value` string puede ser null Valor que ya va puesto. En casillas, `"true"` o `"false"`. máx. 4000 caracteres - `readOnly` boolean Deja el valor fijo. Se ignora si no mandas `value`. default false - `fixedWidth` boolean puede ser null Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra. Solo `text`, `select` y `date`. - `placeholder` string puede ser null Texto de ejemplo para el firmante. Solo campos de texto (`text` y `select`); en otros tipos se ignora. máx. 200 caracteres - `maxLength` integer puede ser null Cuántos caracteres admite el campo (1 a 500). Solo campos de texto (`text` y `select`); en otros tipos se ignora. entre 1 y 500 - `areas` array de objetos requerido Huecos del campo en % del papel. mín. 1 elementos Ver 2 atributos hijos - `areas[].page` integer requerido Página 1-based. mín. 1 - `areas[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `areas[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `areas[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `areas[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `areas[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 #### Devuelve **201** El campo creado, con sus áreas normalizadas. Ver los 15 atributos de la respuesta - `object` string, siempre "template\_field" Siempre `"template_field"`. - `name` string requerido Nombre del campo (clave estable, igual al widget del PDF). - `type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `role` string puede ser null Rol dueño del campo, o `null` si nadie lo ha reclamado. - `required` boolean Si el firmante debe llenarlo antes de firmar. default true - `label` string puede ser null Etiqueta que ve el firmante. - `options` array de string puede ser null Opciones de `select` / `radio`. - `group` string puede ser null Grupo de exclusión mutua (`radio`). - `source` enum puede ser null De dónde nació el campo. Valores: `acroform` `drawn` `template` - `value` string puede ser null Valor que el emisor dejó puesto, o `null`. En casillas, `"true"` o `"false"`. - `readOnly` boolean Si el valor queda fijo: el firmante lo ve y no lo puede cambiar. Sin `value` siempre es `false`. default false - `fixedWidth` boolean Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra (mínimo 6 pt). default false - `placeholder` string puede ser null Texto de ejemplo que ve el firmante en el campo vacío, o `null` si no tiene. - `maxLength` integer puede ser null Cuántos caracteres admite el campo, o `null` si no tiene límite. - `areas` array de objetos requerido Huecos donde aparece el campo (uno o varios). Ver 2 atributos hijos - `areas[].page` integer requerido Página 1-based. mín. 1 - `areas[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `areas[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `areas[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `areas[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `areas[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "telefono", "type": "text", "areas": [ { "page": 1, "rect": { "x": 10, "y": 80, "width": 30, "height": 4 } } ], "role": "Cliente", "required": false }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'telefono', type: 'text', areas: [ { page: 1, rect: { x: 10, y: 80, width: 30, height: 4, }, }, ], role: 'Cliente', required: false, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "telefono", "type": "text", "areas": [ { "page": 1, "rect": { "x": 10, "y": 80, "width": 30, "height": 4, }, }, ], "role": "Cliente", "required": False, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "object": "template_field", "name": "telefono", "type": "text", "role": "Cliente", "required": false, "label": null, "options": null, "group": null, "source": "drawn", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 10, "y": 80, "width": 30, "height": 4 } } ] } ``` **Respuesta · 409 · FIELD_CONFLICT** ``` { "type": "https://allsign.io/developers/docs/errors#FIELD_CONFLICT", "title": "Conflict", "status": 409, "detail": "A field named 'nombre' already exists in this template.", "instance": "/v3/templates/tmpl_4a870c692ac14e2d81be7f9c134d527a/fields", "code": "FIELD_CONFLICT", "requestId": "req_c1f5a7de743a4edb8e6969784838bc7e", "errors": [ { "field": "name", "code": "DUPLICATE", "detail": "Field name already exists." } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## Update template field PATCH `/templates/{template_id}/fields/{name}` Cambia una o varias cosas de un campo por su `name`: moverlo o redistribuir sus `areas`, renombrarlo (`name`, renombra todas sus áreas), cambiar su `type`, asignarle un `role` (o `null` para dejarlo sin rol), `required`, `label`, `options` o `group`. Es la llamada con la que asignas los campos importados del PDF a un rol antes de emitir. #### Parámetros - `template_id` string ruta requerido - `name` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string puede ser null Nuevo nombre (renombra todas sus áreas). de 1 a 120 caracteres - `type` enum puede ser null Nuevo tipo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `role` string puede ser null Nuevo rol (`null` lo deja sin rol). máx. 120 caracteres - `required` boolean puede ser null Si es obligatorio. - `label` string puede ser null Etiqueta para el firmante. máx. 200 caracteres - `options` array de string puede ser null Opciones de `select` / `radio`. - `group` string puede ser null Grupo de exclusión mutua. máx. 120 caracteres - `value` string puede ser null Nuevo valor (`null` lo vacía y suelta el candado). máx. 4000 caracteres - `readOnly` boolean puede ser null Deja el valor fijo. Se ignora si el campo queda sin `value`. - `fixedWidth` boolean puede ser null Ancho fijo (`true`); `false` o `null` lo quita. Solo `text`, `select` y `date`. - `placeholder` string puede ser null Texto de ejemplo, solo en campos de texto (`text` y `select`). Vacío o `null` lo quita. máx. 200 caracteres - `maxLength` integer puede ser null Cuántos caracteres admite (1 a 500), solo en campos de texto (`text` y `select`). `null` quita el límite. entre 1 y 500 - `areas` array de objetos puede ser null Reemplaza todos los huecos. mín. 1 elementos Ver 2 atributos hijos - `areas[].page` integer requerido Página 1-based. mín. 1 - `areas[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `areas[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `areas[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `areas[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `areas[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 #### Devuelve **200** El campo ya actualizado. Ver los 15 atributos de la respuesta - `object` string, siempre "template\_field" Siempre `"template_field"`. - `name` string requerido Nombre del campo (clave estable, igual al widget del PDF). - `type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `role` string puede ser null Rol dueño del campo, o `null` si nadie lo ha reclamado. - `required` boolean Si el firmante debe llenarlo antes de firmar. default true - `label` string puede ser null Etiqueta que ve el firmante. - `options` array de string puede ser null Opciones de `select` / `radio`. - `group` string puede ser null Grupo de exclusión mutua (`radio`). - `source` enum puede ser null De dónde nació el campo. Valores: `acroform` `drawn` `template` - `value` string puede ser null Valor que el emisor dejó puesto, o `null`. En casillas, `"true"` o `"false"`. - `readOnly` boolean Si el valor queda fijo: el firmante lo ve y no lo puede cambiar. Sin `value` siempre es `false`. default false - `fixedWidth` boolean Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra (mínimo 6 pt). default false - `placeholder` string puede ser null Texto de ejemplo que ve el firmante en el campo vacío, o `null` si no tiene. - `maxLength` integer puede ser null Cuántos caracteres admite el campo, o `null` si no tiene límite. - `areas` array de objetos requerido Huecos donde aparece el campo (uno o varios). Ver 2 atributos hijos - `areas[].page` integer requerido Página 1-based. mín. 1 - `areas[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `areas[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `areas[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `areas[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `areas[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X PATCH 'https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields/nombre' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "role": "Cliente", "required": true }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields/nombre', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ role: 'Cliente', required: true, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.patch( "https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields/nombre", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "role": "Cliente", "required": True, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "template_field", "name": "nombre", "type": "text", "role": "Cliente", "required": true, "label": "Nombre", "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 10.101, "width": 40.8497, "height": 3.0303 } } ] } ``` **Respuesta · 404 · FIELD_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#FIELD_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Field 'telefono_movil' not found in this template.", "instance": "/v3/templates/tmpl_24c55a589976432da1a168e3c4859ce6/fields/telefono_movil", "code": "FIELD_NOT_FOUND", "requestId": "req_06aa3c8f7ed843d0afa10acac48240e5" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## Delete template field DELETE `/templates/{template_id}/fields/{name}` Quita el campo y todas sus áreas de la plantilla. Los documentos ya emitidos no cambian. #### Parámetros - `template_id` string ruta requerido - `name` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **204** Campo eliminado. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X DELETE 'https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields/rfc' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields/rfc', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status); ``` **Python** ``` import os import requests respuesta = requests.delete( "https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields/rfc", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code) ``` **Respuesta · 204** *Sin cuerpo.* **Respuesta · 404 · FIELD_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#FIELD_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Field 'rfc' not found in this template.", "instance": "/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields/rfc", "code": "FIELD_NOT_FOUND", "requestId": "req_dc07200411324e03b6bbce3cca2ea6de" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## Replace template fields PUT `/templates/{template_id}/fields` Reemplaza el layout completo de una vez (declarativo e idempotente): mandas la lista final de campos y AllSign crea, mueve, reasigna y borra lo que haga falta para que la plantilla quede exactamente así. Útil para versionar el layout en tu repositorio o para sincronizarlo desde tu propio editor. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `fields` array de objetos requerido Layout completo; reemplaza el anterior. Ver 13 atributos hijos - `fields[].name` string requerido Nombre del campo. de 1 a 120 caracteres - `fields[].type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `fields[].role` string puede ser null Rol dueño (se crea si no existe). máx. 120 caracteres - `fields[].required` boolean Si es obligatorio. default true - `fields[].label` string puede ser null Etiqueta para el firmante. máx. 200 caracteres - `fields[].options` array de string puede ser null Opciones de `select` / `radio`. - `fields[].group` string puede ser null Grupo de exclusión mutua. máx. 120 caracteres - `fields[].value` string puede ser null Valor que ya va puesto. En casillas, `"true"` o `"false"`. máx. 4000 caracteres - `fields[].readOnly` boolean Deja el valor fijo. Se ignora si no mandas `value`. default false - `fields[].fixedWidth` boolean puede ser null Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra. Solo `text`, `select` y `date`. - `fields[].placeholder` string puede ser null Texto de ejemplo para el firmante. Solo campos de texto (`text` y `select`); en otros tipos se ignora. máx. 200 caracteres - `fields[].maxLength` integer puede ser null Cuántos caracteres admite el campo (1 a 500). Solo campos de texto (`text` y `select`); en otros tipos se ignora. entre 1 y 500 - `fields[].areas` array de objetos requerido Huecos del campo en % del papel. mín. 1 elementos Ver 2 atributos hijos - `fields[].areas[].page` integer requerido Página 1-based. mín. 1 - `fields[].areas[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `fields[].areas[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `fields[].areas[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `fields[].areas[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `fields[].areas[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 #### Devuelve **200** La lista completa de campos tal como quedó. Ver los 7 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "list" Siempre `"list"`. - `templateId` string requerido La plantilla dueña de los campos (`tmpl_…`). - `pageCount` integer requerido Páginas del PDF de la plantilla. mín. 0 - `data` array de objetos requerido Campos de la plantilla. Ver 15 atributos hijos - `data[].object` string, siempre "template\_field" Siempre `"template_field"`. - `data[].name` string requerido Nombre del campo (clave estable, igual al widget del PDF). - `data[].type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `data[].role` string puede ser null Rol dueño del campo, o `null` si nadie lo ha reclamado. - `data[].required` boolean Si el firmante debe llenarlo antes de firmar. default true - `data[].label` string puede ser null Etiqueta que ve el firmante. - `data[].options` array de string puede ser null Opciones de `select` / `radio`. - `data[].group` string puede ser null Grupo de exclusión mutua (`radio`). - `data[].source` enum puede ser null De dónde nació el campo. Valores: `acroform` `drawn` `template` - `data[].value` string puede ser null Valor que el emisor dejó puesto, o `null`. En casillas, `"true"` o `"false"`. - `data[].readOnly` boolean Si el valor queda fijo: el firmante lo ve y no lo puede cambiar. Sin `value` siempre es `false`. default false - `data[].fixedWidth` boolean Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra (mínimo 6 pt). default false - `data[].placeholder` string puede ser null Texto de ejemplo que ve el firmante en el campo vacío, o `null` si no tiene. - `data[].maxLength` integer puede ser null Cuántos caracteres admite el campo, o `null` si no tiene límite. - `data[].areas` array de objetos requerido Huecos donde aparece el campo (uno o varios). Ver 2 atributos hijos - `data[].areas[].page` integer requerido Página 1-based. mín. 1 - `data[].areas[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `data[].areas[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].areas[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].areas[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].areas[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `unassignedCount` integer requerido Cuántos campos no tienen rol todavía. mín. 0 - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X PUT 'https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "fields": [ { "name": "nombre", "type": "text", "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 10.101, "width": 40.8497, "height": 3.0303 } } ], "role": "Cliente", "required": true }, { "name": "rfc", "type": "text", "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 15.1515, "width": 40.8497, "height": 3.0303 } } ], "role": null, "required": true }, { "name": "fecha", "type": "date", "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 20.202, "width": 40.8497, "height": 3.0303 } } ], "role": null, "required": true }, { "name": "acepta", "type": "checkbox", "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 25.2525, "width": 3.5948, "height": 2.7778 } } ], "role": null, "required": false }, { "name": "firma_cliente", "type": "signature", "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 39.1414, "width": 40.8497, "height": 7.5758 } } ], "role": "Cliente", "required": true } ] }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ fields: [ { name: 'nombre', type: 'text', areas: [ { page: 1, rect: { x: 8.1699, y: 10.101, width: 40.8497, height: 3.0303, }, }, ], role: 'Cliente', required: true, }, { name: 'rfc', type: 'text', areas: [ { page: 1, rect: { x: 8.1699, y: 15.1515, width: 40.8497, height: 3.0303, }, }, ], role: null, required: true, }, { name: 'fecha', type: 'date', areas: [ { page: 1, rect: { x: 8.1699, y: 20.202, width: 40.8497, height: 3.0303, }, }, ], role: null, required: true, }, { name: 'acepta', type: 'checkbox', areas: [ { page: 1, rect: { x: 8.1699, y: 25.2525, width: 3.5948, height: 2.7778, }, }, ], role: null, required: false, }, { name: 'firma_cliente', type: 'signature', areas: [ { page: 1, rect: { x: 8.1699, y: 39.1414, width: 40.8497, height: 7.5758, }, }, ], role: 'Cliente', required: true, }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.put( "https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/fields", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "fields": [ { "name": "nombre", "type": "text", "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 10.101, "width": 40.8497, "height": 3.0303, }, }, ], "role": "Cliente", "required": True, }, { "name": "rfc", "type": "text", "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 15.1515, "width": 40.8497, "height": 3.0303, }, }, ], "role": None, "required": True, }, { "name": "fecha", "type": "date", "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 20.202, "width": 40.8497, "height": 3.0303, }, }, ], "role": None, "required": True, }, { "name": "acepta", "type": "checkbox", "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 25.2525, "width": 3.5948, "height": 2.7778, }, }, ], "role": None, "required": False, }, { "name": "firma_cliente", "type": "signature", "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 39.1414, "width": 40.8497, "height": 7.5758, }, }, ], "role": "Cliente", "required": True, }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "list", "templateId": "tmpl_2ef548a7f8414f9abf8e0d75ca9a71df", "pageCount": 1, "data": [ { "object": "template_field", "name": "nombre", "type": "text", "role": "Cliente", "required": true, "label": null, "options": null, "group": null, "source": "drawn", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 10.101, "width": 40.8497, "height": 3.0303 } } ] }, { "object": "template_field", "name": "rfc", "type": "text", "role": null, "required": true, "label": null, "options": null, "group": null, "source": "drawn", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 15.1515, "width": 40.8497, "height": 3.0303 } } ] }, { "object": "template_field", "name": "fecha", "type": "date", "role": null, "required": true, "label": null, "options": null, "group": null, "source": "drawn", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 20.202, "width": 40.8497, "height": 3.0303 } } ] }, { "object": "template_field", "name": "acepta", "type": "checkbox", "role": null, "required": false, "label": null, "options": null, "group": null, "source": "drawn", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 25.2525, "width": 3.5948, "height": 2.7778 } } ] }, { "object": "template_field", "name": "firma_cliente", "type": "signature", "role": "Cliente", "required": true, "label": null, "options": null, "group": null, "source": "drawn", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 39.1414, "width": 40.8497, "height": 7.5758 } } ] } ], "unassignedCount": 3, "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## Import fields from a document POST `/templates/{template_id}/fields:from-document` Copia los campos y los roles de un documento a la plantilla: es el "guardar como plantilla" del super editor, por API. Los firmantes del documento **no** viajan — la plantilla guarda estructura (campos + roles), nunca personas. Tampoco viajan los datos: de cada campo se copian nombre, tipo, rol, posición (convertida a % de la hoja), requerido, etiqueta, opciones y grupo, **nunca** el valor que tenía en el documento origen ni su candado (`readOnly`). Los campos existentes de la plantilla se reemplazan por los del documento. #### Parámetros - `template_id` string ruta requerido - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `documentId` string requerido Documento (`doc_…`) del que se copian campos y roles. #### Devuelve **200** La lista de campos de la plantilla después de la importación. Ver los 7 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "list" Siempre `"list"`. - `templateId` string requerido La plantilla dueña de los campos (`tmpl_…`). - `pageCount` integer requerido Páginas del PDF de la plantilla. mín. 0 - `data` array de objetos requerido Campos de la plantilla. Ver 15 atributos hijos - `data[].object` string, siempre "template\_field" Siempre `"template_field"`. - `data[].name` string requerido Nombre del campo (clave estable, igual al widget del PDF). - `data[].type` enum requerido Tipo de campo. Valores: `text` `date` `checkbox` `radio` `select` `signature` `initials` `stamp` - `data[].role` string puede ser null Rol dueño del campo, o `null` si nadie lo ha reclamado. - `data[].required` boolean Si el firmante debe llenarlo antes de firmar. default true - `data[].label` string puede ser null Etiqueta que ve el firmante. - `data[].options` array de string puede ser null Opciones de `select` / `radio`. - `data[].group` string puede ser null Grupo de exclusión mutua (`radio`). - `data[].source` enum puede ser null De dónde nació el campo. Valores: `acroform` `drawn` `template` - `data[].value` string puede ser null Valor que el emisor dejó puesto, o `null`. En casillas, `"true"` o `"false"`. - `data[].readOnly` boolean Si el valor queda fijo: el firmante lo ve y no lo puede cambiar. Sin `value` siempre es `false`. default false - `data[].fixedWidth` boolean Ancho fijo: si el texto no cabe, el campo no se alarga y se encoge la letra (mínimo 6 pt). default false - `data[].placeholder` string puede ser null Texto de ejemplo que ve el firmante en el campo vacío, o `null` si no tiene. - `data[].maxLength` integer puede ser null Cuántos caracteres admite el campo, o `null` si no tiene límite. - `data[].areas` array de objetos requerido Huecos donde aparece el campo (uno o varios). Ver 2 atributos hijos - `data[].areas[].page` integer requerido Página 1-based. mín. 1 - `data[].areas[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `data[].areas[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].areas[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].areas[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].areas[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `unassignedCount` integer requerido Cuántos campos no tienen rol todavía. mín. 0 - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/fields:from-document' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "documentId": "doc_c9b39590d09645c9b9ebc28f4b3a5349" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/fields:from-document', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ documentId: 'doc_c9b39590d09645c9b9ebc28f4b3a5349', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/fields:from-document", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "documentId": "doc_c9b39590d09645c9b9ebc28f4b3a5349", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "list", "templateId": "tmpl_c405a9544678416a9b2e29dfa4a7f331", "pageCount": 1, "data": [ { "object": "template_field", "name": "signature_24f9dec1", "type": "signature", "role": "Cliente", "required": true, "label": null, "options": null, "group": null, "source": "drawn", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 13.0719, "y": 78.2828, "width": 32.6797, "height": 7.5758 } } ] } ], "unassignedCount": 0, "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## List template roles GET `/templates/{template_id}/roles` Los roles de la plantilla en orden, con cuántos campos tiene cada uno. Al crear un documento desde la plantilla, cada firmante toma un rol por `roleName` y hereda todos sus campos. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada con los roles en orden. Ver los 5 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "list" Siempre `"list"`. - `templateId` string requerido La plantilla dueña de los roles (`tmpl_…`). - `data` array de objetos requerido Roles de la plantilla, en orden. Ver 4 atributos hijos - `data[].object` string, siempre "template\_role" Siempre `"template_role"`. - `data[].name` string requerido Nombre del rol (ej. `Cliente`). - `data[].order` integer requerido Orden de presentación, 0-based. mín. 0 - `data[].fieldCount` integer requerido Cuántos campos tiene asignados. mín. 0 - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/roles' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/roles', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/roles", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "list", "templateId": "tmpl_2ef548a7f8414f9abf8e0d75ca9a71df", "data": [ { "object": "template_role", "name": "Cliente", "order": 0, "fieldCount": 1 } ], "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## Replace template roles PUT `/templates/{template_id}/roles` Fija la lista de roles (nombre y orden). Los roles que ya no estén en la lista se quitan y sus campos quedan sin rol (`unassignedCount` sube); los nombres nuevos se crean vacíos, listos para asignarles campos con `PATCH …/fields/{name}`. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `roles` array de string requerido Nombres de rol en orden; los que falten se quitan (sus campos quedan sin rol). #### Devuelve **200** Los roles tal como quedaron. Ver los 5 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "list" Siempre `"list"`. - `templateId` string requerido La plantilla dueña de los roles (`tmpl_…`). - `data` array de objetos requerido Roles de la plantilla, en orden. Ver 4 atributos hijos - `data[].object` string, siempre "template\_role" Siempre `"template_role"`. - `data[].name` string requerido Nombre del rol (ej. `Cliente`). - `data[].order` integer requerido Orden de presentación, 0-based. mín. 0 - `data[].fieldCount` integer requerido Cuántos campos tiene asignados. mín. 0 - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X PUT 'https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/roles' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "roles": [ "Cliente" ] }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/roles', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ roles: [ 'Cliente', ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.put( "https://api.allsign.io/v3/templates/tmpl_2ef548a7f8414f9abf8e0d75ca9a71df/roles", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "roles": [ "Cliente", ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "list", "templateId": "tmpl_2ef548a7f8414f9abf8e0d75ca9a71df", "data": [ { "object": "template_role", "name": "Cliente", "order": 0, "fieldCount": 1 } ], "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/campos.ejemplos.spec.ts` ## Download template file GET `/templates/{template_id}/file` El PDF de la plantilla. Con `format=form` conserva los widgets del formulario **vivos** (para revisarlo en Acrobat o en tu propio visor); con `format=flat` los hornea (aplanado, sin campos editables). Solo plantillas PDF: una plantilla DOCX responde 422. #### Parámetros - `template_id` string ruta requerido - `format` enum query `form` (default) conserva los widgets; `flat` los hornea. Valores: `form` `flat` default "form" - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El PDF (`application/pdf`, `Content-Disposition: attachment`). Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/file?format=flat' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` import { writeFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/file?format=flat', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); writeFileSync('descarga.pdf', Buffer.from(await respuesta.arrayBuffer())); ``` **Python** ``` import os from pathlib import Path import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/file?format=flat", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) Path("descarga.pdf").write_bytes(respuesta.content) ``` **Respuesta · 200** ``` ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Replace a PDF template's file POST `/templates/{template_id}/file` Sustituye el PDF de la plantilla —una cláusula nueva, otro logo— **sin rehacer los campos que ya colocaste**: sus áreas se guardan en porcentaje de página, así que un archivo con otro tamaño de hoja no las mueve. Lo único que sí puede romperse es el **número de páginas**: un campo que vivía en una página que el archivo nuevo ya no tiene se poda —se borra esa área, nunca la plantilla ni el resto de sus áreas— y la respuesta lo dice en `prunedFields`, con el nombre del campo, las páginas que perdió y `removed` en `true` si se quedó sin ninguna. Revísalo siempre después de reemplazar. Solo plantillas PDF (`fileType: "pdf"`) y el reemplazo también debe ser PDF: este endpoint no convierte DOCX↔PDF. El archivo viaja en base64, máximo 10 MB decodificados, igual que al crear la plantilla. #### Parámetros - `template_id` string ruta requerido ID de la plantilla PDF cuyo archivo se reemplaza (`tmpl_…`). - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `file` objeto requerido El PDF nuevo en base64. Máximo 10 MB decodificados. Ver 3 atributos hijos - `file.content` string requerido Contenido del archivo codificado en Base64. Máximo 10 MB decodificados. - `file.fileType` enum Tipo de archivo fuente: `docx` o `pdf`. Valores: `docx` `pdf` default "docx" - `file.name` string puede ser null Nombre del archivo (ej. `contrato.pdf`). Se valida contra path traversal. #### Devuelve **200** La plantilla con el archivo ya reemplazado, las páginas del archivo nuevo y `prunedFields`. Ver los 5 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "template\_file\_replace" Siempre `"template_file_replace"`. - `template` objeto requerido La plantilla con el archivo ya reemplazado. Ver 23 atributos hijos - `template.livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `template.id` string requerido ID de la plantilla (`tmpl_…`). - `template.object` string, siempre "template" Siempre `"template"`. - `template.name` string requerido Nombre de la plantilla. - `template.description` string puede ser null Descripción de la plantilla. - `template.fileType` string requerido Tipo de archivo fuente (ej. `docx`, `pdf`). - `template.originalFilename` string puede ser null Nombre del archivo original subido. - `template.aiEditable` boolean Si la edición asistida por IA está habilitada. default true - `template.variableCount` integer requerido Número de variables detectadas en la plantilla. - `template.fieldCount` integer Número de campos del formulario PDF (nombres distintos). default 0 - `template.unassignedFieldCount` integer Campos del formulario sin rol; `readiness` es `pending` mientras haya alguno. default 0 - `template.roleCount` integer Número de roles de la plantilla. default 0 - `template.pendingCandidates` integer Candidatos del análisis vigente que aún no se confirmaron como variable (`0` si no hay análisis). default 0 - `template.readiness` enum requerido `ready` (≥1 variable y 0 pendientes, o ≥1 campo y todos con rol), `pending` (≥1 candidato sin confirmar o ≥1 campo sin rol) o `empty` (0 variables y 0 campos). Valores: `ready` `pending` `empty` - `template.tags` array de string Etiquetas de la plantilla (arreglo vacío por default). default \[\] - `template.category` string puede ser null Categoría de la plantilla. - `template.usageCount` integer requerido Cuántas veces se ha usado la plantilla. - `template.lastUsedAt` string (fecha-hora ISO 8601) puede ser null Último uso (ISO 8601). - `template.currentVersion` integer requerido Versión actual de la plantilla. - `template.previewUrl` string puede ser null Ruta v3 relativa (`/templates/{id}/preview`) del PNG de la primera página. `null` si la plantilla no tiene una fuente renderizable (p.ej. DOCX). - `template.downloadUrl` string requerido Ruta v3 relativa (`/templates/{id}/download`) del archivo original. - `template.createdAt` string (fecha-hora ISO 8601) requerido Fecha de creación (ISO 8601). - `template.updatedAt` string (fecha-hora ISO 8601) requerido Última actualización (ISO 8601). - `pageCount` integer requerido Páginas del archivo nuevo. - `prunedFields` array de objetos Campos que perdieron área(s) al reemplazar el archivo. Ver 3 atributos hijos - `prunedFields[].name` string requerido Nombre del campo. - `prunedFields[].pages` array de integer requerido Páginas que ocupaba y que el archivo nuevo ya no tiene. - `prunedFields[].removed` boolean requerido `true` si el campo se eliminó por completo (ninguna de sus áreas cupo en el archivo nuevo); `false` si sólo perdió algunas de varias áreas. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [413](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/file' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "file": { "content": "'"$(base64 < formulario-v2.pdf | tr -d '\n')"'", "fileType": "pdf", "name": "formulario-v2.pdf" } }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/file', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ file: { content: readFileSync('formulario-v2.pdf').toString('base64'), fileType: 'pdf', name: 'formulario-v2.pdf', }, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/file", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "file": { "content": base64.b64encode(Path("formulario-v2.pdf").read_bytes()).decode(), "fileType": "pdf", "name": "formulario-v2.pdf", }, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "template_file_replace", "template": { "livemode": false, "id": "tmpl_c405a9544678416a9b2e29dfa4a7f331", "object": "template", "name": "v3audit-ejemplos Formulario PDF", "description": null, "fileType": "pdf", "originalFilename": "formulario-v2.pdf", "aiEditable": true, "variableCount": 0, "fieldCount": 4, "unassignedFieldCount": 3, "roleCount": 1, "pendingCandidates": 0, "readiness": "pending", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 2, "previewUrl": "/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/preview", "downloadUrl": "/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/download", "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:10.214000Z" }, "pageCount": 1, "prunedFields": [] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Download original file GET `/templates/{template_id}/download` El archivo original tal como se subió (PDF o DOCX), sin marcar ni convertir. Se transmite directo, nunca por redirect. Para el PDF con widgets o aplanado usa `GET …/file?format=form|flat`. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El archivo (`Content-Disposition: attachment`). Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/download' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` import { writeFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/download', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); writeFileSync('descarga.docx', Buffer.from(await respuesta.arrayBuffer())); ``` **Python** ``` import os from pathlib import Path import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/download", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) Path("descarga.docx").write_bytes(respuesta.content) ``` **Respuesta · 200** ``` ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Get template preview GET `/templates/{template_id}/preview` PNG de la primera página, para mostrar la plantilla en tu propia lista. Solo plantillas PDF; un DOCX responde 404 `PREVIEW_UNAVAILABLE`. `width` reduce la imagen (nunca la agranda). #### Parámetros - `template_id` string ruta requerido - `width` integer query Ancho máximo en píxeles; la imagen se reduce, nunca se agranda. default 400 · entre 200 y 1200 - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** La imagen (`image/png`). Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/preview?width=400' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` import { writeFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/preview?width=400', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); writeFileSync('vista.png', Buffer.from(await respuesta.arrayBuffer())); ``` **Python** ``` import os from pathlib import Path import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/preview?width=400", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) Path("vista.png").write_bytes(respuesta.content) ``` **Respuesta · 200** ``` ``` **Respuesta · 404 · PREVIEW_UNAVAILABLE** ``` { "type": "https://allsign.io/developers/docs/errors#PREVIEW_UNAVAILABLE", "title": "Not Found", "status": 404, "detail": "This template has no renderable page image.", "instance": "/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/preview", "code": "PREVIEW_UNAVAILABLE", "requestId": "req_b9de17df65f64abb9601e36d7b31625a" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Get template page image GET `/templates/{template_id}/pages/{page_number}` Imagen limpia de una página del PDF (sin widgets ni anotaciones), para pintar tu propio editor de campos encima; las coordenadas de `areas` son porcentajes de esta página. #### Parámetros - `template_id` string ruta requerido - `page_number` integer ruta requerido mín. 1 - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** La imagen (`image/png`). Ver los 7 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "template\_page" Siempre `"template_page"`. - `templateId` string requerido La plantilla (`tmpl_…`). - `pageNumber` integer requerido Página 1-based. mín. 1 - `imageUrl` string requerido URL firmada de la imagen PNG de la página, con los widgets vacíos. - `width` integer requerido Ancho de la imagen en píxeles. mín. 0 - `height` integer requerido Alto de la imagen en píxeles. mín. 0 Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/pages/1' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/pages/1', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/pages/1", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "template_page", "templateId": "tmpl_c405a9544678416a9b2e29dfa4a7f331", "pageNumber": 1, "imageUrl": "", "width": 816, "height": 1056 } ``` **Respuesta · 422 · VALIDATION_ERROR** ``` { "type": "https://allsign.io/developers/docs/errors#VALIDATION_ERROR", "title": "Validation failed", "status": 422, "detail": "Page images are only available for PDF templates.", "instance": "/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/pages/1", "code": "VALIDATION_ERROR", "requestId": "req_91a0c07a012a4302a795c2ea00e54da7", "errors": [ { "field": "pageNumber", "code": "UNSUPPORTED_FILE_TYPE", "detail": "DOCX templates do not expose page images." } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Get template layout GET `/templates/{template_id}/layout` Vista de solo lectura del layout completo: páginas con su tamaño y cada campo o variable con su posición. Útil para dibujar el editor; para cambiar campos usa `…/fields`. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El layout por página. Ver los 5 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "template\_layout" Siempre `"template_layout"`. - `templateId` string requerido La plantilla (`tmpl_…`). - `pageCount` integer requerido Número de páginas. mín. 0 - `items` array de objetos Datos, decisiones y firmas posicionados. Ver 3 atributos hijos - `items[].name` string requerido Nombre del dato, decisión o campo de firma. - `items[].kind` enum requerido Tipo de ítem: `data`, `decision` o `signature`. Valores: `data` `decision` `signature` - `items[].slots` array de objetos requerido Huecos en el papel, en % de la página. Ver 2 atributos hijos - `items[].slots[].page` integer requerido Página 1-based. mín. 1 - `items[].slots[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `items[].slots[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `items[].slots[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `items[].slots[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `items[].slots[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/layout' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/layout', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/layout", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "template_layout", "templateId": "tmpl_2a1bc982e2f04c979ff8d8c2a6468539", "pageCount": 0, "items": [] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Download marked Word file GET `/templates/{template_id}/marked.docx` Solo plantillas DOCX: el Word original con `{{nombre}}` en cada dato ya confirmado, conservando los estilos. Sirve para revisar en Word qué detectó el análisis. Un PDF responde 422. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El DOCX marcado (`Content-Disposition: attachment`). Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/marked.docx' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` import { writeFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/marked.docx', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); writeFileSync('descarga.docx', Buffer.from(await respuesta.arrayBuffer())); ``` **Python** ``` import os from pathlib import Path import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/marked.docx", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) Path("descarga.docx").write_bytes(respuesta.content) ``` **Respuesta · 200** ``` ``` **Respuesta · 422 · VALIDATION_ERROR** ``` { "type": "https://allsign.io/developers/docs/errors#VALIDATION_ERROR", "title": "Validation failed", "status": 422, "detail": "Marked Word download is only available for DOCX templates.", "instance": "/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/marked.docx", "code": "VALIDATION_ERROR", "requestId": "req_314cdc1074994765a3deacf1907b0c60", "errors": [ { "field": "fileType", "code": "UNSUPPORTED_FILE_TYPE", "detail": "PDF templates do not expose a marked Word file." } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## List template variables GET `/templates/{template_id}/variables` Lista las variables de una plantilla — justo lo que necesitas para armar el mapa `templateValues` de Create document. Es una colección acotada (no paginada por cursor): el número de variables lo limita el propio archivo. #### Parámetros - `template_id` string ruta requerido ID de la plantilla (`tmpl_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada (`object: "list"`, `hasMore` siempre `false`) con las variables de la plantilla. Ver los 4 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `templateId` string requerido La plantilla a la que pertenecen las variables (`tmpl_…`). - `data` array de objetos requerido Arreglo de variables de la plantilla. Ver 14 atributos hijos - `data[].name` string requerido Nombre de la variable — es la llave que usas en `templateValues`. Se mantiene en `snake_case` natural, nunca se cameliza. - `data[].label` string requerido Etiqueta legible para mostrar. - `data[].type` string Tipo de dato (token opaco `snake_case`): `text`, `date`, `currency`, `textarea`, `select`, `number`. default "text" - `data[].required` boolean Si la variable es obligatoria. default true - `data[].defaultValue` string puede ser null Valor por default sugerido. - `data[].options` array de string puede ser null Opciones válidas cuando `type` es `select`. - `data[].role` string puede ser null Rol del firmante inferido del prefijo con doble guion bajo, o `null` si la variable no está ligada a un rol. - `data[].source` string puede ser null Origen del dato (token opaco): `form_field`, `jinja`, `signal` o `manual`. - `data[].page` integer puede ser null Página 1-based del hueco principal. mín. 1 - `data[].rect` objeto puede ser null Rectángulo del hueco principal, en % del papel. Ver 4 atributos hijos - `data[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `data[].slots` array de objetos puede ser null Huecos adicionales cuando el mismo dato aparece más de una vez. Ver 2 atributos hijos - `data[].slots[].page` integer requerido Página 1-based. mín. 1 - `data[].slots[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `data[].slots[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].slots[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].slots[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].slots[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `data[].filledBy` string puede ser null Quién llena el dato: `owner` (el remitente) o `null`. Por compatibilidad puede traer `signer` o el nombre de un rol; esos valores ya no se aceptan en el body de crear, editar ni confirmar. - `data[].snippet` string puede ser null Texto exacto del hueco detectado en el análisis (`______`, `[CIUDAD]`), o `null`. - `data[].analysisIndex` integer puede ser null Índice del candidato del análisis del que se confirmó esta variable, o `null` si se creó a mano. mín. 1 - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "templateId": "tmpl_67fb98658fe548d3a71eae28087d7060", "data": [ { "name": "ciudad_celebracion", "label": "Ciudad Celebracion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "fecha_celebracion", "label": "Fecha Celebracion", "type": "date", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "folio_acuerdo", "label": "Folio Acuerdo", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "jurisdiccion", "label": "Jurisdiccion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "monto_pena", "label": "Monto Pena", "type": "currency", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "monto_pena_letra", "label": "Monto Pena Letra", "type": "currency", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "objeto_relacion", "label": "Objeto Relacion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__domicilio", "label": "Domicilio", "type": "textarea", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__email", "label": "Email", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__nombre", "label": "Nombre", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__representante", "label": "Representante", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__rfc", "label": "Rfc", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__telefono", "label": "Telefono", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__domicilio", "label": "Domicilio", "type": "textarea", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__email", "label": "Email", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__nombre", "label": "Nombre", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__representante", "label": "Representante", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__rfc", "label": "Rfc", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__telefono", "label": "Telefono", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "tipo_informacion_confidencial", "label": "Tipo Informacion Confidencial", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "vigencia", "label": "Vigencia", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "vigencia_post_terminacion", "label": "Vigencia Post Terminacion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null } ], "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/documento-desde-plantilla-docx.receta.spec.ts` ## Create template variable POST `/templates/{template_id}/variables` Agrega una variable `{{ }}` a mano a una plantilla DOCX: nombre, etiqueta, tipo y, opcionalmente, su posición (`page`, `rect`, `slots`). `mergeInto` la une a una variable existente como hueco adicional. Todas las variables las llena el remitente: `filledBy` solo acepta `owner`, y `signer` responde `422`. Lo que captura el firmante va en un campo del formulario PDF (guía [Formularios PDF](https://allsign.io/developers/docs/guides/pdf-forms)). #### Parámetros - `template_id` string ruta requerido - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string requerido Nombre de la variable — llave de `templateValues`, en `snake_case` natural. de 1 a 255 caracteres - `label` string puede ser null Etiqueta legible. Si se omite, se deriva del nombre. - `type` string Tipo de dato (token opaco): `text`, `date`, `currency`, `textarea`, `select`, `number`. default "text" - `required` boolean Si la variable es obligatoria. default true - `defaultValue` string puede ser null Valor por default sugerido. - `options` array de string puede ser null Opciones válidas cuando `type` es `select`. - `role` string puede ser null Rol del firmante al que se asigna, o `null`. - `page` integer puede ser null Página 1-based del hueco. mín. 1 - `rect` objeto puede ser null Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `slots` array de objetos puede ser null Huecos adicionales del mismo dato. Ver 2 atributos hijos - `slots[].page` integer requerido Página 1-based. mín. 1 - `slots[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `slots[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `slots[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `slots[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `slots[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `mergeInto` string puede ser null Nombre de una variable existente. Si se envía, este dato se une a ella como `slots`. - `filledBy` enum puede ser null Quién llena el dato: `owner` (el remitente) u omítelo. `signer` responde 422 `SIGNER_VARIABLE_NOT_SUPPORTED`: lo que llena el firmante va en un campo del formulario PDF asignado a su rol (`role` en los campos de la plantilla; `role` o `signer` en los del documento). Valores: `owner` `signer` #### Devuelve **201** La variable creada. Ver los 4 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `templateId` string requerido La plantilla a la que pertenecen las variables (`tmpl_…`). - `data` array de objetos requerido Arreglo de variables de la plantilla. Ver 14 atributos hijos - `data[].name` string requerido Nombre de la variable — es la llave que usas en `templateValues`. Se mantiene en `snake_case` natural, nunca se cameliza. - `data[].label` string requerido Etiqueta legible para mostrar. - `data[].type` string Tipo de dato (token opaco `snake_case`): `text`, `date`, `currency`, `textarea`, `select`, `number`. default "text" - `data[].required` boolean Si la variable es obligatoria. default true - `data[].defaultValue` string puede ser null Valor por default sugerido. - `data[].options` array de string puede ser null Opciones válidas cuando `type` es `select`. - `data[].role` string puede ser null Rol del firmante inferido del prefijo con doble guion bajo, o `null` si la variable no está ligada a un rol. - `data[].source` string puede ser null Origen del dato (token opaco): `form_field`, `jinja`, `signal` o `manual`. - `data[].page` integer puede ser null Página 1-based del hueco principal. mín. 1 - `data[].rect` objeto puede ser null Rectángulo del hueco principal, en % del papel. Ver 4 atributos hijos - `data[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `data[].slots` array de objetos puede ser null Huecos adicionales cuando el mismo dato aparece más de una vez. Ver 2 atributos hijos - `data[].slots[].page` integer requerido Página 1-based. mín. 1 - `data[].slots[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `data[].slots[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].slots[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].slots[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].slots[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `data[].filledBy` string puede ser null Quién llena el dato: `owner` (el remitente) o `null`. Por compatibilidad puede traer `signer` o el nombre de un rol; esos valores ya no se aceptan en el body de crear, editar ni confirmar. - `data[].snippet` string puede ser null Texto exacto del hueco detectado en el análisis (`______`, `[CIUDAD]`), o `null`. - `data[].analysisIndex` integer puede ser null Índice del candidato del análisis del que se confirmó esta variable, o `null` si se creó a mano. mín. 1 - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/variables' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "plazo_meses", "label": "Plazo en meses", "type": "number", "required": false }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/variables', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'plazo_meses', label: 'Plazo en meses', type: 'number', required: false, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/variables", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "plazo_meses", "label": "Plazo en meses", "type": "number", "required": False, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "object": "list", "templateId": "tmpl_2a1bc982e2f04c979ff8d8c2a6468539", "data": [ { "name": "comprador_nombre", "label": "Comprador Nombre", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "comprador_rfc", "label": "Comprador Rfc", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "fecha", "label": "Fecha", "type": "date", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "monto", "label": "Monto", "type": "currency", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "vendedor_nombre", "label": "Vendedor Nombre", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "plazo_meses", "label": "Plazo en meses", "type": "number", "required": false, "defaultValue": null, "options": null, "role": null, "source": "manual", "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null } ], "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Update template variable PATCH `/templates/{template_id}/variables` Cambia una variable por su `name`: renombrarla (`newName`), su etiqueta, tipo o su posición. Los documentos ya creados no cambian. `filledBy` solo acepta `owner`: todas las variables las llena el remitente, y `signer` responde `422`. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string requerido Nombre actual de la variable a modificar. - `newName` string puede ser null Nuevo nombre. Conserva el slot original. - `label` string puede ser null Nueva etiqueta legible. - `type` string puede ser null Nuevo tipo de dato (token opaco). - `required` boolean puede ser null Si la variable es obligatoria. - `defaultValue` string puede ser null Nuevo valor por default. - `options` array de string puede ser null Nuevas opciones cuando `type` es `select`. - `role` string puede ser null Nuevo rol, o `null` para desligarlo. - `page` integer puede ser null Nueva página 1-based del hueco. mín. 1 - `rect` objeto puede ser null Nuevo rectángulo, en % del papel. Ver 4 atributos hijos - `rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `slots` array de objetos puede ser null Reemplaza los huecos adicionales. Ver 2 atributos hijos - `slots[].page` integer requerido Página 1-based. mín. 1 - `slots[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `slots[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `slots[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `slots[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `slots[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `mergeInto` string puede ser null Nombre de otra variable. Si se envía, esta se une a ella como `slots`. - `filledBy` enum puede ser null Quién llena el dato: `owner` (el remitente) u omítelo. `signer` responde 422 `SIGNER_VARIABLE_NOT_SUPPORTED`: lo que llena el firmante va en un campo del formulario PDF asignado a su rol (`role` en los campos de la plantilla; `role` o `signer` en los del documento). Valores: `owner` `signer` #### Devuelve **200** La variable ya actualizada. Ver los 4 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `templateId` string requerido La plantilla a la que pertenecen las variables (`tmpl_…`). - `data` array de objetos requerido Arreglo de variables de la plantilla. Ver 14 atributos hijos - `data[].name` string requerido Nombre de la variable — es la llave que usas en `templateValues`. Se mantiene en `snake_case` natural, nunca se cameliza. - `data[].label` string requerido Etiqueta legible para mostrar. - `data[].type` string Tipo de dato (token opaco `snake_case`): `text`, `date`, `currency`, `textarea`, `select`, `number`. default "text" - `data[].required` boolean Si la variable es obligatoria. default true - `data[].defaultValue` string puede ser null Valor por default sugerido. - `data[].options` array de string puede ser null Opciones válidas cuando `type` es `select`. - `data[].role` string puede ser null Rol del firmante inferido del prefijo con doble guion bajo, o `null` si la variable no está ligada a un rol. - `data[].source` string puede ser null Origen del dato (token opaco): `form_field`, `jinja`, `signal` o `manual`. - `data[].page` integer puede ser null Página 1-based del hueco principal. mín. 1 - `data[].rect` objeto puede ser null Rectángulo del hueco principal, en % del papel. Ver 4 atributos hijos - `data[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `data[].slots` array de objetos puede ser null Huecos adicionales cuando el mismo dato aparece más de una vez. Ver 2 atributos hijos - `data[].slots[].page` integer requerido Página 1-based. mín. 1 - `data[].slots[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `data[].slots[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].slots[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].slots[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].slots[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `data[].filledBy` string puede ser null Quién llena el dato: `owner` (el remitente) o `null`. Por compatibilidad puede traer `signer` o el nombre de un rol; esos valores ya no se aceptan en el body de crear, editar ni confirmar. - `data[].snippet` string puede ser null Texto exacto del hueco detectado en el análisis (`______`, `[CIUDAD]`), o `null`. - `data[].analysisIndex` integer puede ser null Índice del candidato del análisis del que se confirmó esta variable, o `null` si se creó a mano. mín. 1 - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X PATCH 'https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "name": "monto_pena_letra", "type": "text" }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'monto_pena_letra', type: 'text', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.patch( "https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "name": "monto_pena_letra", "type": "text", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "templateId": "tmpl_67fb98658fe548d3a71eae28087d7060", "data": [ { "name": "ciudad_celebracion", "label": "Ciudad Celebracion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "fecha_celebracion", "label": "Fecha Celebracion", "type": "date", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "folio_acuerdo", "label": "Folio Acuerdo", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "jurisdiccion", "label": "Jurisdiccion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "monto_pena", "label": "Monto Pena", "type": "currency", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "monto_pena_letra", "label": "Monto Pena Letra", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "objeto_relacion", "label": "Objeto Relacion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__domicilio", "label": "Domicilio", "type": "textarea", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__email", "label": "Email", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__nombre", "label": "Nombre", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__representante", "label": "Representante", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__rfc", "label": "Rfc", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__telefono", "label": "Telefono", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__domicilio", "label": "Domicilio", "type": "textarea", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__email", "label": "Email", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__nombre", "label": "Nombre", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__representante", "label": "Representante", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__rfc", "label": "Rfc", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__telefono", "label": "Telefono", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "tipo_informacion_confidencial", "label": "Tipo Informacion Confidencial", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "vigencia", "label": "Vigencia", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "vigencia_post_terminacion", "label": "Vigencia Post Terminacion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null } ], "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/documento-desde-plantilla-docx.receta.spec.ts` ## Delete template variable DELETE `/templates/{template_id}/variables` Quita una variable de la plantilla (por `name`). Los documentos ya creados conservan sus valores. #### Parámetros - `template_id` string ruta requerido - `name` string query requerido Nombre de la variable a quitar. mín. 1 caracteres - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **204** Variable eliminada. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X DELETE 'https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/variables?name=plazo_meses' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/variables?name=plazo_meses', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status); ``` **Python** ``` import os import requests respuesta = requests.delete( "https://api.allsign.io/v3/templates/tmpl_2a1bc982e2f04c979ff8d8c2a6468539/variables?name=plazo_meses", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code) ``` **Respuesta · 204** *Sin cuerpo.* Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Confirm variable candidates POST `/templates/{template_id}/variables:confirm` Convierte candidatos del análisis en variables de la plantilla, con nombre final y etiqueta. Todas las variables las llena el remitente: `filledBy` solo acepta `owner`, y `signer` responde `422`. Lo que captura el firmante va en un campo del formulario PDF (guía [Formularios PDF](https://allsign.io/developers/docs/guides/pdf-forms)). Cuando no quedan candidatos pendientes la plantilla pasa a `readiness: "ready"`. #### Parámetros - `template_id` string ruta requerido - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `all` boolean puede ser null `true` para confirmar todos los candidatos no-firma del análisis vigente. - `items` array de objetos puede ser null Candidatos puntuales a confirmar, por índice. Ver 6 atributos hijos - `items[].index` integer requerido Índice (1-based) del candidato del análisis a confirmar. mín. 1 - `items[].name` string puede ser null Nombre a usar en vez del propuesto por el análisis. - `items[].label` string puede ser null Etiqueta legible a usar en vez de la propuesta. - `items[].type` string puede ser null Tipo de dato (token opaco) a usar en vez del propuesto. - `items[].kind` enum puede ser null `data` o `decision`. Un candidato `signature` nunca puede confirmarse aquí. Valores: `data` `decision` - `items[].filledBy` string puede ser null Quién llena el dato: `owner` (el remitente) o `null`. `signer` o el nombre de un rol responden 422 `SIGNER_VARIABLE_NOT_SUPPORTED`: lo que llena el firmante va en un campo del formulario PDF asignado a su rol (`role` en los campos de la plantilla; `role` o `signer` en los del documento). #### Devuelve **200** Las variables de la plantilla después de confirmar. Ver los 4 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `templateId` string requerido La plantilla a la que pertenecen las variables (`tmpl_…`). - `data` array de objetos requerido Arreglo de variables de la plantilla. Ver 14 atributos hijos - `data[].name` string requerido Nombre de la variable — es la llave que usas en `templateValues`. Se mantiene en `snake_case` natural, nunca se cameliza. - `data[].label` string requerido Etiqueta legible para mostrar. - `data[].type` string Tipo de dato (token opaco `snake_case`): `text`, `date`, `currency`, `textarea`, `select`, `number`. default "text" - `data[].required` boolean Si la variable es obligatoria. default true - `data[].defaultValue` string puede ser null Valor por default sugerido. - `data[].options` array de string puede ser null Opciones válidas cuando `type` es `select`. - `data[].role` string puede ser null Rol del firmante inferido del prefijo con doble guion bajo, o `null` si la variable no está ligada a un rol. - `data[].source` string puede ser null Origen del dato (token opaco): `form_field`, `jinja`, `signal` o `manual`. - `data[].page` integer puede ser null Página 1-based del hueco principal. mín. 1 - `data[].rect` objeto puede ser null Rectángulo del hueco principal, en % del papel. Ver 4 atributos hijos - `data[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `data[].slots` array de objetos puede ser null Huecos adicionales cuando el mismo dato aparece más de una vez. Ver 2 atributos hijos - `data[].slots[].page` integer requerido Página 1-based. mín. 1 - `data[].slots[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `data[].slots[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `data[].slots[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `data[].slots[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `data[].slots[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `data[].filledBy` string puede ser null Quién llena el dato: `owner` (el remitente) o `null`. Por compatibilidad puede traer `signer` o el nombre de un rol; esos valores ya no se aceptan en el body de crear, editar ni confirmar. - `data[].snippet` string puede ser null Texto exacto del hueco detectado en el análisis (`______`, `[CIUDAD]`), o `null`. - `data[].analysisIndex` integer puede ser null Índice del candidato del análisis del que se confirmó esta variable, o `null` si se creó a mano. mín. 1 - `hasMore` boolean Siempre `false` en esta colección acotada. default false Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/tmpl_efb8ab79423a4955b7989e3807fa749e/variables:confirm' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "items": [ { "index": 1, "name": "numero_contrato" }, { "index": 2 } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_efb8ab79423a4955b7989e3807fa749e/variables:confirm', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ items: [ { index: 1, name: 'numero_contrato', }, { index: 2, }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/tmpl_efb8ab79423a4955b7989e3807fa749e/variables:confirm", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "items": [ { "index": 1, "name": "numero_contrato", }, { "index": 2, }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "templateId": "tmpl_efb8ab79423a4955b7989e3807fa749e", "data": [ { "name": "numero_contrato", "label": "Contrato número", "type": "number", "required": true, "defaultValue": null, "options": null, "role": null, "source": "signal", "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": "______________", "analysisIndex": 1 }, { "name": "ciudad", "label": "Ciudad", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": "placeholder", "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": "[CIUDAD]", "analysisIndex": 2 } ], "hasMore": false } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:15 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Get template analysis GET `/templates/{template_id}/analysis` Solo plantillas DOCX: el análisis vigente de huecos (`______`, `[CIUDAD]`, `{{ }}`) con sus candidatos a variable. Los candidatos no son variables hasta que los confirmas con `POST …/variables:confirm`; `pendingCandidates` en la plantilla dice cuántos faltan. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El análisis con sus candidatos. Ver los 8 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "template\_analysis" Siempre `"template_analysis"`. - `templateId` string requerido La plantilla analizada (`tmpl_…`). - `status` enum requerido Estado de la lectura: `reading`, `done` o `failed`. Valores: `reading` `done` `failed` - `stages` array de objetos Etapas de la lectura, para polling. Ver 3 atributos hijos - `stages[].name` string requerido Nombre de la etapa de lectura. - `stages[].status` string requerido Estado de la etapa: `pending`, `running`, `done` o `failed`. - `stages[].detail` string puede ser null Detalle legible de la etapa, o `null`. - `candidates` array de objetos Candidatos detectados. Ver 17 atributos hijos - `candidates[].name` string puede ser null Nombre propuesto, o `null` si aún no se nombra. - `candidates[].label` string puede ser null Etiqueta humana tal como aparece en el documento, o `null`. - `candidates[].kind` string requerido Tipo de candidato (token opaco): `data`, `decision` o `signature`. - `candidates[].type` string puede ser null Tipo de dato inferido: `text`, `date`, `amount`, `rfc`, `email`, `phone`, `number`, `boolean` o `choice`, o `null`. - `candidates[].confidence` number puede ser null Confianza 0–1, o `null`. entre 0 y 1 - `candidates[].why` string puede ser null Por qué se propuso este candidato. - `candidates[].slots` array de objetos Huecos donde aparece el candidato. Ver 2 atributos hijos - `candidates[].slots[].page` integer requerido Página 1-based. mín. 1 - `candidates[].slots[].rect` objeto requerido Rectángulo del hueco, en % del papel. Ver 4 atributos hijos - `candidates[].slots[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `candidates[].slots[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `candidates[].slots[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `candidates[].slots[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `candidates[].index` integer puede ser null Índice estable del candidato dentro del análisis, o `null`. mín. 1 - `candidates[].paragraph` integer puede ser null Párrafo (0-based) del DOCX fuente, o `null` si no aplica. mín. 0 - `candidates[].run` integer puede ser null Run (0-based) dentro del párrafo, o `null` si no aplica. mín. 0 - `candidates[].start` integer puede ser null Offset de inicio (0-based) dentro del párrafo del DOCX fuente, o `null`. mín. 0 - `candidates[].end` integer puede ser null Offset final (0-based) dentro del párrafo del DOCX fuente, o `null`. mín. 0 - `candidates[].neighborText` string puede ser null Texto vecino o de párrafo alrededor de la señal detectada, o `null`. - `candidates[].snippet` string puede ser null Texto exacto del hueco detectado (p.ej. `______`, `[CIUDAD]`, `( X )`), o `null`. - `candidates[].options` array de string puede ser null Opciones válidas cuando `kind` es `decision`, o `null`. - `candidates[].filledBy` string puede ser null Quién lo llena según el análisis: `owner` o `null`. Si trae otro valor, no lo reenvíes en `filledBy` al confirmar: las variables son solo del remitente. - `candidates[].labeledBy` string puede ser null `llm` cuando el etiquetador IA ajustó nombre/tipo/parte; `null` si sólo el lector determinista. - `parts` array de objetos Partes detectadas de las líneas de firma. Ver 4 atributos hijos - `parts[].index` integer puede ser null Índice estable de la parte, o `null`. mín. 1 - `parts[].text` string requerido Texto de la parte detectada en una línea de firma. - `parts[].paragraph` integer puede ser null Párrafo (0-based) del DOCX fuente, o `null` si no aplica. mín. 0 - `parts[].run` integer puede ser null Run (0-based) dentro del párrafo, o `null` si no aplica. mín. 0 - `pageCount` integer Número de páginas del documento fuente. default 0 · mín. 0 Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_efb8ab79423a4955b7989e3807fa749e/analysis' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_efb8ab79423a4955b7989e3807fa749e/analysis', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_efb8ab79423a4955b7989e3807fa749e/analysis", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "template_analysis", "templateId": "tmpl_efb8ab79423a4955b7989e3807fa749e", "status": "done", "stages": [ { "name": "read_form", "status": "done", "detail": "leyendo 0 páginas" }, { "name": "detect_candidates", "status": "done", "detail": "28 candidatos" }, { "name": "detect_parts", "status": "done", "detail": "3 partes" } ], "candidates": [ { "name": "contrato_numero", "label": "Contrato número", "kind": "data", "type": "number", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 1, "paragraph": 1, "run": 0, "start": 16, "end": 30, "neighborText": "Contrato número ______________, celebrado en [CIUDAD], el ____ de ____________ de 20____.", "snippet": "______________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "ciudad", "label": "Ciudad", "kind": "data", "type": "text", "confidence": 1, "why": "entre corchetes", "slots": [], "index": 2, "paragraph": 1, "run": 0, "start": 45, "end": 53, "neighborText": "Contrato número ______________, celebrado en [CIUDAD], el ____ de ____________ de 20____.", "snippet": "[CIUDAD]", "options": null, "filledBy": null, "labeledBy": null }, { "name": "celebrado", "label": "Celebrado", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 3, "paragraph": 1, "run": 0, "start": 58, "end": 62, "neighborText": "Contrato número ______________, celebrado en [CIUDAD], el ____ de ____________ de 20____.", "snippet": "____", "options": null, "filledBy": null, "labeledBy": null }, { "name": "celebrado_2", "label": "Celebrado", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 4, "paragraph": 1, "run": 0, "start": 66, "end": 78, "neighborText": "Contrato número ______________, celebrado en [CIUDAD], el ____ de ____________ de 20____.", "snippet": "____________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "dato_20", "label": "20", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 5, "paragraph": 1, "run": 0, "start": 84, "end": 88, "neighborText": "Contrato número ______________, celebrado en [CIUDAD], el ____ de ____________ de 20____.", "snippet": "____", "options": null, "filledBy": null, "labeledBy": null }, { "name": "dato_1", "label": "Dato 1", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 6, "paragraph": 2, "run": 0, "start": 6, "end": 40, "neighborText": "Entre __________________________________ (en adelante \"EL ARRENDADOR\"), con RFC [RFC DEL ARRENDADOR] y domicilio en ______________________________________________,", "snippet": "__________________________________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "rfc_del_arrendador", "label": "RFC del arrendador", "kind": "data", "type": "rfc", "confidence": 1, "why": "entre corchetes", "slots": [], "index": 7, "paragraph": 2, "run": 0, "start": 80, "end": 100, "neighborText": "Entre __________________________________ (en adelante \"EL ARRENDADOR\"), con RFC [RFC DEL ARRENDADOR] y domicilio en ______________________________________________,", "snippet": "[RFC DEL ARRENDADOR]", "options": null, "filledBy": null, "labeledBy": null }, { "name": "domicilio", "label": "Domicilio", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 8, "paragraph": 2, "run": 0, "start": 116, "end": 162, "neighborText": "Entre __________________________________ (en adelante \"EL ARRENDADOR\"), con RFC [RFC DEL ARRENDADOR] y domicilio en ______________________________________________,", "snippet": "______________________________________________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "dato_2", "label": "Dato 2", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 9, "paragraph": 3, "run": 0, "start": 2, "end": 36, "neighborText": "y __________________________________ (en adelante \"EL ARRENDATARIO\"), con RFC [RFC DEL ARRENDATARIO], teléfono ______________ y correo ______________________.", "snippet": "__________________________________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "rfc_del_arrendatario", "label": "RFC del arrendatario", "kind": "data", "type": "rfc", "confidence": 1, "why": "entre corchetes", "slots": [], "index": 10, "paragraph": 3, "run": 0, "start": 78, "end": 100, "neighborText": "y __________________________________ (en adelante \"EL ARRENDATARIO\"), con RFC [RFC DEL ARRENDATARIO], teléfono ______________ y correo ______________________.", "snippet": "[RFC DEL ARRENDATARIO]", "options": null, "filledBy": null, "labeledBy": null }, { "name": "telefono", "label": "Teléfono", "kind": "data", "type": "phone", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 11, "paragraph": 3, "run": 0, "start": 111, "end": 125, "neighborText": "y __________________________________ (en adelante \"EL ARRENDATARIO\"), con RFC [RFC DEL ARRENDATARIO], teléfono ______________ y correo ______________________.", "snippet": "______________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "correo", "label": "Correo", "kind": "data", "type": "email", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 12, "paragraph": 3, "run": 0, "start": 135, "end": 157, "neighborText": "y __________________________________ (en adelante \"EL ARRENDATARIO\"), con RFC [RFC DEL ARRENDATARIO], teléfono ______________ y correo ______________________.", "snippet": "______________________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "arrendador_entrega_arrendamiento_vehiculo_marca", "label": "Arrendador entrega arrendamiento vehículo marca", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 13, "paragraph": 5, "run": 0, "start": 57, "end": 71, "neighborText": "EL ARRENDADOR entrega en arrendamiento el vehículo marca ______________, modelo ______________, placas ______________ y número de serie ______________________.", "snippet": "______________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "modelo", "label": "Modelo", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 14, "paragraph": 5, "run": 0, "start": 80, "end": 94, "neighborText": "EL ARRENDADOR entrega en arrendamiento el vehículo marca ______________, modelo ______________, placas ______________ y número de serie ______________________.", "snippet": "______________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "placas", "label": "Placas", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 15, "paragraph": 5, "run": 0, "start": 103, "end": 117, "neighborText": "EL ARRENDADOR entrega en arrendamiento el vehículo marca ______________, modelo ______________, placas ______________ y número de serie ______________________.", "snippet": "______________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "numero_serie", "label": "Número serie", "kind": "data", "type": "number", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 16, "paragraph": 5, "run": 0, "start": 136, "end": 158, "neighborText": "EL ARRENDADOR entrega en arrendamiento el vehículo marca ______________, modelo ______________, placas ______________ y número de serie ______________________.", "snippet": "______________________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "renta_mensual_es", "label": "Renta mensual es $", "kind": "data", "type": "amount", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 17, "paragraph": 7, "run": 0, "start": 24, "end": 38, "neighborText": "La renta mensual es de $______________ (______________________________ pesos 00/100 M.N.), pagadera los días ____ de cada mes.", "snippet": "______________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "renta_mensual_es_2", "label": "Renta mensual es $", "kind": "data", "type": "amount", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 18, "paragraph": 7, "run": 0, "start": 40, "end": 70, "neighborText": "La renta mensual es de $______________ (______________________________ pesos 00/100 M.N.), pagadera los días ____ de cada mes.", "snippet": "______________________________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "pagadera_dias", "label": "Pagadera días", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 19, "paragraph": 7, "run": 0, "start": 109, "end": 113, "neighborText": "La renta mensual es de $______________ (______________________________ pesos 00/100 M.N.), pagadera los días ____ de cada mes.", "snippet": "____", "options": null, "filledBy": null, "labeledBy": null }, { "name": "transferencia_tarjeta_efectivo", "label": "Transferencia / Tarjeta / Efectivo", "kind": "decision", "type": "choice", "confidence": 0.7, "why": "equis en el texto", "slots": [], "index": 20, "paragraph": 8, "run": 0, "start": 16, "end": 61, "neighborText": "Forma de pago: ( X ) Transferencia ( ) Tarjeta ( ) Efectivo", "snippet": "( X ) Transferencia ( ) Tarjeta ( )", "options": [ "Transferencia", "Tarjeta", "Efectivo" ], "filledBy": null, "labeledBy": null }, { "name": "dato_3", "label": "$", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 21, "paragraph": 9, "run": 0, "start": 23, "end": 37, "neighborText": "Depósito en garantía: $______________. Vigencia: ______ meses a partir del ____ de ____________ de 20____.", "snippet": "______________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "vigencia", "label": "Vigencia", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 22, "paragraph": 9, "run": 0, "start": 49, "end": 55, "neighborText": "Depósito en garantía: $______________. Vigencia: ______ meses a partir del ____ de ____________ de 20____.", "snippet": "______", "options": null, "filledBy": null, "labeledBy": null }, { "name": "meses_partir", "label": "Meses partir", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 23, "paragraph": 9, "run": 0, "start": 75, "end": 79, "neighborText": "Depósito en garantía: $______________. Vigencia: ______ meses a partir del ____ de ____________ de 20____.", "snippet": "____", "options": null, "filledBy": null, "labeledBy": null }, { "name": "meses_partir_2", "label": "Meses partir", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 24, "paragraph": 9, "run": 0, "start": 83, "end": 95, "neighborText": "Depósito en garantía: $______________. Vigencia: ______ meses a partir del ____ de ____________ de 20____.", "snippet": "____________", "options": null, "filledBy": null, "labeledBy": null }, { "name": "dato_20_2", "label": "20", "kind": "data", "type": "text", "confidence": 0.7, "why": "raya en el contrato", "slots": [], "index": 25, "paragraph": 9, "run": 0, "start": 101, "end": 105, "neighborText": "Depósito en garantía: $______________. Vigencia: ______ meses a partir del ____ de ____________ de 20____.", "snippet": "____", "options": null, "filledBy": null, "labeledBy": null }, { "name": "nombre_y_firma_del_arrendador", "label": "Nombre y firma del arrendador", "kind": "signature", "type": null, "confidence": 0.7, "why": "línea de firma", "slots": [], "index": 26, "paragraph": 13, "run": 0, "start": 0, "end": 29, "neighborText": "NOMBRE Y FIRMA DEL ARRENDADOR", "snippet": "NOMBRE Y FIRMA DEL ARRENDADOR", "options": null, "filledBy": null, "labeledBy": null }, { "name": "nombre_y_firma_del_arrendatario", "label": "Nombre y firma del arrendatario", "kind": "signature", "type": null, "confidence": 0.7, "why": "línea de firma", "slots": [], "index": 27, "paragraph": 16, "run": 0, "start": 0, "end": 31, "neighborText": "NOMBRE Y FIRMA DEL ARRENDATARIO", "snippet": "NOMBRE Y FIRMA DEL ARRENDATARIO", "options": null, "filledBy": null, "labeledBy": null }, { "name": "nombre_y_firma_del_aval", "label": "Nombre y firma del aval", "kind": "signature", "type": null, "confidence": 0.7, "why": "línea de firma", "slots": [], "index": 28, "paragraph": 19, "run": 0, "start": 0, "end": 23, "neighborText": "NOMBRE Y FIRMA DEL AVAL", "snippet": "NOMBRE Y FIRMA DEL AVAL", "options": null, "filledBy": null, "labeledBy": null } ], "parts": [ { "index": 1, "text": "ARRENDADOR", "paragraph": 13, "run": 0 }, { "index": 2, "text": "ARRENDATARIO", "paragraph": 16, "run": 0 }, { "index": 3, "text": "AVAL", "paragraph": 19, "run": 0 } ], "pageCount": 0 } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:15 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Validate template values POST `/templates/{template_id}/validate-values` Comprueba un mapa `templateValues` contra las variables de la plantilla **sin crear nada y sin consumir crédito**. Es el ensayo previo de Create document: te dice qué falta antes de que el documento exista. Por qué importa: si emites el documento sin una variable requerida, la respuesta es `422` y no se crea nada — pero ya escribiste el formulario, ya pediste los datos al usuario y ya armaste el request. Validar en cada guardado te deja corregir mientras el usuario sigue en la pantalla. **Siempre responde `200`**, incluso cuando el mapa está mal: es un diagnóstico, no un rechazo. Lo que te dice si puedes crear el documento es el campo `valid`. La respuesta separa tres cosas que no pesan igual: - **`errors`** — falta una variable **requerida**, llegó vacía, o mandaste una llave que la plantilla no declara (`UNKNOWN_VARIABLE`, casi siempre un typo como `nombre_completoo`). Cualquiera de ellos pone `valid` en `false`. `POST /v3/templates/{id}/documents` rechaza con `422` estos mismos errores; `POST /v3/documents` rechaza la requerida que falta, pero descarta sin imprimir las llaves desconocidas. - **`warnings`** — el valor no cuadra con el `type` de la variable. **Nunca bloquean.** El `type` no se declara: se infiere del nombre, así que una variable llamada `forma_de_pago` se marca `currency` aunque contenga texto. Trátalos como una pista para revisar, no como un error. - **`ignored`** — las mismas llaves desconocidas, en una lista simple de nombres. Cada una aparece también en `errors` con `UNKNOWN_VARIABLE`. #### Parámetros - `template_id` string ruta requerido ID de la plantilla (`tmpl_…`) contra la que se validan los valores. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `templateValues` objeto (llave → valor) El mismo mapa que mandarías en `templateValues` al crear el documento. Las llaves son los `name` de las variables y conservan su forma natural (`nombre_completo`): nunca se camelizan, porque son datos del negocio y no campos del protocolo. #### Devuelve **200** El diagnóstico del mapa (`object: "template_values_validation"`), con `valid` y los tres bloques. `200` no significa que el mapa esté bien — revisa `valid`. Ver los 7 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "template\_values\_validation" Siempre `"template_values_validation"`. - `templateId` string requerido La plantilla contra la que se validó (`tmpl_…`). - `valid` boolean requerido `false` únicamente cuando hay `errors`, incluida una llave que la plantilla no declara. Los `warnings` no invalidan: el `type` de una variable se infiere de su nombre, así que no puede bloquear. - `errors` array de objetos Problemas que SÍ romperían el documento: una variable requerida ausente o vacía saldría como un espacio en blanco en el contrato, y una llave que la plantilla no declara (`UNKNOWN_VARIABLE`) no se imprimiría en ningún lado. `POST /v3/templates/{id}/documents` rechaza con 422 estos mismos errores. Ver 3 atributos hijos - `errors[].name` string requerido La variable a la que se refiere el hallazgo. - `errors[].code` string requerido Código estable del hallazgo: `MISSING_REQUIRED_VARIABLE` (falta), `EMPTY_REQUIRED_VARIABLE` (llegó vacía), `UNKNOWN_VARIABLE` (la plantilla no declara esa llave) o `TYPE_MISMATCH_HINT` (el valor no cuadra con el tipo inferido). - `errors[].detail` string requerido Explicación legible de qué pasaría si envías así el documento. - `warnings` array de objetos Avisos que no bloquean. El `type` de cada variable se **infiere de su nombre** (`monto_…` → `currency`), no está declarado en la plantilla; por eso un desajuste se avisa pero nunca rechaza. Ver 3 atributos hijos - `warnings[].name` string requerido La variable a la que se refiere el hallazgo. - `warnings[].code` string requerido Código estable del hallazgo: `MISSING_REQUIRED_VARIABLE` (falta), `EMPTY_REQUIRED_VARIABLE` (llegó vacía), `UNKNOWN_VARIABLE` (la plantilla no declara esa llave) o `TYPE_MISMATCH_HINT` (el valor no cuadra con el tipo inferido). - `warnings[].detail` string requerido Explicación legible de qué pasaría si envías así el documento. - `ignored` array de string Llaves que mandaste y la plantilla no declara — normalmente un typo. Cada una sale también en `errors` con `UNKNOWN_VARIABLE`: `POST /v3/templates/{id}/documents` las rechaza con 422 y `POST /v3/documents` las descarta sin imprimirlas. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/validate-values' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "templateValues": { "folio_acuerdo": "NDA-2026-014", "fecha_celebracion": "mañana", "ciudad_celebracion": "Ciudad de México", "objeto_relacion": "una integración de firma electrónica", "tipo_informacion_confidencial": "técnica, comercial y financiera", "vigencia": "2 años", "vigencia_post_terminacion": "3 años", "monto_pena": "cien mil", "monto_pena_letra": "doscientos cincuenta mil pesos 00/100 M.N.", "jurisdiccion": "Ciudad de México", "parte_a__nombre": "Comercializadora Ejemplo SA de CV", "parte_a__rfc": "CEJ200101AB1", "parte_a__domicilio": "Av. Reforma 100, Cuauhtémoc, Ciudad de México", "parte_a__representante": "Ana Torres", "parte_a__email": "contacto@ejemplo.com", "parte_a__telefono": "+525555550001", "parte_b__nombre": "Servicios Muestra SC", "parte_b__rfc": "SMU190505CD2", "parte_b__domicilio": "Insurgentes Sur 200, Benito Juárez, Ciudad de México", "parte_b__representante": "Luis Ramírez", "parte_b__email": "legal@ejemplo.com", "parte_b__telefono": "+525555550002", "numero_cliente": "C-0042" } }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/validate-values', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ templateValues: { folio_acuerdo: 'NDA-2026-014', fecha_celebracion: 'mañana', ciudad_celebracion: 'Ciudad de México', objeto_relacion: 'una integración de firma electrónica', tipo_informacion_confidencial: 'técnica, comercial y financiera', vigencia: '2 años', vigencia_post_terminacion: '3 años', monto_pena: 'cien mil', monto_pena_letra: 'doscientos cincuenta mil pesos 00/100 M.N.', jurisdiccion: 'Ciudad de México', parte_a__nombre: 'Comercializadora Ejemplo SA de CV', parte_a__rfc: 'CEJ200101AB1', parte_a__domicilio: 'Av. Reforma 100, Cuauhtémoc, Ciudad de México', parte_a__representante: 'Ana Torres', parte_a__email: 'contacto@ejemplo.com', parte_a__telefono: '+525555550001', parte_b__nombre: 'Servicios Muestra SC', parte_b__rfc: 'SMU190505CD2', parte_b__domicilio: 'Insurgentes Sur 200, Benito Juárez, Ciudad de México', parte_b__representante: 'Luis Ramírez', parte_b__email: 'legal@ejemplo.com', parte_b__telefono: '+525555550002', numero_cliente: 'C-0042', }, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/validate-values", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "templateValues": { "folio_acuerdo": "NDA-2026-014", "fecha_celebracion": "mañana", "ciudad_celebracion": "Ciudad de México", "objeto_relacion": "una integración de firma electrónica", "tipo_informacion_confidencial": "técnica, comercial y financiera", "vigencia": "2 años", "vigencia_post_terminacion": "3 años", "monto_pena": "cien mil", "monto_pena_letra": "doscientos cincuenta mil pesos 00/100 M.N.", "jurisdiccion": "Ciudad de México", "parte_a__nombre": "Comercializadora Ejemplo SA de CV", "parte_a__rfc": "CEJ200101AB1", "parte_a__domicilio": "Av. Reforma 100, Cuauhtémoc, Ciudad de México", "parte_a__representante": "Ana Torres", "parte_a__email": "contacto@ejemplo.com", "parte_a__telefono": "+525555550001", "parte_b__nombre": "Servicios Muestra SC", "parte_b__rfc": "SMU190505CD2", "parte_b__domicilio": "Insurgentes Sur 200, Benito Juárez, Ciudad de México", "parte_b__representante": "Luis Ramírez", "parte_b__email": "legal@ejemplo.com", "parte_b__telefono": "+525555550002", "numero_cliente": "C-0042", }, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "template_values_validation", "templateId": "tmpl_67fb98658fe548d3a71eae28087d7060", "valid": false, "errors": [ { "name": "fecha_celebracion", "code": "INVALID_DATE", "detail": "Write a date as YYYY-MM-DD or DD/MM/YYYY." }, { "name": "monto_pena", "code": "INVALID_AMOUNT", "detail": "Write a numeric amount. Example: 15000 or $15,000.00." }, { "name": "numero_cliente", "code": "UNKNOWN_VARIABLE", "detail": "The template has no variable with this name. Check the spelling in GET /v3/templates/{id}/variables." } ], "warnings": [], "ignored": [ "numero_cliente" ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/documento-desde-plantilla-docx.receta.spec.ts` ## Get signing layout (legacy) GET `/templates/{template_id}/signing` El layout de firma del modo anterior a los formularios: cajas de firma/iniciales/fecha por rol sobre una plantilla DOCX. Para plantillas PDF con formulario usa `…/fields` y `…/roles`, que lo sustituyen. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Roles con sus cajas. Ver los 4 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "template\_signing" Siempre `"template_signing"`. - `templateId` string requerido La plantilla (`tmpl_…`). - `roles` array de objetos Roles y sus campos de firma. Ver 2 atributos hijos - `roles[].name` string requerido Nombre del rol (ej. `proveedor`). - `roles[].fields` array de objetos Campos de firma de este rol. Ver 8 atributos hijos - `roles[].fields[].roleName` string requerido Nombre del rol al que pertenece este campo. - `roles[].fields[].kind` enum requerido Tipo de campo: `signature`, `initials`, `name`, `date` o `vobo`. Valores: `signature` `initials` `name` `date` `vobo` - `roles[].fields[].page` integer requerido Página 1-based. mín. 1 - `roles[].fields[].rect` objeto requerido Rectángulo del campo, en % del papel. Ver 4 atributos hijos - `roles[].fields[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `roles[].fields[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `roles[].fields[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `roles[].fields[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `roles[].fields[].rule` enum puede ser null Regla de expansión. `all_pages` replica el campo en cada página. Valores: `all_pages` - `roles[].fields[].anchor` string puede ser null Texto ancla en el PDF, o `null` si va por coordenadas. - `roles[].fields[].expect` integer puede ser null Cuántas coincidencias del ancla se esperan. mín. 1 - `roles[].fields[].onMiss` enum puede ser null Qué hacer si el ancla no aparece: `fail` o `ignore`. Valores: `fail` `ignore` Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "template_signing", "templateId": "tmpl_c405a9544678416a9b2e29dfa4a7f331", "roles": [ { "name": "Cliente", "fields": [ { "roleName": "Cliente", "kind": "signature", "page": 1, "rect": { "x": 8.1699, "y": 39.1414, "width": 40.8497, "height": 7.5758 }, "rule": null, "anchor": null, "expect": null, "onMiss": null } ] } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Replace signing layout (legacy) PUT `/templates/{template_id}/signing` Reemplaza el layout de firma completo (roles y cajas) de una plantilla DOCX. Declarativo. En plantillas PDF con formulario usa `PUT …/fields`. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `roles` array de objetos requerido Disposición completa de roles y campos. Reemplaza la anterior. Ver 2 atributos hijos - `roles[].name` string requerido Nombre del rol (ej. `proveedor`). - `roles[].fields` array de objetos Campos de firma de este rol. Ver 8 atributos hijos - `roles[].fields[].roleName` string requerido Nombre del rol al que pertenece este campo. - `roles[].fields[].kind` enum requerido Tipo de campo: `signature`, `initials`, `name`, `date` o `vobo`. Valores: `signature` `initials` `name` `date` `vobo` - `roles[].fields[].page` integer requerido Página 1-based. mín. 1 - `roles[].fields[].rect` objeto requerido Rectángulo del campo, en % del papel. Ver 4 atributos hijos - `roles[].fields[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `roles[].fields[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `roles[].fields[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `roles[].fields[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `roles[].fields[].rule` enum puede ser null Regla de expansión. `all_pages` replica el campo en cada página. Valores: `all_pages` - `roles[].fields[].anchor` string puede ser null Texto ancla en el PDF, o `null` si va por coordenadas. - `roles[].fields[].expect` integer puede ser null Cuántas coincidencias del ancla se esperan. mín. 1 - `roles[].fields[].onMiss` enum puede ser null Qué hacer si el ancla no aparece: `fail` o `ignore`. Valores: `fail` `ignore` #### Devuelve **200** El layout tal como quedó. Ver los 4 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "template\_signing" Siempre `"template_signing"`. - `templateId` string requerido La plantilla (`tmpl_…`). - `roles` array de objetos Roles y sus campos de firma. Ver 2 atributos hijos - `roles[].name` string requerido Nombre del rol (ej. `proveedor`). - `roles[].fields` array de objetos Campos de firma de este rol. Ver 8 atributos hijos - `roles[].fields[].roleName` string requerido Nombre del rol al que pertenece este campo. - `roles[].fields[].kind` enum requerido Tipo de campo: `signature`, `initials`, `name`, `date` o `vobo`. Valores: `signature` `initials` `name` `date` `vobo` - `roles[].fields[].page` integer requerido Página 1-based. mín. 1 - `roles[].fields[].rect` objeto requerido Rectángulo del campo, en % del papel. Ver 4 atributos hijos - `roles[].fields[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `roles[].fields[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `roles[].fields[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `roles[].fields[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `roles[].fields[].rule` enum puede ser null Regla de expansión. `all_pages` replica el campo en cada página. Valores: `all_pages` - `roles[].fields[].anchor` string puede ser null Texto ancla en el PDF, o `null` si va por coordenadas. - `roles[].fields[].expect` integer puede ser null Cuántas coincidencias del ancla se esperan. mín. 1 - `roles[].fields[].onMiss` enum puede ser null Qué hacer si el ancla no aparece: `fail` o `ignore`. Valores: `fail` `ignore` Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X PUT 'https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "roles": [ { "name": "Cliente", "fields": [ { "roleName": "Cliente", "kind": "signature", "page": 1, "rect": { "x": 8.1699, "y": 39.1414, "width": 40.8497, "height": 9 } } ] } ] }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ roles: [ { name: 'Cliente', fields: [ { roleName: 'Cliente', kind: 'signature', page: 1, rect: { x: 8.1699, y: 39.1414, width: 40.8497, height: 9, }, }, ], }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.put( "https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "roles": [ { "name": "Cliente", "fields": [ { "roleName": "Cliente", "kind": "signature", "page": 1, "rect": { "x": 8.1699, "y": 39.1414, "width": 40.8497, "height": 9, }, }, ], }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "template_signing", "templateId": "tmpl_c405a9544678416a9b2e29dfa4a7f331", "roles": [ { "name": "Cliente", "fields": [ { "roleName": "Cliente", "kind": "signature", "page": 1, "rect": { "x": 8.1699, "y": 39.1414, "width": 40.8497, "height": 9 }, "rule": null, "anchor": null, "expect": null, "onMiss": null } ] } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## List documents to seed the signing layout (legacy) GET `/templates/{template_id}/signing/documents` Documentos ya firmados creados desde esta plantilla, de los que puedes copiar las cajas de firma con `POST …/signing:from-document`. #### Parámetros - `template_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección de documentos candidatos. Ver los 2 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Documentos candidatos, más recientes primero. Ver 6 atributos hijos - `data[].documentId` string requerido El documento candidato (`doc_…`). - `data[].name` string requerido Nombre del documento. - `data[].completedAt` string (fecha-hora ISO 8601) puede ser null Cuándo terminó de firmarse, o `null` si el documento aún no se completa (puede seguir listado si ya trae una disposición de firma original). - `data[].fieldCount` integer requerido Campos de firma originales que tiene este documento. mín. 0 - `data[].roleNames` array de string Roles con campos de firma en este documento. - `data[].matchable` boolean requerido `true` si el documento nació exactamente de esta plantilla (mapeo directo por rol); `false` si solo es comparable por archivo/hash. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing/documents' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing/documents', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing/documents", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "documentId": "doc_2abcccc1b53249348a14e6a17c3ebe28", "name": "v3audit-ejemplos borrador desde plantilla", "completedAt": null, "fieldCount": 1, "roleNames": [], "matchable": true } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` ## Import signing layout from a document (legacy) POST `/templates/{template_id}/signing:from-document` Copia las cajas de firma (por rol) de un documento firmado al layout de la plantilla DOCX. El equivalente para formularios PDF es `POST …/fields:from-document`. #### Parámetros - `template_id` string ruta requerido - `Idempotency-Key` string header Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `documentId` string puede ser null Documento (`doc_…`) del que se copian los campos de firma. - `consolidate` boolean Si es `true`, consolida campos de documentos comparables en vez de copiar uno solo. default false #### Devuelve **200** El layout tal como quedó. Ver los 7 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `object` string, siempre "template\_signing\_import" Siempre `"template_signing_import"`. - `templateId` string requerido La plantilla destino (`tmpl_…`). - `matchedCount` integer requerido Campos que encajaron. mín. 0 - `totalCount` integer requerido Campos considerados. mín. 0 - `signing` objeto requerido Disposición resultante. Ver 4 atributos hijos - `signing.livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `signing.object` string, siempre "template\_signing" Siempre `"template_signing"`. - `signing.templateId` string requerido La plantilla (`tmpl_…`). - `signing.roles` array de objetos Roles y sus campos de firma. Ver 2 atributos hijos - `signing.roles[].name` string requerido Nombre del rol (ej. `proveedor`). - `signing.roles[].fields` array de objetos Campos de firma de este rol. Ver 8 atributos hijos - `signing.roles[].fields[].roleName` string requerido Nombre del rol al que pertenece este campo. - `signing.roles[].fields[].kind` enum requerido Tipo de campo: `signature`, `initials`, `name`, `date` o `vobo`. Valores: `signature` `initials` `name` `date` `vobo` - `signing.roles[].fields[].page` integer requerido Página 1-based. mín. 1 - `signing.roles[].fields[].rect` objeto requerido Rectángulo del campo, en % del papel. Ver 4 atributos hijos - `signing.roles[].fields[].rect.x` number requerido X izquierdo, porcentaje del ancho de la página. entre 0 y 100 - `signing.roles[].fields[].rect.y` number requerido Y superior, porcentaje del alto de la página. entre 0 y 100 - `signing.roles[].fields[].rect.width` number requerido Ancho en porcentaje de la página. entre 0 y 100 - `signing.roles[].fields[].rect.height` number requerido Alto en porcentaje de la página. entre 0 y 100 - `signing.roles[].fields[].rule` enum puede ser null Regla de expansión. `all_pages` replica el campo en cada página. Valores: `all_pages` - `signing.roles[].fields[].anchor` string puede ser null Texto ancla en el PDF, o `null` si va por coordenadas. - `signing.roles[].fields[].expect` integer puede ser null Cuántas coincidencias del ancla se esperan. mín. 1 - `signing.roles[].fields[].onMiss` enum puede ser null Qué hacer si el ancla no aparece: `fail` o `ignore`. Valores: `fail` `ignore` - `unmatched` array de objetos Campos que no encajaron, con su diferencia. Ver 4 atributos hijos - `unmatched[].roleName` string requerido Rol del campo que no encajó. - `unmatched[].kind` enum requerido Tipo del campo que no encajó. Valores: `signature` `initials` `name` `date` `vobo` - `unmatched[].page` integer requerido Página 1-based del campo que no encajó. mín. 1 - `unmatched[].detail` string requerido Por qué quedó fuera (posición distinta, rol sin pareja, etc.). Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) [503](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing:from-document' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "documentId": "doc_c9b39590d09645c9b9ebc28f4b3a5349" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing:from-document', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ documentId: 'doc_c9b39590d09645c9b9ebc28f4b3a5349', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/tmpl_c405a9544678416a9b2e29dfa4a7f331/signing:from-document", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "documentId": "doc_c9b39590d09645c9b9ebc28f4b3a5349", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "template_signing_import", "templateId": "tmpl_c405a9544678416a9b2e29dfa4a7f331", "matchedCount": 1, "totalCount": 1, "signing": { "livemode": false, "object": "template_signing", "templateId": "tmpl_c405a9544678416a9b2e29dfa4a7f331", "roles": [ { "name": "Cliente", "fields": [ { "roleName": "Cliente", "kind": "signature", "page": 1, "rect": { "x": 13.0719, "y": 78.2828, "width": 32.6797, "height": 7.5758 }, "rule": null, "anchor": null, "expect": null, "onMiss": null } ] } ] }, "unmatched": [] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/plantillas.ejemplos.spec.ts` --- Fuente: https://allsign.io/developers/docs/endpoints/folders.md # Folders Las carpetas te dejan organizar documentos en una jerarquía. Puedes crearlas, consultarlas, renombrarlas, moverlas, eliminarlas y listar los documentos que contienen. Los ids de carpeta llevan el prefijo `fld_`. Una carpeta puede anidarse bajo otra vía `parentId`; `null` (u omitido) significa nivel raíz. Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). [Descargar OpenAPI](https://allsign.io/developers/docs/openapi.public.json)[Colección de Postman](https://allsign.io/developers/docs/allsign-api-v3.postman_collection.json) Endpoints - [GET`/folders`](#list-folders) - [POST`/folders`](#create-folder) - [GET`/folders/{folder_id}`](#retrieve-folder) - [PATCH`/folders/{folder_id}`](#update-folder) - [DELETE`/folders/{folder_id}`](#delete-folder) - [GET`/folders/{folder_id}/documents`](#list-folder-documents) ## El objeto Folder #### Atributos - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido ID de la carpeta (`fld_…`). - `object` string, siempre "folder" Siempre `"folder"`. - `name` string requerido Nombre de la carpeta. - `ownerId` string requerido Dueño de la carpeta (`usr_…`). - `parentId` string puede ser null Carpeta padre (`fld_…`), o `null` si está en la raíz. - `hasDocs` boolean requerido Si la carpeta contiene al menos un documento. - `isMain` boolean requerido Si es la carpeta principal (raíz) del usuario. - `createdAt` string (fecha-hora ISO 8601) requerido Fecha de creación (ISO 8601). - `updatedAt` string (fecha-hora ISO 8601) requerido Última actualización (ISO 8601). **El objeto Folder** ``` { "livemode": false, "id": "fld_c1c3fb2ea6634f929addda83f073a00c", "object": "folder", "name": "v3audit-ejemplos Clientes", "ownerId": "usr_beadaffd52b943749b179fded8733b31", "parentId": null, "hasDocs": false, "isMain": false, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/carpetas.ejemplos.spec.ts` ## List folders GET `/folders` Lista tus carpetas con paginación por cursor. #### Parámetros - `limit` integer query Resultados por página (1–100, default `20`). default 20 · entre 1 y 100 - `startingAfter` string query Cursor: carpetas después de este `id` (`fld_…`). - `endingBefore` string query Cursor: carpetas antes de este `id` (`fld_…`). - `sort` enum query Orden por fecha de creación: `createdAt` o `-createdAt` (default `-createdAt`). Valores: `createdAt` `-createdAt` - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Sobre de paginación por cursor (`object: "list"`) con objetos Folder. Ver los 6 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Arreglo de objetos Folder. Ver 10 atributos hijos - `data[].livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `data[].id` string requerido ID de la carpeta (`fld_…`). - `data[].object` string, siempre "folder" Siempre `"folder"`. - `data[].name` string requerido Nombre de la carpeta. - `data[].ownerId` string requerido Dueño de la carpeta (`usr_…`). - `data[].parentId` string puede ser null Carpeta padre (`fld_…`), o `null` si está en la raíz. - `data[].hasDocs` boolean requerido Si la carpeta contiene al menos un documento. - `data[].isMain` boolean requerido Si es la carpeta principal (raíz) del usuario. - `data[].createdAt` string (fecha-hora ISO 8601) requerido Fecha de creación (ISO 8601). - `data[].updatedAt` string (fecha-hora ISO 8601) requerido Última actualización (ISO 8601). - `hasMore` boolean requerido `true` si hay más resultados después de esta página. - `nextCursor` string puede ser null Cursor para la siguiente página (pásalo como `startingAfter`). - `previousCursor` string puede ser null Cursor para la página anterior (pásalo como `endingBefore`). - `limit` integer requerido El límite aplicado a esta página. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/folders?limit=5' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/folders?limit=5', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/folders?limit=5", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "fld_14bc58afe3634831bf82928332c1ab86", "object": "folder", "name": "v3audit-ejemplos Contratos 2026", "ownerId": "usr_beadaffd52b943749b179fded8733b31", "parentId": "fld_c1c3fb2ea6634f929addda83f073a00c", "hasDocs": false, "isMain": false, "createdAt": "2026-07-11T18:00:00.441000Z", "updatedAt": "2026-07-11T18:00:00.441000Z" }, { "livemode": false, "id": "fld_c1c3fb2ea6634f929addda83f073a00c", "object": "folder", "name": "v3audit-ejemplos Clientes", "ownerId": "usr_beadaffd52b943749b179fded8733b31", "parentId": null, "hasDocs": false, "isMain": false, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" }, { "livemode": false, "id": "fld_44390d30ef9744ada77750fa1c9a1a9b", "object": "folder", "name": "v3audit-contrato cursor 293a3238", "ownerId": "usr_beadaffd52b943749b179fded8733b31", "parentId": null, "hasDocs": true, "isMain": false, "createdAt": "2026-07-11T12:45:17.060000Z", "updatedAt": "2026-07-11T12:45:17.060000Z" } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": 5 } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/carpetas.ejemplos.spec.ts` ## Create folder POST `/folders` Crea una carpeta. Puedes anidarla pasando un `parentId`; si lo omites (o mandas `null`), la carpeta queda a nivel raíz. #### Parámetros - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string requerido Nombre de la carpeta. - `parentId` string puede ser null Carpeta padre (`fld_…`) para anidar. `null` u omitido = nivel raíz. #### Devuelve **201** El objeto Folder creado. — [el objeto Folder](#objeto-folder) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/folders' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "name": "v3audit-ejemplos Clientes" }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/folders', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'v3audit-ejemplos Clientes', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.post( "https://api.allsign.io/v3/folders", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "name": "v3audit-ejemplos Clientes", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "fld_c1c3fb2ea6634f929addda83f073a00c", "object": "folder", "name": "v3audit-ejemplos Clientes", "ownerId": "usr_beadaffd52b943749b179fded8733b31", "parentId": null, "hasDocs": false, "isMain": false, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/carpetas.ejemplos.spec.ts` ## Retrieve folder GET `/folders/{folder_id}` Consulta una carpeta por su `id`. #### Parámetros - `folder_id` string ruta requerido ID de la carpeta (`fld_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El objeto Folder. — [el objeto Folder](#objeto-folder) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "fld_c1c3fb2ea6634f929addda83f073a00c", "object": "folder", "name": "v3audit-ejemplos Clientes", "ownerId": "usr_beadaffd52b943749b179fded8733b31", "parentId": null, "hasDocs": false, "isMain": false, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` **Respuesta · 404 · FOLDER_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#FOLDER_NOT_FOUND", "title": "Folder not found", "status": 404, "detail": "No folder was found with that id.", "instance": "/v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c", "code": "FOLDER_NOT_FOUND", "requestId": "req_8014888fbbde44e0845518524e2aee04" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/carpetas.ejemplos.spec.ts` ## Update folder PATCH `/folders/{folder_id}` Merge-patch parcial: solo se modifican los campos que envías. Los únicos campos mutables son `name` y `parentId`. Enviar cualquier otro campo se rechaza al parsear con **422 `VALIDATION_ERROR`** nombrando el campo ofensor. #### Parámetros - `folder_id` string ruta requerido ID de la carpeta (`fld_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `name` string puede ser null Nuevo nombre de la carpeta. - `parentId` string puede ser null Mover bajo otra carpeta (`fld_…`), o `null` para llevarla a la raíz. #### Devuelve **200** El objeto Folder actualizado. — [el objeto Folder](#objeto-folder) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X PATCH 'https://api.allsign.io/v3/folders/fld_14bc58afe3634831bf82928332c1ab86' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "name": "v3audit-ejemplos Contratos firmados" }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/folders/fld_14bc58afe3634831bf82928332c1ab86', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'v3audit-ejemplos Contratos firmados', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.patch( "https://api.allsign.io/v3/folders/fld_14bc58afe3634831bf82928332c1ab86", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "name": "v3audit-ejemplos Contratos firmados", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "fld_14bc58afe3634831bf82928332c1ab86", "object": "folder", "name": "v3audit-ejemplos Contratos firmados", "ownerId": "usr_beadaffd52b943749b179fded8733b31", "parentId": "fld_c1c3fb2ea6634f929addda83f073a00c", "hasDocs": false, "isMain": false, "createdAt": "2026-07-11T18:00:00.441000Z", "updatedAt": "2026-07-11T18:00:01.157000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/carpetas.ejemplos.spec.ts` ## Delete folder DELETE `/folders/{folder_id}` Elimina una carpeta **vacía**. Si todavía tiene documentos o subcarpetas, la respuesta es **`409 FOLDER_NOT_EMPTY`** (problem+json): vacía o mueve su contenido primero. La carpeta borrada no se recupera. #### Parámetros - `folder_id` string ruta requerido ID de la carpeta (`fld_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **204** Eliminada. Sin body. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X DELETE 'https://api.allsign.io/v3/folders/fld_14bc58afe3634831bf82928332c1ab86' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/folders/fld_14bc58afe3634831bf82928332c1ab86', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status); ``` **Python** ``` import os import requests respuesta = requests.delete( "https://api.allsign.io/v3/folders/fld_14bc58afe3634831bf82928332c1ab86", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code) ``` **Respuesta · 204** *Sin cuerpo.* **Respuesta · 409 · FOLDER_NOT_EMPTY** ``` { "type": "https://allsign.io/developers/docs/errors#FOLDER_NOT_EMPTY", "title": "Conflict", "status": 409, "detail": "Folder has documents. Move or delete them before deleting the folder.", "instance": "/v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c", "code": "FOLDER_NOT_EMPTY", "requestId": "req_0f54ba0e06d645f9adae72d71abadcc9" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/carpetas.ejemplos.spec.ts` ## List folder documents GET `/folders/{folder_id}/documents` Lista los documentos contenidos en una carpeta, con paginación por cursor. Devuelve el mismo sobre que List documents. #### Parámetros - `folder_id` string ruta requerido ID de la carpeta (`fld_…`). - `limit` integer query Resultados por página (1–100, default `20`). default 20 · entre 1 y 100 - `startingAfter` string query Cursor: documentos después de este `id` (`doc_…`). - `endingBefore` string query Cursor: documentos antes de este `id` (`doc_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Sobre de paginación por cursor con objetos Document. Ver los 7 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Arreglo de objetos Document. Ver 20 atributos hijos - `data[].livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `data[].id` string requerido ID del documento (`doc_…`). - `data[].object` string, siempre "document" Siempre `"document"`. - `data[].name` string requerido Nombre visible del documento. - `data[].status` enum requerido Estado del ciclo de vida: `draft`, `collecting_data`, `awaiting_signatures`, `correcting`, `processing`, `completed`, `expired`, `voided`, `error`. Valores: `draft` `collecting_data` `awaiting_signatures` `correcting` `processing` `completed` `expired` `voided` `error` - `data[].documentType` string requerido Token opaco del tipo de documento. - `data[].signerCount` integer requerido Número total de firmantes. - `data[].signedCount` integer requerido Cuántos firmantes ya firmaron. - `data[].ownerId` string requerido Dueño del documento (`usr_…`). - `data[].orgId` string puede ser null Organización del documento (`org_…`). - `data[].folderId` string puede ser null Carpeta que contiene el documento (`fld_…`). Es `null` si el documento no está en ninguna carpeta que esta credencial pueda abrir con `GET /v3/folders/{id}`. - `data[].expiresAt` string (fecha-hora ISO 8601) puede ser null Fecha límite de firma (ISO 8601). - `data[].signingOrder` enum `parallel` o `sequential` (firmantes por etapas con `routingOrder`). Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. Valores: `parallel` `sequential` - `data[].currentStage` integer puede ser null Etapa activa en un documento `sequential` mientras espera firmas; `null` en paralelo o al terminar. Disponible sólo si el orden de firma está habilitado en el entorno; si no, pedir `sequential` responde 403 `SEQUENTIAL_NOT_AVAILABLE` y todo documento firma en paralelo. - `data[].expirationReminders` array de integer puede ser null Horas antes del vencimiento en las que se envían recordatorios. - `data[].templateId` string puede ser null Plantilla de la que nació el documento (`tmpl_…`), o `null`. - `data[].templateVersionId` string puede ser null Versión de esa plantilla cuyo layout se aplicó (`tv_…`), o `null`. - `data[].parentDocumentId` string puede ser null Documento del que este es un anexo (`doc_…`). `null` si es un documento raíz. Ver `POST /documents/{id}/annexes` y `GET /documents/{id}/family`. - `data[].createdAt` string (fecha-hora ISO 8601) requerido Fecha de creación (ISO 8601). - `data[].updatedAt` string (fecha-hora ISO 8601) requerido Última actualización (ISO 8601). - `hasMore` boolean requerido `true` si hay más resultados después de esta página. - `nextCursor` string puede ser null Cursor para la siguiente página (pásalo como `startingAfter`). - `previousCursor` string puede ser null Cursor para la página anterior (pásalo como `endingBefore`). - `limit` integer requerido El límite aplicado a esta página. - `totalCount` integer puede ser null Total de coincidencias. Solo se llena cuando pides `includeTotal=true`. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c/documents?limit=5' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c/documents?limit=5', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/folders/fld_c1c3fb2ea6634f929addda83f073a00c/documents?limit=5", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "doc_ec50be6599c04f82a3d8bba51b5cbb24", "object": "document", "name": "v3audit-ejemplos documento en carpeta", "status": "draft", "documentType": "EDITABLE", "signerCount": 0, "signedCount": 0, "ownerId": "usr_beadaffd52b943749b179fded8733b31", "orgId": "6332dfb0-7654-42fb-8d21-16dcecdee536", "folderId": "fld_c1c3fb2ea6634f929addda83f073a00c", "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T17:59:58.914000Z", "updatedAt": "2026-07-11T18:00:01.675000Z" } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": 5, "totalCount": null } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/carpetas.ejemplos.spec.ts` --- Fuente: https://allsign.io/developers/docs/endpoints/analytics.md # Analytics Los endpoints de Analytics resumen la actividad de firma de tu tenant: indicadores clave, el embudo de firma, la tendencia mensual, quién frena los documentos, la actividad por miembro del equipo y los eventos más recientes. Son de **solo lectura** y agregan datos que ya viven en tus documentos. **Los seis endpoints exigen el scope `analytics:read`** (o `analytics:*`). Una API key con solo `document:*` recibe **403 `PERMISSION_DENIED`** con la extensión `requiredScope: "analytics:read"` — genera o edita una key con ese scope en el Dashboard antes de consultarlos. Todas las respuestas usan **camelCase** en el wire e ids opacos con prefijo (`usr_`, `evt_`). Los errores siguen **problem+json** (RFC 9457) con un `code` en `UPPER_SNAKE`. Estas listas son **colecciones acotadas** (`object: "list"` con `hasMore` siempre `false`): NO paginan por cursor — su tamaño lo limita el propio endpoint (el embudo tiene 4 etapas, la tendencia 6 meses, etc.). Cada respuesta trae headers `RateLimit-*`. Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). [Descargar OpenAPI](https://allsign.io/developers/docs/openapi.public.json)[Colección de Postman](https://allsign.io/developers/docs/allsign-api-v3.postman_collection.json) Endpoints - [GET`/analytics/kpis`](#kpis) - [GET`/analytics/funnel`](#signing-funnel) - [GET`/analytics/trend`](#monthly-trend) - [GET`/analytics/bottlenecks`](#bottlenecks) - [GET`/analytics/team`](#team-activity) - [GET`/analytics/events`](#recent-events) ## KPIs GET `/analytics/kpis` Indicadores clave del periodo: total de documentos, completados, pendientes, expirados, tasa de finalización y tiempo promedio de firma. La respuesta es un **objeto plano** (no un sobre `list`). #### Parámetros - `period` string query Ventana de tiempo: `7d`, `30d`, `90d` o `12m`. Default `30d`. Otro valor es un `422 VALIDATION_ERROR`. default "30d" - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Objeto plano con los KPIs del periodo. Ver los 6 atributos de la respuesta - `totalDocs` integer requerido Documentos enviados en el periodo, sin contar borradores. - `completed` integer requerido Documentos con todas las firmas completas. - `pending` integer requerido Documentos aún esperando firmas. - `expired` integer requerido Documentos que vencieron sin completarse. - `completionRate` number requerido Porcentaje de completados sobre los enviados (0–100). - `avgSignTimeHours` number puede ser null Horas promedio entre la creación del documento y su última firma. `null` si aún no hay documentos completados en el periodo. Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/analytics/kpis?period=30d' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/analytics/kpis?period=30d', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/analytics/kpis?period=30d", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "totalDocs": 114, "completed": 110, "pending": 2, "expired": 0, "completionRate": 96.5, "avgSignTimeHours": null } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/analitica.ejemplos.spec.ts` ## Signing funnel GET `/analytics/funnel` El embudo de firma en **cuatro etapas fijas**: Enviados → En progreso → Completados → Expirados. La colección siempre trae exactamente 4 elementos. #### Parámetros - `period` string query Ventana de tiempo: `7d`, `30d`, `90d` o `12m`. Default `30d`. default "30d" - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada (`object: "list"`) con las 4 etapas del embudo, en orden. Ver los 6 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Las 4 etapas del embudo, en orden. Ver 3 atributos hijos - `data[].label` string requerido Nombre de la etapa (ej. "Enviados"). - `data[].count` integer requerido Documentos en esa etapa. - `data[].pct` number requerido Porcentaje de esa etapa respecto a la primera. - `hasMore` boolean Siempre `false` — colección acotada, sin cursor. default false - `nextCursor` null Siempre `null` — esta colección no pagina. - `previousCursor` null Siempre `null` — esta colección no pagina. - `limit` null Siempre `null` — sin parámetro `limit` en este endpoint. Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/analytics/funnel?period=30d' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/analytics/funnel?period=30d', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/analytics/funnel?period=30d", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "label": "Enviados", "count": 114, "pct": 100 }, { "label": "En progreso", "count": 2, "pct": 1.8 }, { "label": "Completados", "count": 110, "pct": 96.5 }, { "label": "Expirados", "count": 0, "pct": 0 } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": null } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/analitica.ejemplos.spec.ts` ## Monthly trend GET `/analytics/trend` Documentos firmados y horas promedio de firma por mes, para los **últimos 6 meses** (fijo, sin parámetros). #### Parámetros - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada (`object: "list"`) con un punto por mes (hasta 6). Ver los 6 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Un punto por mes (hasta 6). Ver 3 atributos hijos - `data[].month` string requerido Abreviatura del mes en inglés, sin año (ej. "Jan"). No es formato ISO `YYYY-MM`: el mismo valor se repite para meses de distintos años. Mantiene el formato que ya expone `GET /v2/analytics/trend` (contrato público en vivo) por compatibilidad. - `data[].signed` integer requerido Documentos firmados ese mes. - `data[].avgHours` number requerido Horas promedio de firma ese mes. - `hasMore` boolean Siempre `false`. default false - `nextCursor` null Siempre `null` — esta colección no pagina. - `previousCursor` null Siempre `null` — esta colección no pagina. - `limit` null Siempre `null` — sin parámetro `limit` en este endpoint. Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/analytics/trend' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/analytics/trend', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/analytics/trend", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "month": "Oct", "signed": 110, "avgHours": 0 } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": null } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/analitica.ejemplos.spec.ts` ## Bottlenecks GET `/analytics/bottlenecks` Los firmantes que más documentos tienen pendientes — quién está frenando tus flujos de firma. #### Parámetros - `limit` integer query Máximo de firmantes a devolver (1–20, default `5`). default 5 · entre 1 y 20 - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada (`object: "list"`), firmantes ordenados por firmas pendientes. Ver los 6 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Firmantes ordenados por firmas pendientes. Ver 4 atributos hijos - `data[].signerName` string requerido Nombre del firmante. - `data[].signerEmail` string puede ser null Correo del firmante, si se conoce. - `data[].pendingCount` integer requerido Firmas pendientes de esta persona. - `data[].avgDays` number requerido Días promedio que llevan pendientes. - `hasMore` boolean Siempre `false` — la lista la acota `limit`, no un cursor. default false - `nextCursor` null Siempre `null` — esta colección no pagina. - `previousCursor` null Siempre `null` — esta colección no pagina. - `limit` integer requerido El límite aplicado a esta respuesta. Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/analytics/bottlenecks?limit=5' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/analytics/bottlenecks?limit=5', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/analytics/bottlenecks?limit=5", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "signerName": "signer-declined@sandbox.allsign.io", "signerEmail": "signer-declined@sandbox.allsign.io", "pendingCount": 2, "avgDays": 0.1 } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": 5 } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/analitica.ejemplos.spec.ts` ## Team activity GET `/analytics/team` Actividad de envío y firma por cada miembro del tenant — una fila por miembro. #### Parámetros - `period` string query Ventana de tiempo: `7d`, `30d`, `90d` o `12m`. Default `30d`. default "30d" - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada (`object: "list"`), una fila por miembro del tenant. Ver los 6 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Una fila por miembro del equipo. Ver 7 atributos hijos - `data[].userId` string requerido ID opaco del miembro (`usr_…`) — el mismo prefijo que usa `GET /v3/users/me`, no un UUID crudo. - `data[].name` string requerido Nombre del miembro. - `data[].initials` string requerido Iniciales para avatar. - `data[].role` string requerido Rol del miembro en el tenant. - `data[].sent` integer requerido Documentos enviados por el miembro. - `data[].signed` integer requerido Documentos enviados por el miembro que ya se completaron. - `data[].rate` number requerido Tasa de finalización del miembro. - `hasMore` boolean Siempre `false` — acotada por el número de asientos del tenant. default false - `nextCursor` null Siempre `null` — esta colección no pagina. - `previousCursor` null Siempre `null` — esta colección no pagina. - `limit` null Siempre `null` — sin parámetro `limit` en este endpoint. Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/analytics/team?period=30d' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/analytics/team?period=30d', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/analytics/team?period=30d", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "userId": "usr_40f0ca57b4a34de68d2adce4c1f053f3", "name": "Ana Torres", "initials": "AT", "role": "owner", "sent": 114, "signed": 110, "rate": 96.5 }, { "userId": "usr_22697584acbb4cdfa129d218622459cf", "name": "Luis Ramírez", "initials": "LR", "role": "developer", "sent": 0, "signed": 0, "rate": 0 } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": null } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/analitica.ejemplos.spec.ts` ## Recent events GET `/analytics/events` El feed de actividad reciente del tenant — los últimos eventos de documento (creado, enviado, firmado, etc.). #### Parámetros - `limit` integer query Máximo de eventos a devolver (1–50, default `8`). default 8 · entre 1 y 50 - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada (`object: "list"`), eventos recientes (más nuevos primero). Ver los 6 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Eventos recientes, más nuevos primero. Ver 6 atributos hijos - `data[].id` string requerido ID del evento (`evt_…`). - `data[].documentName` string requerido Nombre del documento del evento. - `data[].eventType` string requerido Tipo de evento (token opaco, ej. `document.created`). - `data[].actorName` string puede ser null Nombre de quien originó el evento, o `null` si fue el sistema. - `data[].actorInitials` string requerido Iniciales del actor. - `data[].createdAt` string (fecha-hora ISO 8601) requerido Cuándo ocurrió (ISO 8601). - `hasMore` boolean Siempre `false` — la lista la acota `limit`. default false - `nextCursor` null Siempre `null` — esta colección no pagina. - `previousCursor` null Siempre `null` — esta colección no pagina. - `limit` integer requerido El límite aplicado a esta respuesta. Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/analytics/events?limit=5' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/analytics/events?limit=5', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/analytics/events?limit=5", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "id": "evt_8ae1527a4958486a8ab7309659efb9dc", "documentName": "Contrato de compraventa — firma en orden", "eventType": "document.viewed", "actorName": "Ana Torres", "actorInitials": "AT", "createdAt": "2026-07-11T18:00:00.000000Z" }, { "id": "evt_1b16458ac1454c7b9fa51bc71d9900f2", "documentName": "Mi primer contrato (sandbox)", "eventType": "document.viewed", "actorName": "Ana Torres", "actorInitials": "AT", "createdAt": "2026-07-11T17:59:57.296000Z" }, { "id": "evt_f0700cbe6c184ef9ba5d0062a2796b50", "documentName": "Contrato de compraventa — firma en orden", "eventType": "signature.signed", "actorName": "signer-pending+2@sandbox.allsign.io", "actorInitials": "SH", "createdAt": "2026-07-11T17:59:57.140000Z" }, { "id": "evt_58b3d46cc6e3455fa54e76084910f03b", "documentName": "Contrato de compraventa — firma en orden", "eventType": "signature.consent_given", "actorName": "signer-pending+2@sandbox.allsign.io", "actorInitials": "SH", "createdAt": "2026-07-11T17:59:57.140000Z" }, { "id": "evt_a5b8b7014cdf42c5ab4ada3e583b3bb4", "documentName": "Mi primer contrato (sandbox)", "eventType": "document.viewed", "actorName": "Ana Torres", "actorInitials": "AT", "createdAt": "2026-07-11T17:59:40.522000Z" } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": 5 } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/analitica.ejemplos.spec.ts` --- Fuente: https://allsign.io/developers/docs/endpoints/users.md # Users Dos endpoints para conocer **quién eres** frente a la API y **quién más** está en tu tenant. `GET /v3/users/me` identifica al principal detrás de la credencial (útil para verificar scopes y entorno); `GET /v3/users/team` lista a los miembros del equipo. Las respuestas usan **camelCase** en el wire e ids opacos con prefijo (`usr_`, `ten_`). Los errores siguen **problem+json** (RFC 9457) con un `code` en `UPPER_SNAKE`. `GET /v3/users/team` es una **colección acotada** (`object: "list"`, `hasMore` siempre `false`): su tamaño lo limitan los asientos del tenant, nunca necesita cursor. Cada respuesta trae headers `RateLimit-*`. Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). [Descargar OpenAPI](https://allsign.io/developers/docs/openapi.public.json)[Colección de Postman](https://allsign.io/developers/docs/allsign-api-v3.postman_collection.json) Endpoints - [GET`/users/me`](#get-current-user) - [GET`/users/team`](#list-team-members) ## El objeto UserMe #### Atributos - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido ID opaco del usuario detrás de la credencial (`usr_…`). - `email` string puede ser null Correo del usuario, si está disponible. - `fullName` string puede ser null Nombre para mostrar del usuario; si su perfil no tiene nombre, la parte local del correo. - `tenantId` string requerido ID opaco del tenant (`ten_…`). - `orgId` string puede ser null ID opaco de la organización del usuario (`org_…`), o `null` si no pertenece a ninguna. - `scopes` array de string requerido Los scopes que trae tu API key (ej. `["document:read", "document:write"]`). - `environment` string requerido Entorno de la key: `live`, `dev` o `test`. Sólo `live` opera sobre datos reales, consume créditos y trae `livemode: true`; `dev` y `test` son de prueba. - `authMode` string requerido Modo de autenticación con el que se resolvió la petición (ej. `api_key`). **El objeto UserMe** ``` { "livemode": false, "id": "usr_1fac1e055ae544ac9f9620b42b5ad98d", "email": "ana@ejemplo.com", "fullName": "Ana Torres", "tenantId": "ten_0831aa4e33224c3bbc71f396d736d34c", "orgId": "org_8f4afda0323e478985538d5ccee18821", "scopes": [ "document:read", "document:write", "document:delete", "signature:read", "webhook:read", "webhook:write", "webhook:delete", "constancia:read", "constancia:write", "embedded:write", "analytics:read", "user:read" ], "environment": "test", "authMode": "api_key" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/usuarios.ejemplos.spec.ts` ## Get current user GET `/users/me` Devuelve el tenant y el usuario detrás de tu credencial, junto con los scopes que tiene tu API key y el entorno en que opera. Usa este endpoint como sonda de salud de tu credencial antes de una integración. **Responde `200` (autenticado) o `401` (sin credencial válida) — nunca `403`.** Solo confirma tu propia identidad, así que no está protegido por ningún scope. #### Parámetros - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El usuario y tenant detrás de la credencial, con sus scopes y entorno. — [el objeto UserMe](#objeto-userme) Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/users/me' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/users/me', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/users/me", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "usr_1fac1e055ae544ac9f9620b42b5ad98d", "email": "ana@ejemplo.com", "fullName": "Ana Torres", "tenantId": "ten_0831aa4e33224c3bbc71f396d736d34c", "orgId": "org_8f4afda0323e478985538d5ccee18821", "scopes": [ "document:read", "document:write", "document:delete", "signature:read", "webhook:read", "webhook:write", "webhook:delete", "constancia:read", "constancia:write", "embedded:write", "analytics:read", "user:read" ], "environment": "test", "authMode": "api_key" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/usuarios.ejemplos.spec.ts` ## List team members GET `/users/team` Lista a cada miembro de tu tenant con su rol. A diferencia de `/users/me`, este endpoint **sí está protegido por un scope**: expone datos de otras personas, así que requiere `user:read`. #### Parámetros - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Colección acotada (`object: "list"`, `hasMore` siempre `false`) con los miembros del tenant. Ver los 6 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Miembros del tenant. Ver 4 atributos hijos - `data[].id` string requerido ID opaco del miembro (`usr_…`). - `data[].name` string requerido Nombre del miembro. - `data[].email` string requerido Correo del miembro. - `data[].role` string requerido Rol del miembro en el tenant (ej. `owner`, `admin`, `member`). - `hasMore` boolean Siempre `false` — colección acotada por los asientos del tenant, sin cursor. default false - `nextCursor` null Siempre `null` — esta colección no pagina. - `previousCursor` null Siempre `null` — esta colección no pagina. - `limit` null Siempre `null` — sin parámetro `limit` en este endpoint. Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/users/team' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/users/team', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/users/team", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "id": "usr_550487c74d4a4061ba2af6b802dcacde", "name": "Sofía Herrera", "email": "sofia@ejemplo.com", "role": "owner" }, { "id": "usr_b478f972e8574a739be5fb7364382c33", "name": "Carlos Méndez", "email": "carlos@ejemplo.com", "role": "developer" } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": null } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/observadores-y-transferencia.receta.spec.ts` --- Fuente: https://allsign.io/developers/docs/endpoints/signing-sessions.md # Embedded Signing Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). [Descargar OpenAPI](https://allsign.io/developers/docs/openapi.public.json)[Colección de Postman](https://allsign.io/developers/docs/allsign-api-v3.postman_collection.json) Endpoints - [POST`/signing-sessions`](#create-signing-session) - [GET`/signing-sessions/{session_id}`](#retrieve-signing-session) - [POST`/signing-sessions/{session_id}/init`](#init-signing-session) - [GET`/signing-sessions/{session_id}/policy`](#get-session-policy) ## El objeto SigningSession #### Atributos - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido ID de la sesión (`ses_…`). - `object` string, siempre "signing\_session" Siempre `"signing_session"`. - `status` enum requerido Estado de la sesión: `pending`, `active`, `completed`, `expired` o `cancelled`. Valores: `pending` `active` `completed` `expired` `cancelled` - `clientSecret` string puede ser null El secreto que tu frontend pasa al iframe. Devuelto una sola vez — en el GET viene `null`. - `document` objeto requerido Documento que firma la sesión. Ver 2 atributos hijos - `document.id` string requerido ID del documento (`doc_…`). - `document.title` string requerido Nombre del documento. - `signer` objeto requerido Firmante ligado a la sesión. Ver 2 atributos hijos - `signer.email` string requerido Correo del participante que firma en el iframe. - `signer.name` string requerido Nombre del firmante. - `expiresAt` string (fecha-hora ISO 8601) puede ser null Cuándo expira la sesión (ISO 8601). - `createdAt` string (fecha-hora ISO 8601) puede ser null Cuándo se creó (ISO 8601). - `mountedAt` string (fecha-hora ISO 8601) puede ser null Cuándo se montó el iframe por primera vez. - `completedAt` string (fecha-hora ISO 8601) puede ser null Cuándo se completó la firma. - `signature` objeto puede ser null Estado autoritativo de la firma. Ver 3 atributos hijos - `signature.id` string requerido ID de la firma (`sgr_…`). - `signature.status` enum requerido Estado autoritativo de la firma: `pending`, `sent`, `signed`, `cancelled`. Valores: `pending` `sent` `signed` `cancelled` `waiting_turn` - `signature.signedAt` string (fecha-hora ISO 8601) puede ser null Momento de la firma (ISO 8601). - `evidence` objeto puede ser null Paquete de evidencia sellada. Solo con `?expand=evidence`. Ver 3 atributos hijos - `evidence.available` boolean requerido Si el paquete de evidencia ya está listo. - `evidence.evidencePdf` objeto puede ser null Puntero al PDF de evidencia sellado. Ver 3 atributos hijos - `evidence.evidencePdf.s3Key` string puede ser null Ruta S3 del PDF de evidencia. - `evidence.evidencePdf.presignedUrl` string puede ser null URL prefirmada del PDF de evidencia (válida ~24 h). - `evidence.evidencePdf.hash` string puede ser null Hash del PDF de evidencia. - `evidence.nom151` objeto puede ser null Puntero a la constancia NOM-151. Ver 3 atributos hijos - `evidence.nom151.s3Key` string puede ser null Ruta S3 de la constancia NOM-151. - `evidence.nom151.presignedUrl` string puede ser null URL prefirmada de la constancia NOM-151. - `evidence.nom151.data` objeto (llave → valor) puede ser null Metadatos de la constancia NOM-151. **El objeto SigningSession** ``` { "livemode": false, "id": "ses_76fc6e4f1aff48f3a8da1c2d9e21a149", "object": "signing_session", "status": "pending", "clientSecret": null, "document": { "id": "doc_51208ae8826c409999876c8f7e18e372", "title": "v3audit-ejemplos firma embebida" }, "signer": { "email": "ana@ejemplo.com", "name": "ana@ejemplo.com" }, "expiresAt": "2026-07-11T18:00:00.000000Z", "createdAt": "2026-07-11T17:44:59.981000Z", "mountedAt": null, "completedAt": null, "signature": { "id": "sgr_7f491e1b1be14ee2a192effe1bdb50dc", "status": "sent", "signedAt": null }, "evidence": null } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:14 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/firma-embebida.ejemplos.spec.ts` ## Create signing session POST `/signing-sessions` Acuña un `clientSecret` que autoriza al iframe a firmar como un firmante (identificado por su correo) de un documento existente. **El `clientSecret` se devuelve una sola vez, aquí.** Requiere el scope `embedded:write` y honra la cabecera `Idempotency-Key`. `livemode` sale de la key, no del body: una key `live` exige `allowedOrigins`. #### Parámetros - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `documentId` string requerido ID del documento a firmar (`doc_…`). Debe existir y tener al firmante como participante. - `signerEmail` string puede ser null Correo del participante que firmará. Manda éste o `signerPhone`; uno de los dos es obligatorio. - `signerPhone` string puede ser null WhatsApp del participante que firmará, en E.164 (`+5255…`). Alternativa a `signerEmail` para el firmante que no dio correo. - `successUrl` string puede ser null A dónde redirigir cuando la firma se completa. - `cancelUrl` string puede ser null A dónde redirigir si el firmante cancela. - `brandProfileId` string puede ser null UUID de un perfil de marca de tu cuenta; el iframe usa sus colores. Si no existe o es de otra cuenta responde 404 `RESOURCE_NOT_FOUND`. - `allowedOrigins` array de string puede ser null Orígenes donde puede montarse el iframe. Obligatorio para keys `live`. - `otpMode` enum Verificación de identidad al crear: `none` (default), `required` o `integrator_verified`. Solo `none` está implementado. Valores: `none` `required` `integrator_verified` #### Devuelve **201** La sesión creada, con `clientSecret` en claro (única vez). — [el objeto SigningSession](#objeto-signingsession) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/signing-sessions' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "documentId": "doc_e6d57f83692e4ff5ad07f57f6bd6a117", "signerEmail": "ana@ejemplo.com", "allowedOrigins": [ "https://app.ejemplo.com" ], "successUrl": "https://app.ejemplo.com/firmado", "cancelUrl": "https://app.ejemplo.com/cancelado" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/signing-sessions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ documentId: 'doc_e6d57f83692e4ff5ad07f57f6bd6a117', signerEmail: 'ana@ejemplo.com', allowedOrigins: [ 'https://app.ejemplo.com', ], successUrl: 'https://app.ejemplo.com/firmado', cancelUrl: 'https://app.ejemplo.com/cancelado', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/signing-sessions", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "documentId": "doc_e6d57f83692e4ff5ad07f57f6bd6a117", "signerEmail": "ana@ejemplo.com", "allowedOrigins": [ "https://app.ejemplo.com", ], "successUrl": "https://app.ejemplo.com/firmado", "cancelUrl": "https://app.ejemplo.com/cancelado", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a", "object": "signing_session", "status": "pending", "clientSecret": "", "document": { "id": "doc_e6d57f83692e4ff5ad07f57f6bd6a117", "title": "Contrato de servicios — Ana Torres" }, "signer": { "email": "ana@ejemplo.com", "name": "ana@ejemplo.com" }, "expiresAt": "2026-07-11T18:15:01.869000Z", "createdAt": null, "mountedAt": null, "completedAt": null, "signature": null, "evidence": null } ``` **Respuesta · 404 · RESOURCE_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#RESOURCE_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "No participant with email nadie@ejemplo.com on document c2af0193-88b8-4767-be86-e9bc40106542", "instance": "/v3/signing-sessions", "code": "RESOURCE_NOT_FOUND", "requestId": "req_243a82a23c1a45aba4757d8ed3758d31" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/firma-embebida-en-tu-app.receta.spec.ts` ## Retrieve signing session GET `/signing-sessions/{session_id}` Lee el estado **autoritativo del lado del servidor** de una sesión — la verdad sobre si la firma se completó, no lo que reporte el iframe. Un `?expand=evidence` adjunta el paquete de evidencia (PDF sellado + NOM-151 + URLs prefirmadas) cuando ya existe. Requiere `embedded:write`. El `clientSecret` no se devuelve aquí (viene `null`). #### Parámetros - `session_id` string ruta requerido ID de la sesión (`ses_…`). - `expand` string query Pasa `evidence` para adjuntar el paquete de evidencia sellada. Otro valor es un **400 `INVALID_EXPAND`**. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El objeto sesión, con evidencia si se pidió `?expand=evidence`. — [el objeto SigningSession](#objeto-signingsession) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/signing-sessions/ses_76fc6e4f1aff48f3a8da1c2d9e21a149' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/signing-sessions/ses_76fc6e4f1aff48f3a8da1c2d9e21a149', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/signing-sessions/ses_76fc6e4f1aff48f3a8da1c2d9e21a149", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "ses_76fc6e4f1aff48f3a8da1c2d9e21a149", "object": "signing_session", "status": "pending", "clientSecret": null, "document": { "id": "doc_51208ae8826c409999876c8f7e18e372", "title": "v3audit-ejemplos firma embebida" }, "signer": { "email": "ana@ejemplo.com", "name": "ana@ejemplo.com" }, "expiresAt": "2026-07-11T18:00:00.000000Z", "createdAt": "2026-07-11T17:44:59.981000Z", "mountedAt": null, "completedAt": null, "signature": { "id": "sgr_7f491e1b1be14ee2a192effe1bdb50dc", "status": "sent", "signedAt": null }, "evidence": null } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:14 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/firma-embebida.ejemplos.spec.ts` ## Init signing session POST `/signing-sessions/{session_id}/init` Intercambia el `clientSecret` por un **guest token** nuevo de la sesión. **Ruta pública — no lleva `Authorization`**: la autenticación es el `clientSecret` en el body. Si la sesión se creó con `allowedOrigins`, `parentOrigin` es **obligatorio** y tiene que ser uno de ellos: sin él, o con otro origen, la respuesta es `403 PERMISSION_DENIED` y no se entrega token. La única excepción es de desarrollo: un origen `http://localhost:` se acepta con cualquier lista. Llamarla otra vez rota el guest token y entrega uno nuevo. **No la necesitas para embeber la firma:** ni el SDK ni el iframe de AllSign la llaman; AllSign hace el equivalente por ti al abrir la firma. Por eso una página tuya que no está en `allowedOrigins` no recibe este `403`: lo normal es que el navegador ni siquiera cargue el iframe, porque AllSign lo sirve con `Content-Security-Policy: frame-ancestors` limitado a esa lista. Si aun así llega a cargar, la llamada con la que el iframe inicia la sesión responde `403` con el código `ORIGIN_NOT_ALLOWED`. #### Parámetros - `session_id` string ruta requerido ID de la sesión (`ses_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `clientSecret` string requerido El secreto acuñado en el create (forma `as_sess_{id}_secret_{rnd}`). Es la credencial de esta ruta. - `parentOrigin` string puede ser null El origen (`https://…`) de la página que embebe el iframe, para validar contra `allowedOrigins`. Obligatorio si la sesión se creó con `allowedOrigins`: si falta o no está en la lista, la respuesta es 403 `PERMISSION_DENIED`. `http://localhost`, con o sin puerto, se acepta siempre. #### Devuelve **200** El guest token nuevo y el contexto de la sesión para montar el iframe. Ver los 12 atributos de la respuesta - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `sessionId` string requerido ID de la sesión (`ses_…`). - `signatureId` string requerido Firma que el guest token autoriza (`sgr_…`). - `signerId` string puede ser null ID del firmante, si aplica. - `guestToken` string requerido Credencial de un solo uso para el iframe (rotada en cada `init`). - `signer` objeto requerido Firmante ligado a la sesión. Ver 2 atributos hijos - `signer.email` string requerido Correo del participante que firma en el iframe. - `signer.name` string requerido Nombre del firmante. - `document` objeto requerido Documento que firma la sesión. Ver 2 atributos hijos - `document.id` string requerido ID del documento (`doc_…`). - `document.title` string requerido Nombre del documento. - `brandProfileId` string puede ser null Perfil de marca, si se configuró. - `appearance` objeto puede ser null Marca del workspace para el iframe. `null` = el aspecto por defecto de AllSign (marca no habilitada, sin plan de pago, sin perfil o valor guardado inválido). Ver 5 atributos hijos - `appearance.primaryColor` string requerido Color de marca, `#rrggbb` en minúsculas. - `appearance.primaryTextColor` string requerido Color del texto sobre `primaryColor` (`#ffffff` o `#000000`), el de mayor contraste WCAG. - `appearance.borderRadius` integer puede ser null Radio de las esquinas en px (0–24). - `appearance.fontFamily` string puede ser null `system`, `geist`, `manrope` o `inter`. Clientes deben tolerar valores nuevos. - `appearance.poweredByAllSign` boolean El pie «Powered by AllSign» siempre se muestra. default true - `locale` string requerido Idioma del iframe (ej. `es`). - `successUrl` string puede ser null Redirección de éxito. - `cancelUrl` string puede ser null Redirección de cancelación. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/init' \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "clientSecret": "", "parentOrigin": "https://app.ejemplo.com" }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/init', { method: 'POST', headers: { 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ clientSecret: '', parentOrigin: 'https://app.ejemplo.com', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import requests respuesta = requests.post( "https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/init", headers={ "AllSign-Version": "2026-07-11", }, json={ "clientSecret": "", "parentOrigin": "https://app.ejemplo.com", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "sessionId": "ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a", "signatureId": "sgr_fdcf573b43b24b2484b1a795ffff848f", "signerId": "usr_7e3a6c7366b54e50a5f49fbc1f0e8b9c", "guestToken": "", "signer": { "email": "ana@ejemplo.com", "name": "ana@ejemplo.com" }, "document": { "id": "doc_e6d57f83692e4ff5ad07f57f6bd6a117", "title": "Contrato de servicios — Ana Torres" }, "brandProfileId": null, "appearance": { "primaryColor": "#e54848", "primaryTextColor": "#000000", "borderRadius": 24, "fontFamily": null, "poweredByAllSign": true }, "locale": "es", "successUrl": "https://app.ejemplo.com/firmado", "cancelUrl": "https://app.ejemplo.com/cancelado" } ``` **Respuesta · 403 · PERMISSION_DENIED** ``` { "type": "https://allsign.io/developers/docs/errors#PERMISSION_DENIED", "title": "Permission denied", "status": 403, "detail": "The origin 'https://otro.ejemplo.com' is not authorized to embed this session.", "instance": "/v3/signing-sessions/ses_ee38d79b09ad45b9ad094034c1910ecc/init", "code": "PERMISSION_DENIED", "requestId": "req_beaface9931b45e4a4eb9e51c8b13f9c" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/firma-embebida-en-tu-app.receta.spec.ts` ## Get session policy GET `/signing-sessions/{session_id}/policy` Consulta pública (por id, **no** por el secreto) de los orígenes autorizados a embeber la firma de la sesión: devuelve `allowedOrigins` y la directiva `frameAncestors` que les corresponde. **Ruta pública — no lleva `Authorization`.** No expone el `clientSecret` ni datos del documento. **No la necesitas para embeber la firma:** AllSign aplica esa misma lista en la cabecera `Content-Security-Policy: frame-ancestors` del iframe por su cuenta, sin pasar por esta ruta. #### Parámetros - `session_id` string ruta requerido ID de la sesión (`ses_…`). - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** La política CSP (`allowedOrigins` + `frameAncestors`) de la sesión. Ver los 3 atributos de la respuesta - `sessionId` string requerido ID de la sesión (`ses_…`). - `allowedOrigins` array de string requerido Orígenes autorizados a montar el iframe (los `allowedOrigins` del create). - `frameAncestors` string requerido El valor listo para el header `Content-Security-Policy: frame-ancestors`. Es `*` cuando no se configuraron orígenes. Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/policy' \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/policy', { headers: { 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import requests respuesta = requests.get( "https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/policy", headers={ "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "sessionId": "ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a", "allowedOrigins": [ "https://app.ejemplo.com" ], "frameAncestors": "https://app.ejemplo.com http://localhost:*" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/firma-embebida-en-tu-app.receta.spec.ts` --- Fuente: https://allsign.io/developers/docs/endpoints/constancias.md # Constancias Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). [Descargar OpenAPI](https://allsign.io/developers/docs/openapi.public.json)[Colección de Postman](https://allsign.io/developers/docs/allsign-api-v3.postman_collection.json) Endpoints - [POST`/constancias`](#emitir-constancia-nom-151) - [GET`/constancias/{constancia_id}`](#recuperar-una-constancia) - [GET`/constancias`](#listar-constancias) - [POST`/constancias/verify`](#verificar-una-constancia) - [GET`/constancias/certchain`](#raiz-de-confianza-para-verificar-offline) ## El objeto ConstanciaResponse #### Atributos - `livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `id` string requerido - `hashSha256` string requerido - `externalId` string requerido puede ser null - `label` string requerido puede ser null - `constancia` string requerido - `constanciaEncoding` string, siempre "base64" - `constanciaMediaType` string default "application/pkcs7-mime" - `constanciaSha256` string requerido - `serialNumber` string requerido puede ser null - `algorithm` string requerido puede ser null - `issuer` string requerido puede ser null - `sealedAt` string (fecha-hora ISO 8601) requerido puede ser null - `policyOid` string requerido puede ser null - `tsaName` string requerido puede ser null - `environment` string requerido - `sandbox` boolean requerido - `downloadUrl` string requerido - `downloadUrlExpiresAt` string (fecha-hora ISO 8601) requerido - `createdAt` string (fecha-hora ISO 8601) requerido **El objeto ConstanciaResponse** ``` { "livemode": false, "id": "cst_83c44d9ca7c7479cb88bd7b09c08f6ff", "hashSha256": "683cd72903d1381092c59cb519b999124fdd04954fcafb37cb1207f6bfba9e01", "externalId": null, "label": "v3audit-ejemplos factura F-1024", "constancia": "", "constanciaEncoding": "base64", "constanciaMediaType": "application/pkcs7-mime", "constanciaSha256": "714d26194f9cdaac8a55724ccf914a7ef5d524a4c0a761c13b5d606132d3660d", "serialNumber": "SANDBOX-CB59CC05CBAA4902", "algorithm": "sha-256", "issuer": "AllSign Sandbox — NOM-151 Mock (no validez legal)", "sealedAt": "2026-07-11T18:00:00Z", "policyOid": null, "tsaName": "AllSign Sandbox — NOM-151 Mock (no validez legal)", "environment": "test", "sandbox": true, "downloadUrl": "", "downloadUrlExpiresAt": "2026-07-11T18:15:01.274000Z", "createdAt": "2026-07-11T18:00:00.325000Z" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/constancias.ejemplos.spec.ts` ## Emitir constancia NOM-151 POST `/constancias` Emite una **Constancia de Conservación NOM-151** a partir del hash SHA-256 de tu documento. **Tu documento nunca sale de tu infraestructura.** No es una restricción nuestra: la NOM-151-SCFI-2016 lo **ordena**. Apéndice Normativo A, numeral A.2.4 — *«el Prestador de Servicios de Certificación únicamente recibirá la huella digital electrónica del mensaje de datos»*. Usa este endpoint si ya firmas por tu cuenta (e.firma del SAT, tu propio flujo) y solo necesitas la prueba legal del momento en que el documento existió. **El hash** va en hexadecimal minúsculas de 64 caracteres — exactamente lo que produce `sha256sum archivo.pdf`. Se aceptan mayúsculas y se normalizan; **no** se acepta base64 ni prefijos tipo `sha256:`. **`Idempotency-Key` es obligatoria.** Este endpoint cobra y llama a un PSC externo: sin la key, un timeout de red te deja sin forma segura de reintentar. Reintentar con la misma key devuelve la primera respuesta, nunca emite dos veces. **`externalId`** es tu propia referencia (el folio de tu expediente). Te sirve para recuperar la constancia después sin haber guardado nuestro `id`, y como segunda red contra el doble cobro. Reusar un `externalId` **con el mismo hash** devuelve la constancia ya emitida; reusarlo **con otro hash** devuelve `409` — la referencia ya está tomada por otro documento, y devolverte la constancia equivocada sería peor que fallar. **Facturación y entornos.** En `live` consume créditos y llama al PSC. Con una API key `test` o `dev` no cobra, no contacta al PSC y devuelve un artefacto **sin validez legal**, marcado con `sandbox: true` y `livemode: false`. **Requiere contrato.** El addon se activa por tenant tras firmar el contrato de prestación de servicio; sin él, `live` responde `403 CONTRACT_REQUIRED`. #### Parámetros - `Idempotency-Key` string header requerido Clave única (UUID v4) que vuelve idempotente el POST: un reintento con la misma clave repite la respuesta original en lugar de volver a crear, cobrar o notificar. - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `hashSha256` string requerido SHA-256 hex del documento. de 64 a 64 caracteres - `externalId` string puede ser null máx. 255 caracteres - `label` string puede ser null máx. 255 caracteres #### Devuelve **201** Constancia emitida. El artefacto viene **inline en base64** en `constancia`. — [el objeto ConstanciaResponse](#objeto-constanciaresponse) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [402](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [409](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) [503](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/constancias' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "hashSha256": "683cd72903d1381092c59cb519b999124fdd04954fcafb37cb1207f6bfba9e01", "label": "v3audit-ejemplos factura F-1024" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/constancias', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ hashSha256: '683cd72903d1381092c59cb519b999124fdd04954fcafb37cb1207f6bfba9e01', label: 'v3audit-ejemplos factura F-1024', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/constancias", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "hashSha256": "683cd72903d1381092c59cb519b999124fdd04954fcafb37cb1207f6bfba9e01", "label": "v3audit-ejemplos factura F-1024", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "cst_83c44d9ca7c7479cb88bd7b09c08f6ff", "hashSha256": "683cd72903d1381092c59cb519b999124fdd04954fcafb37cb1207f6bfba9e01", "externalId": null, "label": "v3audit-ejemplos factura F-1024", "constancia": "", "constanciaEncoding": "base64", "constanciaMediaType": "application/pkcs7-mime", "constanciaSha256": "714d26194f9cdaac8a55724ccf914a7ef5d524a4c0a761c13b5d606132d3660d", "serialNumber": "SANDBOX-CB59CC05CBAA4902", "algorithm": "sha-256", "issuer": "AllSign Sandbox — NOM-151 Mock (no validez legal)", "sealedAt": "2026-07-11T18:00:00Z", "policyOid": null, "tsaName": "AllSign Sandbox — NOM-151 Mock (no validez legal)", "environment": "test", "sandbox": true, "downloadUrl": "", "downloadUrlExpiresAt": "2026-07-11T18:15:00.856000Z", "createdAt": "2026-07-11T18:00:00.325000Z" } ``` **Respuesta · 409 · EXTERNAL_ID_CONFLICT** ``` { "type": "https://allsign.io/developers/docs/errors#EXTERNAL_ID_CONFLICT", "title": "Conflict", "status": 409, "detail": "externalId 'pedido-muzi0on9' already identifies a constancia for another document (hash 1872a4d06486…). Use a different externalId, or send the same hash to retrieve the one already issued.", "instance": "/v3/constancias", "code": "EXTERNAL_ID_CONFLICT", "requestId": "req_0d782805b9534c43bd20d8468e9b3ce2" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/constancias.ejemplos.spec.ts` ## Recuperar una constancia GET `/constancias/{constancia_id}` Devuelve una constancia que emitimos, con el **artefacto inline en base64** — así que basta con haber guardado el `id` (o tu `externalId`) para recuperarla meses después. **Archiva `constancia`, no `downloadUrl`.** La URL de descarga es una conveniencia con caducidad (`downloadUrlExpiresAt`); si guardas el JSON con la URL y vuelves después, te queda un enlace muerto. El base64 es la fuente de verdad y no expira. Los campos `sealedAt`, `serialNumber`, `policyOid` y `tsaName` se extraen **del interior del token**, donde están cubiertos por la firma — no del JSON que envuelve la respuesta del PSC. `sealedAt` es el `genTime` del sello: el instante con valor legal. Con `Accept: application/pkcs7-mime` la misma ruta devuelve el **DER crudo** en vez del JSON, listo para pasárselo a `openssl` sin decodificar nada. #### Parámetros - `constancia_id` string ruta requerido - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** La constancia, con el artefacto inline. — [el objeto ConstanciaResponse](#objeto-constanciaresponse) Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/constancias/cst_83c44d9ca7c7479cb88bd7b09c08f6ff' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/constancias/cst_83c44d9ca7c7479cb88bd7b09c08f6ff', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/constancias/cst_83c44d9ca7c7479cb88bd7b09c08f6ff", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "cst_83c44d9ca7c7479cb88bd7b09c08f6ff", "hashSha256": "683cd72903d1381092c59cb519b999124fdd04954fcafb37cb1207f6bfba9e01", "externalId": null, "label": "v3audit-ejemplos factura F-1024", "constancia": "", "constanciaEncoding": "base64", "constanciaMediaType": "application/pkcs7-mime", "constanciaSha256": "714d26194f9cdaac8a55724ccf914a7ef5d524a4c0a761c13b5d606132d3660d", "serialNumber": "SANDBOX-CB59CC05CBAA4902", "algorithm": "sha-256", "issuer": "AllSign Sandbox — NOM-151 Mock (no validez legal)", "sealedAt": "2026-07-11T18:00:00Z", "policyOid": null, "tsaName": "AllSign Sandbox — NOM-151 Mock (no validez legal)", "environment": "test", "sandbox": true, "downloadUrl": "", "downloadUrlExpiresAt": "2026-07-11T18:15:01.274000Z", "createdAt": "2026-07-11T18:00:00.325000Z" } ``` **Respuesta · 404 · CONSTANCIA_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#CONSTANCIA_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "No constancia was found with that id.", "instance": "/v3/constancias/cst_6d09d152ab804645b8884b7e20337328", "code": "CONSTANCIA_NOT_FOUND", "requestId": "req_c8a10a1a99bb4db09e4d9b3a9f42a9ef" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/constancias.ejemplos.spec.ts` ## Listar constancias GET `/constancias` Lista las constancias del tenant, más recientes primero, con paginación por cursor. **El listado NO trae el artefacto.** Devolver el base64 de hasta 100 constancias por página significaría descargarlas todas de nuestro almacenamiento en cada petición. Para el artefacto usa `GET /v3/constancias/{constanciaId}`, que trae uno. Sirve para **conciliar**: filtra por tu `externalId` para encontrar el expediente que buscas, o pagina el periodo completo para cuadrar lo emitido contra lo cobrado. Paginación: manda `startingAfter` con el `nextCursor` de la respuesta anterior. `hasMore` te dice si vale la pena pedir otra página. El listado está **acotado al entorno de tu API key**: una key `test` no ve las constancias `live` del tenant, ni al revés. #### Parámetros - `limit` integer query default 20 · entre 1 y 100 - `startingAfter` string query - `externalId` string query - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** Página de constancias (sin el artefacto — ver la nota arriba). Ver los 5 atributos de la respuesta - `object` string, siempre "list" Siempre `"list"`. - `data` array de objetos requerido Ver 14 atributos hijos - `data[].livemode` boolean requerido `true` en entorno `live`, `false` en `test`/`dev` (marca de agua, sin validez legal). - `data[].object` string, siempre "constancia" Siempre `"constancia"`. - `data[].id` string requerido - `data[].hashSha256` string requerido - `data[].externalId` string puede ser null - `data[].label` string puede ser null - `data[].constanciaSha256` string puede ser null - `data[].serialNumber` string puede ser null - `data[].sealedAt` string (fecha-hora ISO 8601) puede ser null - `data[].policyOid` string puede ser null - `data[].tsaName` string puede ser null - `data[].environment` string requerido - `data[].sandbox` boolean requerido - `data[].createdAt` string (fecha-hora ISO 8601) requerido - `hasMore` boolean requerido - `limit` integer requerido - `nextCursor` string puede ser null Errores posibles (problem+json): [400](https://allsign.io/developers/docs/errors) [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/constancias?limit=5' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/constancias?limit=5', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/constancias?limit=5", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "object": "constancia", "id": "cst_83c44d9ca7c7479cb88bd7b09c08f6ff", "hashSha256": "683cd72903d1381092c59cb519b999124fdd04954fcafb37cb1207f6bfba9e01", "externalId": null, "label": "v3audit-ejemplos factura F-1024", "constanciaSha256": "714d26194f9cdaac8a55724ccf914a7ef5d524a4c0a761c13b5d606132d3660d", "serialNumber": "SANDBOX-CB59CC05CBAA4902", "sealedAt": "2026-07-11T18:00:00Z", "policyOid": null, "tsaName": "AllSign Sandbox — NOM-151 Mock (no validez legal)", "environment": "test", "sandbox": true, "createdAt": "2026-07-11T18:00:00.325000Z" }, { "livemode": false, "object": "constancia", "id": "cst_d664e0f6c9bc440086ca777d83ad4899", "hashSha256": "6f1aeb27eb4962aa1b5b41517b25cd44e4dd8548652b9a189db51c77860d3f9d", "externalId": "pedido-muzftkgx", "label": "v3audit-ejemplos constancia original", "constanciaSha256": "e0739e3838e175ea2c1c090dda289bdf075a04bfe8a745cf35254075b901bd7c", "serialNumber": "SANDBOX-BC87121081EF45BA", "sealedAt": "2026-07-11T17:00:34Z", "policyOid": null, "tsaName": "AllSign Sandbox — NOM-151 Mock (no validez legal)", "environment": "test", "sandbox": true, "createdAt": "2026-07-11T17:00:34.535000Z" }, { "livemode": false, "object": "constancia", "id": "cst_9afc5eabbff2413fb9ebefc661c83efe", "hashSha256": "d768c518537eb60c11d8884bf3c2edbb72d97cf2178dcb49065e1d42ecdf4ad8", "externalId": null, "label": "v3audit-ejemplos factura F-1024", "constanciaSha256": "4ba5788195f551bfb27c17e8fa03e928df899cc06feb7edada1599b34efcef02", "serialNumber": "SANDBOX-BCA90D6F9CEB45A6", "sealedAt": "2026-07-11T16:58:23Z", "policyOid": null, "tsaName": "AllSign Sandbox — NOM-151 Mock (no validez legal)", "environment": "test", "sandbox": true, "createdAt": "2026-07-11T16:58:23.388000Z" }, { "livemode": false, "object": "constancia", "id": "cst_5e0a125652304f61b856a903160fc992", "hashSha256": "5abbbac063ea69840f916cdeeaa1e8328b4c4b18943bcc17cf5bf40e4e5b0462", "externalId": "pedido-muzan15b", "label": "v3audit-ejemplos constancia original", "constanciaSha256": "6acb30589bbe70a794a4717630ffe085d0b8e1dbb9b864504b922e80de372fcd", "serialNumber": "SANDBOX-70E660CAB89A4ED8", "sealedAt": "2026-07-11T14:35:31Z", "policyOid": null, "tsaName": "AllSign Sandbox — NOM-151 Mock (no validez legal)", "environment": "test", "sandbox": true, "createdAt": "2026-07-11T14:35:31.330000Z" }, { "livemode": false, "object": "constancia", "id": "cst_170be41c795b475b9ec34fcd09256129", "hashSha256": "1c4ea618a639817109a12a6f21d6d3fcd3039b275404b27235a9f83a22d35f2e", "externalId": null, "label": "v3audit-ejemplos factura F-1024", "constanciaSha256": "66d05b52d3f61e73ce60296b9ae5e490bf5d4b039a5ef92a84dc8e24e8c98957", "serialNumber": "SANDBOX-609299F0EAFB4A2B", "sealedAt": "2026-07-11T14:33:19Z", "policyOid": null, "tsaName": "AllSign Sandbox — NOM-151 Mock (no validez legal)", "environment": "test", "sandbox": true, "createdAt": "2026-07-11T14:33:19.564000Z" } ], "hasMore": true, "limit": 5, "nextCursor": "" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/constancias.ejemplos.spec.ts` ## Verificar una constancia POST `/constancias/verify` Corre los **cuatro** checks criptográficos sobre una constancia y devuelve el resultado por criterio. | Check | Qué prueba | |---|---| | `integrity` | El hash sellado dentro del token es el de tu documento | | `chainOfTrust` | El certificado firmante encadena a la raíz de **Secretaría de Economía** | | `cmsSignature` | La firma CMS del token es válida (RFC 5652) | | `certValidity` | El certificado estaba vigente **al momento del sellado** | Los cuatro son necesarios y ninguno implica a otro: un imprint correcto no dice nada de quién firmó, y una firma válida de un emisor cualquiera no acredita nada. **`status` es tri-estado, no un booleano** (ETSI EN 319 102-1): - `VALID` — los cuatro checks pasaron. - `INVALID` — afirmación fuerte: el artefacto está mal (el documento no es el que se selló, o el emisor no es acreditado). - `INDETERMINATE` — **no pudimos concluir**. Token ilegible, o un check que no se pudo completar. *No* significa que la constancia sea falsa. Esa distinción importa: colapsar `INDETERMINATE` en `INVALID` te haría reportar «tu constancia es falsa» cuando el problema es de nuestro lado. > **Esto es una conveniencia, no una autoridad.** AllSign no es PSC acreditado, y preguntarle al emisor si su propio artefacto es válido no constituye prueba ante un tercero. Para eso está la verificación independiente: baja la raíz en `/v3/constancias/certchain` y verifica con `openssl` por tu cuenta. El valor de este endpoint es que valida el certificado del PSC contra la raíz correcta, que es el paso que se suele equivocar. Puedes mandar `constancia` (base64) + `hashSha256`, **o** solo `constanciaId` si la emitimos nosotros — en ese caso el hash sale de nuestro registro. #### Parámetros - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Cuerpo de la petición application/json - `hashSha256` string puede ser null de 64 a 64 caracteres - `constancia` string puede ser null - `constanciaId` string puede ser null #### Devuelve **200** Resultado de la verificación. Revisa `status` **y** `checks`; un `INDETERMINATE` con `errors` te dice qué no se pudo comprobar. Ver los 12 atributos de la respuesta - `status` enum requerido Valores: `VALID` `INDETERMINATE` `INVALID` - `reason` string requerido - `hashSha256` string puede ser null - `checks` objeto puede ser null Resultado POR CRITERIO. Los cuatro checks son independientes. Se publican por separado a propósito: un `INVALID` sin desglose no le dice al cliente si el documento fue alterado (integridad) o si el emisor no está acreditado (cadena) — son problemas distintos con remedios distintos. Ver 4 atributos hijos - `checks.integrity` boolean requerido - `checks.chainOfTrust` boolean requerido - `checks.cmsSignature` boolean requerido - `checks.certValidity` boolean requerido - `sealedHash` string puede ser null - `sealedAt` string puede ser null - `serialNumber` string puede ser null - `policyOid` string puede ser null - `tsaName` string puede ser null - `signerSubject` string puede ser null - `signerIssuer` string puede ser null - `errors` array de string Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [404](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) **cURL** ``` curl -X POST 'https://api.allsign.io/v3/constancias/verify' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "constanciaId": "cst_83c44d9ca7c7479cb88bd7b09c08f6ff", "hashSha256": "683cd72903d1381092c59cb519b999124fdd04954fcafb37cb1207f6bfba9e01" }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/constancias/verify', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ constanciaId: 'cst_83c44d9ca7c7479cb88bd7b09c08f6ff', hashSha256: '683cd72903d1381092c59cb519b999124fdd04954fcafb37cb1207f6bfba9e01', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.post( "https://api.allsign.io/v3/constancias/verify", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "constanciaId": "cst_83c44d9ca7c7479cb88bd7b09c08f6ff", "hashSha256": "683cd72903d1381092c59cb519b999124fdd04954fcafb37cb1207f6bfba9e01", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "status": "INDETERMINATE", "reason": "Artefacto de sandbox (AllSign NOM-151 Mock): sin validez legal y no verificable por diseño. Emite con una API key de environment `live` para obtener una constancia real verificable.", "hashSha256": "683cd72903d1381092c59cb519b999124fdd04954fcafb37cb1207f6bfba9e01", "checks": { "integrity": false, "chainOfTrust": false, "cmsSignature": false, "certValidity": false }, "sealedHash": null, "sealedAt": null, "serialNumber": null, "policyOid": null, "tsaName": null, "signerSubject": null, "signerIssuer": null, "errors": [ "Artefacto de sandbox (AllSign NOM-151 Mock): sin validez legal y no verificable por diseño. Emite con una API key de environment `live` para obtener una constancia real verificable." ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/constancias.ejemplos.spec.ts` ## Raíz de confianza para verificar offline GET `/constancias/certchain` Devuelve en PEM el certificado raíz de la **Autoridad Certificadora Raíz Segunda de Secretaría de Economía** — el `-CAfile` que necesitas para verificar una constancia sin depender de AllSign. ⚠️ **`openssl ts -verify` rechaza una constancia NOM-151 con la invocación normal.** No es que la constancia esté mal: el certificado firmante **no lleva el Extended Key Usage `Time Stamping`** (solo `Digital Signature, Non Repudiation`), porque una constancia de conservación no es un sello de tiempo puro. Sin el flag correcto verás `unsuitable certificate purpose` y concluirás, equivocadamente, que la constancia es inválida. El flag es **`-purpose any`**. Y como SeguriData entrega un **token CMS pelado** (no un `TimeStampResp`), hace falta además **`-token_in`**. Si tu versión de `openssl` aun así se niega, `openssl cms -verify -purpose any` comprueba firma y cadena — **pero no compara el message imprint**, así que en ese camino tienes que extraer el `TSTInfo` y comparar el hash tú mismo. > Servimos esta raíz **por conveniencia, no como autoridad**: no somos PSC acreditado. La fuente autoritativa es la Secretaría de Economía; si tu proceso requiere rigor, bájala de ahí y compara. El archivo que servimos se valida contra su fingerprint SHA-256 antes de entregarse. #### Parámetros - `AllSign-Version` enum header Fecha del contrato contra la que quieres resolver la petición. Si la omites se sirve la versión vigente; una fecha desconocida responde 400 `UNSUPPORTED_API_VERSION`. Valores: `2026-07-11` #### Devuelve **200** El certificado raíz en PEM. Errores posibles (problem+json): [401](https://allsign.io/developers/docs/errors) [403](https://allsign.io/developers/docs/errors) [422](https://allsign.io/developers/docs/errors) [429](https://allsign.io/developers/docs/errors) [503](https://allsign.io/developers/docs/errors) **cURL** ``` curl 'https://api.allsign.io/v3/constancias/certchain' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` import { writeFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/constancias/certchain', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); writeFileSync('descarga.bin', Buffer.from(await respuesta.arrayBuffer())); ``` **Python** ``` import os from pathlib import Path import requests respuesta = requests.get( "https://api.allsign.io/v3/constancias/certchain", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) Path("descarga.bin").write_bytes(respuesta.content) ``` **Respuesta · 200** ``` ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:12 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/constancias.ejemplos.spec.ts` --- Fuente: https://allsign.io/developers/docs/quickstart.md # Quickstart: de cero a firma en 5 pasos Vas a crear tu primer documento, enviarlo a firma y firmarlo en el **sandbox**, sin cobros ni correos reales. Todo corre contra `https://api.allsign.io/v3` con una key de prueba (prefijo `allsign_test_sk_` o `allsign_dev_sk_`, según tu panel): los documentos se crean, los firmantes existen y los webhooks se disparan, pero nada sale al mundo real. La base URL es la **misma** en sandbox y en producción — la key decide el entorno (ver [Entornos](https://allsign.io/developers/docs/environments)). Cuando tu integración funcione aquí, solo cambias la key — el contrato es idéntico. ## Para agentes y asistentes de IA - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** El prefijo de la key decide el entorno: `allsign_test_sk_` / `allsign_dev_sk_` = sandbox, `allsign_live_sk_` = producción. Para pasar a producción solo cambias la key. → [Entornos](https://allsign.io/developers/docs/environments) - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) Las respuestas de esta página son las que dio la API en la última corrida de sus tests, en el entorno interno de AllSign con una key de sandbox. La receta [Lleva tu primer documento a firma](https://allsign.io/developers/docs/recetas/primer-documento-a-firma) hace este mismo recorrido paso a paso, en cURL, Node y Python. Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/ejemplos/usuarios.ejemplos.spec.ts` ## Paso 1 · Obtén tu API key de prueba Entra a tu panel de AllSign, ve a **Developers → API Keys** y genera una key de **entorno de prueba**. Reconócela porque el prefijo **no** es `live`: `allsign_test_sk_` o `allsign_dev_sk_` (según tu panel) — ambas operan en sandbox. Guárdala como secreto (nunca la subas a tu repo ni la pegues en el front); se envía en cada petición como `Authorization: Bearer`. ``` # Guárdala en una variable de entorno, no en el código export ALLSIGN_API_KEY="allsign_test_sk_tu_key_de_prueba" ``` Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). La key de sandbox y la de producción son distintas y no son intercambiables: una key de prueba jamás toca datos reales. ## Paso 2 · Verifica tu conexión y registra tu webhook Antes de crear nada, confirma que tu key es válida con una llamada barata: `GET /v3/users/me`. Devuelve tu usuario y tu tenant. Este endpoint es tu *ping* de autenticación: - **200** — la key sirve; ya estás dentro. - **401 `AUTHENTICATION_REQUIRED`** — falta la key o está mal escrita. `/v3/users/me` **nunca** responde `403`: cualquier key autenticada puede leer su propio usuario, sin importar sus scopes. Si ves 403 aquí, es un bug, no un problema de permisos. ``` curl https://api.allsign.io/v3/users/me \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` ``` { "livemode": false, "id": "usr_1fac1e055ae544ac9f9620b42b5ad98d", "email": "ana@ejemplo.com", "fullName": "Ana Torres", "tenantId": "ten_0831aa4e33224c3bbc71f396d736d34c", "orgId": "org_8f4afda0323e478985538d5ccee18821", "scopes": [ "document:read", "document:write", "document:delete", "signature:read", "webhook:read", "webhook:write", "webhook:delete", "constancia:read", "constancia:write", "embedded:write", "analytics:read", "user:read" ], "environment": "test", "authMode": "api_key" } ``` ### Registra tu endpoint de webhooks Para enterarte de cada firma y del cierre sin consultar en bucle, registra una URL HTTPS pública de tu servidor con `POST /v3/webhooks` y los eventos que quieres recibir. Hazlo antes de enviar el documento: el paso 5 espera los avisos en este endpoint. ``` curl https://api.allsign.io/v3/webhooks \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "url": "https://tu-servidor.com/webhooks/allsign", "events": ["signer.signed", "document.completed"] }' ``` ``` { "livemode": false, "id": "whe_faed666bbdc5444693acfac1655160a6", "object": "webhook_endpoint", "url": "https://ejemplo.com/webhooks/allsign", "events": [ "signer.signed", "document.completed" ], "description": "Mi primer contrato", "status": "enabled", "apiVersion": "2026-07-11", "environment": "test", "secretLast4": "S5w=", "createdAt": "2026-07-11T18:00:00.000000Z", "secret": "whsec_…" } ``` Guarda el `secret` (`whsec_…`): solo viene en esta respuesta y con él verificas cada entrega. ## Paso 3 · Crea un documento con sus firmantes Un documento nace en estado `draft`: existe, pero todavía no se envía a nadie. Lo creas con `POST /v3/documents` y, **en la misma petición**, le dices quién firma con `signers[]`: cada firmante lleva `name` y un canal de contacto, `email` **o** `phone` (para invitación por WhatsApp). Los firmantes se adjuntan al crear: la API v3 no tiene una operación para agregarlos después, así que un documento creado sin `signers[]` no se puede enviar. El documento sale de una de dos fuentes (`source`): | `source` | Qué mandas | Notas | | --- | --- | --- | | `template` | `templateId` + `templateValues` (los valores de las variables) | El más rápido: reusas una plantilla ya diseñada. Si la plantilla define roles, agrega `roleName` a cada firmante para que herede los campos de su rol. | | `file` | El PDF en `base64` (`content` + `name`) | El archivo pesa **≤ 10 MB**; si lo excedes → `413 DOCUMENT_TOO_LARGE`. | Desde plantilla (rellenas `templateValues`): ``` curl https://api.allsign.io/v3/documents \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "source": "template", "templateId": "tmpl_...", "name": "Mi primer contrato (sandbox)", "templateValues": { "arrendatario": "Ana Torres", "monto": "12000" }, "signers": [ { "name": "Ana Torres", "email": "signer-pending@sandbox.allsign.io" } ] }' ``` Desde archivo (este comando lee `contrato.pdf` de tu carpeta y lo manda en base64): ``` curl https://api.allsign.io/v3/documents \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d @- <", "sha256": "8a6bf4a711f5204bcce37981400b4a4030d969b33d24e7f2ad78748ca1e68725" }, "nom151": null, "reason": null, "signedCount": 1, "totalSigners": 1 } ``` Responde `available: true` y, en `evidencePdf.url`, un enlace temporal para descargar el PDF con su `sha256`; mientras el documento no termina, `available` es `false` y `reason` dice por qué. Verifica la firma del webhook (Standard Webhooks) antes de confiar en el cuerpo (ver [Webhooks](https://allsign.io/developers/docs/webhooks)). ## Qué sigue - [Autenticación](https://allsign.io/developers/docs/authentication) — prefijos de key, scopes por recurso y el contrato de errores 401/403. - [Paginación](https://allsign.io/developers/docs/pagination) — cómo recorrer listas con cursores opacos. - [Idempotencia](https://allsign.io/developers/docs/idempotency) — qué POST la requieren y cómo reintentar sin duplicar. - [Errores](https://allsign.io/developers/docs/errors) — el contrato `problem+json` y el catálogo de códigos. - [Endpoints de Documents](https://allsign.io/developers/docs/endpoints/documents) — la referencia completa de cada operación. Cuando tu flujo pase en sandbox, cambia tu key de prueba por la de producción. La base URL es la misma (`https://api.allsign.io/v3`): la key decide el entorno. El contrato no cambia. --- Fuente: https://allsign.io/developers/docs/authentication.md # Autenticación La API v3 se autentica con una **API key** enviada como *bearer token*. Cada key trae un conjunto de **scopes** que definen qué recursos y qué acciones puede tocar. Sin key válida obtienes `401`; con key válida pero sin el scope necesario, `403`. Todo error es un documento `application/problem+json`. ## Bearer token Manda tu key en el header `Authorization` con el esquema `Bearer` en cada petición. Nunca la pongas en la URL ni en el cuerpo, y nunca la expongas en código de cliente. ``` Authorization: Bearer allsign_{env}_sk_... ``` ``` curl https://api.allsign.io/v3/documents \ -H "Authorization: Bearer allsign_test_sk_..." ``` ## Prefijos de key El prefijo de la key te dice, a simple vista, en qué entorno estás operando. El segmento `{env}` es `live` (producción), `test` o `dev` (ambos sandbox — cuál te toca depende de tu panel): | Prefijo | Entorno | Efecto | | --- | --- | --- | | `allsign_test_sk_…` | Sandbox | Cero cobros, cero correos reales. Ideal para desarrollo y CI. | | `allsign_dev_sk_…` | Sandbox | Igual que `test`: cero cobros, cero correos reales. `AllSign-Environment` reporta `dev`. | | `allsign_live_sk_…` | Producción | Documentos, cobros y notificaciones reales. | `sk` = *secret key*: es un secreto de servidor. Trátala como una contraseña, rótala si se filtra, y no la subas al repositorio. ## Scopes por recurso Los scopes tienen la forma `recurso:acción`. Una key solo puede hacer lo que sus scopes permiten. La taxonomía está **congelada y es append-only**: nunca renombramos ni quitamos un scope; solo agregamos nuevos. Así, un cliente que programa contra un scope no se rompe. | Scope | Permite | | --- | --- | | `document:read` | Listar y leer documentos, firmantes, eventos y evidencia. También gobierna la lectura de **plantillas**, **carpetas** y *signer sets*. | | `document:write` | Crear, actualizar, enviar y anular (*void*) documentos. También crear, cambiar y borrar plantillas, carpetas y *signer sets*. | | `document:delete` | Eliminar documentos en lote (*bulk delete*). | | `signature:read` | Está en la taxonomía, pero hoy ninguna ruta de v3 lo exige: el estado de firma se lee con `document:read`. | | `analytics:read` | Leer KPIs, embudos y métricas. | | `user:read` | Leer los miembros del equipo (`GET /v3/users/me` no lo necesita). | | `embedded:write` | Crear y consultar sesiones de firma embebida (embedded signing). | | `constancia:read` | Listar y leer constancias NOM-151 independientes. | | `constancia:write` | Emitir constancias NOM-151 independientes (cobra créditos). | | `webhook:read` | Listar y leer webhooks. | | `webhook:write` | Crear y actualizar webhooks (incluye rotar el secreto). | | `webhook:delete` | Eliminar webhooks. | Existen **wildcards** para conveniencia: - `recurso:*` — todas las acciones sobre un recurso (p.ej. `document:*` = read + write + delete). - `*` — acceso total (úsalo con extremo cuidado; prefiere el mínimo privilegio). ## Sin key: 401 Si no mandas key, o la key es inválida o revocada, la API responde `401 AUTHENTICATION_REQUIRED` y agrega el header estándar `WWW-Authenticate: Bearer`. Esto significa "autentícate", no "no tienes permiso". ``` HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer Content-Type: application/problem+json { "type": "https://allsign.io/developers/docs/errors#AUTHENTICATION_REQUIRED", "title": "Authentication required", "status": 401, "code": "AUTHENTICATION_REQUIRED", "detail": "Provide an API key via Authorization: Bearer allsign_live_sk_…", "requestId": "req_..." } ``` ## Sin scope: 403 con requiredScope Si tu key es válida pero le falta el scope que el endpoint exige, obtienes `403 PERMISSION_DENIED`. El error te dice **exactamente** qué scope necesitas (`requiredScope`) y cuáles tiene tu key (`yourScopes`), para que corrijas sin adivinar. ``` HTTP/1.1 403 Forbidden Content-Type: application/problem+json { "type": "https://allsign.io/developers/docs/errors#PERMISSION_DENIED", "title": "Permission denied", "status": 403, "code": "PERMISSION_DENIED", "detail": "This API key is missing the 'document:write' scope.", "requiredScope": "document:write", "yourScopes": ["document:read", "user:read"], "requestId": "req_..." } ``` `GET /v3/users/me` es la excepción: **nunca** responde `403`. Cualquier key autenticada puede leer su propio usuario, sin importar sus scopes — úsalo como *health check* de autenticación. ## OAuth 2.1 (próximamente) Hoy la única forma de autenticarse es la API key (bearer). **OAuth 2.1** (para apps de terceros que actúan en nombre de un usuario) está en el roadmap pero no está habilitado en producción. Si presentas un token OAuth (con forma de JWT) como bearer en `https://api.allsign.io/v3`, la API responde `501 OAUTH_NOT_IMPLEMENTED`, para que tu integración distinga "todavía no existe" de "salió mal". El spec declara un esquema `oauth2` con URLs de autorización y de token en `auth.allsign.io`. Ese host todavía no existe: no conectes un cliente generado a esas URLs. Cuando OAuth se habilite, esta sección dirá cómo obtener un token. ``` HTTP/1.1 501 Not Implemented Content-Type: application/problem+json { "type": "https://allsign.io/developers/docs/errors#OAUTH_NOT_IMPLEMENTED", "title": "Not Implemented", "status": 501, "code": "OAUTH_NOT_IMPLEMENTED", "detail": "This credential looks like an OAuth token. AllSign v3 currently accepts API keys only (Authorization: Bearer allsign_live_sk_…). OAuth (Ory Hydra) is a fast-follow; see https://allsign.io/developers/docs/authentication.", "requestId": "req_..." } ``` ## Formato de error (problem+json) Todo error de autenticación (y todo error de la API) es un documento **RFC 9457 `application/problem+json`**. Ramifica tu lógica por el campo `code` (estable, append-only), **nunca** por `detail` (texto en inglés que puede cambiar) ni por el `title`. | Campo | Qué es | | --- | --- | | `type` | URI que ancla al código en la página de [Errores](https://allsign.io/developers/docs/errors). | | `title` | Resumen humano corto (inglés). | | `status` | El código HTTP, repetido en el cuerpo. | | `detail` | Explicación específica de esta instancia (inglés; puede cambiar). | | `instance` | La ruta que causó el error. | | `code` | **Tu punto de ramificación**: identificador estable (p.ej. `PERMISSION_DENIED`). | | `requestId` | El `req_…` para correlacionar con nuestros logs. | | `errors` | Arreglo de errores a nivel de campo (poblado en validación `422`). | ## Headers en cada respuesta `AllSign-Request-Id` viaja en **toda** respuesta. Los headers de entorno y *rate limiting* viajan en toda respuesta **autenticada** (incluidos `403` y `429`) — un `401` sin credenciales no los trae: | Header | Qué te dice | | --- | --- | | `AllSign-Request-Id` | El `req_…` de esta petición (igual a `requestId`). Cítalo al reportar problemas. | | `AllSign-Environment` | `live`, `test` o `dev`: confirma en qué entorno respondió. | | `RateLimit-Limit` | El tope de tu ventana actual. | | `RateLimit-Remaining` | Cuántas peticiones te quedan en la ventana. | | `RateLimit-Reset` | Segundos (delta, no epoch) que faltan para tener cupo seguro. | | `Retry-After` | Presente en `429`: cuántos segundos esperar antes de reintentar. Solo el `429` del recordatorio a firmante no lo trae (ver [Rate limits](https://allsign.io/developers/docs/rate-limits#el-429)). | --- Fuente: https://allsign.io/developers/docs/pagination.md # Paginación Todos los endpoints de lista de la API v3 usan **paginación por cursor**. Los cursores son **opacos**: son cadenas que solo AllSign entiende. No los construyas, no los decodifiques y no infieras nada de su contenido — solo pásalos de vuelta tal cual. Un cursor malformado o adivinado devuelve `400 INVALID_CURSOR`. ## El sobre de lista Toda lista responde con el mismo **sobre (envelope)**: `object: "list"`, el arreglo `data`, y los metadatos de paginación. Nunca recibes un arreglo pelón. ``` { "object": "list", "data": [ /* ... los recursos de esta página ... */ ], "hasMore": true, "nextCursor": "djF8Y3JlYXRlZEF0fC4uLg", "previousCursor": null, "limit": 20, "totalCount": null } ``` | Campo | Qué es | | --- | --- | | `object` | Siempre `"list"`. | | `data` | Los recursos de esta página, en orden. | | `hasMore` | `true` si hay más páginas después de esta. **Tu señal de fin de loop.** | | `nextCursor` | Cursor opaco para la página siguiente (o `null` si no hay). | | `previousCursor` | Cursor opaco para la página anterior (o `null`). | | `limit` | El tamaño de página efectivo que se aplicó. | | `totalCount` | El total de la colección — `null` salvo que pidas `includeTotal=true`. | ## Parámetros | Parámetro | Qué hace | | --- | --- | | `limit` | Recursos por página. Rango **1–100**, **default 20**. | | `startingAfter` | Cursor: trae la página *después* de este punto (avanzar). | | `endingBefore` | Cursor: trae la página *antes* de este punto (retroceder). | `startingAfter` y `endingBefore` son **mutuamente excluyentes**: si mandas ambos, obtienes `422 VALIDATION_ERROR`. Elige una dirección por petición. Al paginar, **mantén fijos el orden y los filtros** entre peticiones. Un cursor está atado al conjunto de `sort` + filtros con el que lo generaste; cambiarlos a media paginación produce resultados inconsistentes. Y recuerda: si el cursor no es válido para esa consulta, la API responde `400 INVALID_CURSOR`. ## Recorrer todas las páginas El patrón correcto es un bucle `while` guiado por `hasMore`: mientras sea `true`, pasa el `nextCursor` de la respuesta como `startingAfter` de la siguiente petición. **No** uses `totalCount` para decidir cuándo parar. JavaScript: ``` async function listAll() { const out = []; let cursor = null; let hasMore = true; while (hasMore) { const url = new URL("https://api.allsign.io/v3/documents"); url.searchParams.set("limit", "100"); if (cursor) url.searchParams.set("startingAfter", cursor); const res = await fetch(url, { headers: { Authorization: "Bearer " + process.env.ALLSIGN_API_KEY, "AllSign-Version": "2026-07-11" }, }); const page = await res.json(); out.push(...page.data); hasMore = page.hasMore; cursor = page.nextCursor; } return out; } ``` Python: ``` import os, requests def list_all(): out, cursor = [], None while True: params = {"limit": 100} if cursor: params["startingAfter"] = cursor r = requests.get( "https://api.allsign.io/v3/documents", headers={"Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11"}, params=params, ) page = r.json() out.extend(page["data"]) if not page["hasMore"]: break cursor = page["nextCursor"] return out ``` cURL (una página; encadena manualmente con el `nextCursor` devuelto): ``` # Primera página curl "https://api.allsign.io/v3/documents?limit=100" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" # Siguiente página: pega el nextCursor de la respuesta anterior curl "https://api.allsign.io/v3/documents?limit=100&startingAfter=djF8Y3JlYXRlZEF0fC4uLg" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` Regla de oro: el loop termina cuando `hasMore === false`, no cuando llegas a un total. Así funciona aunque la colección crezca mientras paginas. ## Conteo total `totalCount` es `null` por default. Solo se calcula si pides `includeTotal=true`, y ese cálculo es **caro** (escanea la colección completa). Pídelo únicamente cuando de verdad necesites mostrar un total (p.ej. "1 284 documentos" en una UI), y **nunca** lo uses como condición para terminar tu loop de paginación — para eso está `hasMore`. ``` curl "https://api.allsign.io/v3/documents?limit=20&includeTotal=true" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` ``` { "object": "list", "data": [ /* ... */ ], "hasMore": true, "nextCursor": "djF8Y3JlYXRlZEF0fC4uLg", "limit": 20, "totalCount": 1284 } ``` --- Fuente: https://allsign.io/developers/docs/idempotency.md # Idempotencia El header `Idempotency-Key` hace que reintentar un `POST` mutante sea **seguro**: si tu red se cae después de mandar la petición pero antes de recibir la respuesta, reintentas con la misma key y AllSign te devuelve el resultado original en vez de crear un segundo documento (o cobrar dos veces). Usa un **UUID v4** nuevo por cada operación lógica. ## Para agentes y asistentes de IA - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** Sin ella la API responde `400 IDEMPOTENCY_KEY_REQUIRED`; al reintentar la *misma* operación reutiliza la *misma* key y recibes la primera respuesta, sin doble cobro ni doble invitación. ``` Idempotency-Key: $(uuidgen) ``` ## Qué POST la requieren La `Idempotency-Key` es **requerida** en los `POST` que crean, cobran o mandan algo a una persona. Sin ella la respuesta es `400 IDEMPOTENCY_KEY_REQUIRED`: | Operación | Idempotency-Key | | --- | --- | | `POST /v3/documents` | **Requerida** | | `POST /v3/documents/{id}/send` | **Requerida** | | `POST /v3/documents/{id}/void` | **Requerida** | | `POST /v3/documents/{id}/annexes` | **Requerida** | | `POST /v3/documents/{id}/signers/{signerId}/remind` | **Requerida** | | `POST /v3/documents/{id}/signers/remind-all` | **Requerida** | | `POST /v3/documents/{id}/signers/{signerId}/reassign` | **Requerida** | | `POST /v3/documents/bulk-sends` | **Requerida** | | `POST /v3/templates/{id}/documents` | **Requerida** | | `POST /v3/signing-sessions` | **Requerida** | | `POST /v3/constancias` | **Requerida** | | `POST /v3/webhooks` y `POST /v3/webhooks/{id}/rotate-secret` | Opcional | | Altas de plantillas, campos y *signer sets* (`POST /v3/templates`, `…/fields`, `…:duplicate`, `…:from-document`, `POST /v3/signer-sets`…) | Opcional | **Opcional** quiere decir que si la mandas se honra igual (un reintento devuelve la primera respuesta), y si no la mandas la petición se procesa sin protección contra duplicados. Los recordatorios (`remind` y `remind-all`) la exigen aunque además tienen su propio freno de **un recordatorio cada 4 horas** por firmante: la llave evita que un reintento de red mande dos correos; el freno evita que tu código insista de más. No la usan: - `GET` — ya son idempotentes por naturaleza. - `PATCH` y `DELETE` — idempotentes por definición (el estado final es el mismo). - Los `POST` que no la declaran: `POST /v3/folders`, `POST /v3/documents/{id}/fields:assign-role`, `POST /v3/templates/{id}/validate-values`, `POST /v3/constancias/verify`, `POST /v3/devices/enroll` y el `init` de una sesión de firma. ## Cómo funciona La primera vez que llega una key, AllSign procesa la petición normalmente y **guarda la respuesta** asociada a esa key. Si vuelve a llegar la *misma* key con el *mismo* cuerpo, no reprocesa: te devuelve la respuesta guardada **íntegra** (mismo status, mismo body), con el header `Idempotency-Replayed: true` para que sepas que fue un replay. ``` HTTP/1.1 201 Created Idempotency-Replayed: true Content-Type: application/json { "id": "doc_...", "status": "draft" } ``` Para decidir si dos peticiones son "la misma", AllSign calcula un **fingerprint sobre los bytes crudos** del cuerpo (no sobre el JSON normalizado). Reintenta con exactamente el mismo payload que enviaste la primera vez — un cambio de espacios o de orden de campos cuenta como cuerpo distinto. ## Respuestas de conflicto | Código | Cuándo | Qué hacer | | --- | --- | --- | | `409 IDEMPOTENCY_KEY_REUSED` | Reusaste una key con una **petición distinta** (cuerpo o query string) en el mismo endpoint. | Usa una key nueva para la petición nueva; no mezcles operaciones bajo una misma key. | | `409 IDEMPOTENCY_KEY_IN_PROGRESS` | La petición original **sigue procesándose**. | Espera y reintenta; trae `retryAfter` (segundos) para saber cuánto. | | `400 IDEMPOTENCY_KEY_REQUIRED` | El endpoint la exige y **no la mandaste**. | Agrega el header `Idempotency-Key` con un UUID v4. | | `400 IDEMPOTENCY_KEY_INVALID` | La key no tiene el **formato** esperado (UUID v4). | Genera un UUID v4 válido. | ``` HTTP/1.1 409 Conflict Content-Type: application/problem+json { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_IN_PROGRESS", "title": "Conflict", "status": 409, "code": "IDEMPOTENCY_KEY_IN_PROGRESS", "detail": "A request with this Idempotency-Key is still in progress.", "retryAfter": 2, "requestId": "req_..." } ``` ## Qué se cachea y por cuánto No todas las respuestas se guardan para replay. Solo se cachean los resultados **determinísticos**: si reintentar puede dar un resultado distinto, no tiene sentido guardar el anterior. | Respuesta | ¿Se cachea? | Por qué | | --- | --- | --- | | `2xx` (éxito) | **Sí** | El resultado es final; reintentar debe devolver lo mismo. | | `4xx` de validación determinista (p.ej. `422`) | **Sí** | El mismo cuerpo fallará igual; se replica el error. | | `402` (pago requerido) | No | Transitorio: reintentar tras arreglar el pago debe poder tener éxito. | | `429` (rate limited) | No | Transitorio: reintentar más tarde debe funcionar. | | `5xx` (error del servidor) | No | Transitorio: reintentar puede tener éxito. | Las keys se **retienen 24 horas**. Dentro de esa ventana, reintentar con la misma key te devuelve la respuesta guardada; después, esa key se libera y una petición nueva se procesa desde cero. Genera una key por operación lógica y no la reutilices para operaciones diferentes. --- Fuente: https://allsign.io/developers/docs/versioning.md # Versionado de la API La v3 versiona en **dos ejes independientes**: el *major* vive en la ruta (`/v3`) y los cambios de comportamiento dentro de v3 se seleccionan con un header **fechado**, `AllSign-Version`. Fijas una fecha, congelas el comportamiento. ## Dos ejes de versión No mezcles los dos ejes: el *major* solo se mueve ante un cambio que rompe (breaking); todo lo demás —campos nuevos, defaults, correcciones de comportamiento— se entrega como una **versión fechada** dentro del mismo major, y tú eliges cuándo adoptarla. | Eje | Dónde | Cuándo cambia | Ejemplo | | --- | --- | --- | --- | | **Major** | En la ruta: `/v3/...` | Solo ante un *breaking change* (se corta compatibilidad) | `/v3` → `/v4` | | **Versión fechada** | Header `AllSign-Version` | Cambios de comportamiento *dentro* de v3 | `2026-07-11` | La versión fechada activa es `2026-07-11` — el mismo valor que `info.version` del OpenAPI y que el campo `apiVersion` que devuelve la API. ## El header AllSign-Version Manda la fecha en el header `AllSign-Version`. El nombre va en **Hyphenated-Pascal-Case** y **sin prefijo `X-`** (los headers con `X-` están deprecados por el RFC 6648). El valor es una fecha ISO `YYYY-MM-DD`. ``` curl "https://api.allsign.io/v3/documents?limit=20" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` Fijar `AllSign-Version: 2026-07-11` te garantiza el mismo contrato mientras exista esa versión, aunque publiquemos versiones fechadas más nuevas después. ## Si falta o es desconocida Dos caminos, según lo que mandes: - **Omites el header** → la petición corre contra la **única versión de hoy** (`2026-07-11`). Cómodo para explorar; en producción conviene fijarla explícita para no moverte solo cuando salga una nueva. - **Mandas una fecha desconocida o inválida** (formato malo, o una versión que no existe) → `400` `UNSUPPORTED_API_VERSION`. Fallamos cerrado: nunca adivinamos a qué versión te referías. ``` HTTP/1.1 400 Bad Request Content-Type: application/problem+json { "type": "https://allsign.io/developers/docs/errors#UNSUPPORTED_API_VERSION", "title": "Unsupported API version", "status": 400, "detail": "Unknown AllSign-Version '2019-01-01'. Supported: 2026-07-11.", "code": "UNSUPPORTED_API_VERSION", "requestId": "req_..." } ``` ### Escribe clientes tolerantes Aun fijando la versión, tu cliente debe ser *forward-compatible* para que las adiciones no te rompan: - **Ignora los campos que no conozcas** en las respuestas — agregar un campo no es un breaking change. - **Tolera valores nuevos en los enums de respuesta** marcados `x-extensible-enum` (como `DocumentStatus` y `SignerStatus`): pueden aparecer estados nuevos sin cambiar de versión. Maneja el *default* con un caso genérico, no con un `switch` exhaustivo que truene ante lo desconocido. ### Deprecación Cuando algo se depreque, lo anunciaremos por headers estándar: `Deprecation` y `Sunset` (RFC 9745 / RFC 8594) más un `Link` a la guía de migración. Hoy **no hay nada deprecado** en v3 — pero instrumenta tu cliente para loguear esos headers y enterarte a tiempo. ## Confirmar la versión activa `GET /v3/healthz` te devuelve la versión fechada activa en el campo `apiVersion`. Úsalo como *smoke check* al arrancar o en CI para confirmar contra qué contrato estás corriendo. ``` GET /v3/healthz { "status": "ok", "apiVersion": "2026-07-11", "requestId": "req_..." } ``` El valor de `apiVersion` siempre coincide con `info.version` del OpenAPI y con lo que fijas en `AllSign-Version`. --- Fuente: https://allsign.io/developers/docs/environments.md # Entornos: live y sandbox Hay **dos mundos** completamente aislados: producción y sandbox. La **API key** que uses decide en cuál caes — no hay un toggle aparte, y la base URL es la misma. Lo que creas con una key de prueba nace de prueba y así se queda para siempre. ## Para agentes y asistentes de IA - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** El prefijo de la key decide el entorno: `allsign_test_sk_` / `allsign_dev_sk_` = sandbox, `allsign_live_sk_` = producción. Para pasar a producción solo cambias la key. ## Los dos entornos | | Live (producción) | Sandbox | | --- | --- | --- | | **Prefijo de la key** | `allsign_live_sk_` | `allsign_test_sk_` o `allsign_dev_sk_` (según tu panel) | | **Cobros** | Reales (consumen tu saldo) | Ninguno | | **Emails / notificaciones** | Se envían de verdad | Simulados (no salen al firmante) | | **Firma y NOM-151** | Validez legal plena | Simulada, con *watermark*, sin validez legal | | `livemode` | `true` | `false` | El campo `environment` tiene **tres valores posibles**: `live`, `test` y `dev`. Los últimos dos son sabores del mismo mundo sandbox — cuál te toca depende de cómo tu panel emitió la key; ambos operan igual (cero cobros, `livemode: false`). Usa tu key de prueba para desarrollar e integrar sin riesgo; cambia a `allsign_live_sk_` cuando estés listo para documentos con efectos reales. ## Sandbox (test) El sandbox reproduce el flujo completo —crear, enviar, firmar, webhooks— pero **todo es simulado**: no se cobra, los correos no salen al firmante real, y los PDFs firmados llevan un *watermark* que deja claro que **no tienen validez legal**. Es el lugar para armar y probar tu integración de punta a punta antes de tocar producción. Todo recurso creado en sandbox trae `livemode: false`: ``` { "id": "doc_3f2a...", "status": "awaiting_signatures", "livemode": false, "createdAt": "2026-07-18T15:04:00Z" } ``` ### Firmantes mágicos Como los correos del sandbox son simulados, un firmante normal **nunca va a firmar**. Para recorrer el ciclo completo, el sandbox reserva tres direcciones *mágicas* (estilo Twilio): dos se manejan solas y la tercera espera a que tú la firmes. | Dirección | Qué hace | Webhooks que dispara | | --- | --- | --- | | `signer-success@sandbox.allsign.io` | Firma automáticamente al recibir la invitación. | `signer.signed` y, si era el último firmante, `document.completed`. | | `signer-declined@sandbox.allsign.io` | Rechaza automáticamente. | `signer.declined`. | | `signer-pending@sandbox.allsign.io` | No firma solo: queda pendiente hasta que llames a [`POST /v3/sandbox/signers/{signerId}/sign`](https://allsign.io/developers/docs/endpoints/documents#sign-as-a-signer-sandbox-only). | Al firmarlo, `signer.signed` y, si era el último firmante, `document.completed`. | Cuál usar: - **`signer-success`** para el camino feliz sin intervenir: envías y el documento se completa solo. Es la que usa el [Quickstart](https://allsign.io/developers/docs/quickstart) para que su paso 5 (el webhook de completado) llegue de verdad. - **`signer-declined`** para probar cómo reacciona tu integración a un rechazo. - **`signer-pending`** cuando necesitas ver el documento **esperando firmas** (`awaiting_signatures`) —tu pantalla de pendientes, recordatorios, reasignaciones— y decidir tú en qué momento firma. Ninguna dirección `@sandbox.allsign.io` recibe correo, en ningún entorno: AllSign descarta esos envíos. Fuera del sandbox, además, no firman ni rechazan solas, y la firma simulada responde `409 ENVIRONMENT_MISMATCH` con una key live. No las uses como firmantes de un documento `live`: su invitación por correo nunca saldría. ## El entorno de la key marca el documento Al **crear** un recurso, el entorno de la key se **estampa permanente** en él. Un documento creado con una key de prueba queda en **sandbox para siempre** — no se "promueve" ni se convierte. Los **listados nunca se cruzan**: `GET /v3/documents` con una key `live` solo devuelve documentos `live`, y viceversa. Para saber de qué entorno es un recurso concreto, lee su campo `livemode` — viaja en cada respuesta. **Para pasar a producción no conviertes tus documentos de prueba.** Simplemente **empiezas a crear con la key `live`**; los de prueba se quedan en sandbox y ya. Trátalos como datos desechables. ## Cómo saber en qué entorno estás Tres señales, redundantes a propósito, te dicen el entorno sin adivinar: 1. **`livemode` en cada recurso** — `true` = live, `false` = sandbox. La señal más directa. 2. **`GET /v3/users/me`** — devuelve `environment` (`"live"` / `"test"` / `"dev"`) y `livemode` de la key con la que preguntas. 3. **El header `AllSign-Environment`** — viene en cada respuesta autenticada. ``` GET /v3/users/me { "id": "usr_9a1c...", "email": "tu@empresa.mx", "environment": "test", "livemode": false } ``` ``` HTTP/1.1 200 OK AllSign-Environment: test AllSign-Request-Id: req_... ``` Y en **webhooks** hay dos señales. El *envelope* v3 del evento trae `livemode` en el cuerpo, para que tu handler distinga sin depender de qué endpoint lo disparó: ``` { "eventId": "evt_7f3a...", "eventType": "document.completed", "apiVersion": "2026-07-11", "occurredAt": "2026-07-18T15:04:00.000Z", "tenantId": "…", "livemode": true, "data": { "documentId": "doc_2b9c...", "status": "completed" } } ``` Además, **cada entrega** de webhook —de cualquier cohorte— lleva la cabecera `AllSign-Livemode: true|false`. Para los endpoints clásicos (formato v2, ver [Webhooks](https://allsign.io/developers/docs/webhooks)) es la **única** señal de entorno: su cuerpo está congelado byte a byte y no trae el campo `livemode`. --- Fuente: https://allsign.io/developers/docs/rate-limits.md # Límites de tasa (rate limits) El límite es **por API key**, en una ventana deslizante de 60 segundos, y lo cuenta un contador que comparten todas las réplicas de la API. Cada respuesta autenticada —incluidas las `4xx` de scope o de validación, y hasta la de un cuerpo que no es JSON válido— cuenta, y te dice cuánto te queda y cuánto esperar, así que no tienes que adivinar. ## Para agentes y asistentes de IA - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** Con una API key válida, todo `429` de la v3 es problem+json `RATE_LIMITED`, y el de un límite agotado trae `Retry-After` y `retryAfter` en segundos, iguales a `RateLimit-Reset`: reintentar antes solo junta más `429`. El único sin `Retry-After` es el del recordatorio a firmante, que se libera cada 4 horas. ## Headers en cada respuesta Mandamos **dos formatos juntos** para máxima compatibilidad: el trío clásico y el par del *draft* IETF, más el entorno. ``` HTTP/1.1 200 OK RateLimit-Limit: 100 RateLimit-Remaining: 98 RateLimit-Reset: 60 RateLimit: "default";r=98;t=60 RateLimit-Policy: "default";q=100;w=60 AllSign-Environment: live ``` | Header | Qué es | | --- | --- | | `RateLimit-Limit` | Techo de peticiones en la ventana. | | `RateLimit-Remaining` | Cuántas te quedan en la ventana actual. | | `RateLimit-Reset` | **Segundos-delta** (NO epoch): cuánto esperar para tener cupo seguro. Vale lo que dura la ventana de la política (`60`, o `300` en el enrolamiento de tabletas) y a veces un segundo menos: como la ventana es deslizante, es el techo de la espera, no el instante exacto en que se libera la siguiente petición. | | `RateLimit` | Header *draft* IETF combinado: el nombre de la política, `r` = remaining y `t` = segundos al reinicio. El techo viaja en `RateLimit-Policy`. | | `RateLimit-Policy` | La política que te está midiendo: `"default";q=100;w=60` = la de tu plan, con cuota de 100 por ventana de 60 s. Las demás políticas están en [Endpoints con límite propio](#endpoints-con-limite-propio). | | `AllSign-Environment` | `live`, `test` o `dev`. | El *bucket* es **por API key**, no por IP: varias IPs o varios *workers* con la misma key comparten el cupo, y cada key del tenant tiene el suyo. El contador vive en un almacén compartido por todas las réplicas de la API, así que `RateLimit-Remaining` baja de uno en uno aunque cada petición la atienda una réplica distinta. Si ese almacén deja de responder, cada proceso cuenta por su lado mientras vuelve; por eso la última palabra la tiene el `429` con su `Retry-After`. ### Cuánto te toca El techo viene de tu **plan de suscripción activo** y aplica igual a las llaves `dev`, `test` y `live` del mismo tenant. | Plan | Límite | | --- | --- | | Sin plan activo | 10 req/min | | Freemium | 10 req/min | | Esencial · Colabora · Conecta | 100 req/min | | Business | 150 req/min | Si tu suscripción tiene un `api_rpm_override` acordado con nosotros, ese valor gana sobre el del plan. ### Endpoints con límite propio Algunas rutas tienen además un **tope propio**, porque cada llamada cuesta bastante más que una petición normal, o un **cupo por IP**, porque no llevan API key. Cada uno tiene su nombre de política, y ese nombre es el que ves en `RateLimit-Policy` del `429` cuando se agota. | Endpoint | Política | Cupo | Por qué | | --- | --- | --- | --- | | `POST /v3/constancias` | `constancias` | 20/min por key | Cada llamada es una emisión facturada ante el PSC. Se cuenta aparte y además gasta una petición de tu plan. | | `POST /v3/constancias/verify` | `constancias-verify` | 60/min por key | Cada llamada corre los cuatro checks criptográficos de la constancia. También gasta una de tu plan. | | `POST /v3/documents/{id}/send` | — | 250 participantes/min por key | Se cuentan **participantes** invitados, no llamadas: cada uno es un envío. Su `429` se reconoce por `X-RateLimit-Reason: invite-fanout`. | | `POST /v3/signing-sessions/{id}/init` y `GET /v3/signing-sessions/{id}/policy` | `signing-session` | 10/min por IP, compartidos entre las dos | Son rutas públicas, sin API key: se limitan por IP y no tocan tu cupo. | | `POST /v3/devices/enroll` | `device-enroll` | 5 cada 5 min por IP | Frena que alguien adivine códigos de enrolamiento de un solo uso. | | `POST /v3/devassist/chat` | `devassist` | 10/min por IP | Cada turno dispara una llamada a un modelo de lenguaje. No lleva API key, así que no toca tu cupo. | Una respuesta que pasó trae los `RateLimit-*` de tu plan (`default`), también en las constancias; en un `429` traen la política que se agotó. Si `RateLimit-Policy` nombra algo que no es `default`, subir de plan no lo cambia. ## El 429 Todo `429` de la v3 hecho con una API key válida es `application/problem+json` con `code: "RATE_LIMITED"`. Cuando se agota un límite —el de tu plan, un tope propio o un cupo por IP— trae el header `Retry-After` y **además** el campo `retryAfter` en el cuerpo, ambos en segundos e iguales a `RateLimit-Reset`, junto con los `RateLimit-*` de la política agotada (con `RateLimit-Remaining: 0`). No tienes que parsear headers si prefieres leer el body. ``` HTTP/1.1 429 Too Many Requests Content-Type: application/problem+json Retry-After: 60 RateLimit-Limit: 100 RateLimit-Remaining: 0 RateLimit-Reset: 60 RateLimit: "default";r=0;t=60 RateLimit-Policy: "default";q=100;w=60 AllSign-Environment: live { "type": "https://allsign.io/developers/docs/errors#RATE_LIMITED", "title": "Rate limited", "status": 429, "detail": "Rate limit exceeded. Limit: 100 requests per minute.", "instance": "/v3/documents", "code": "RATE_LIMITED", "requestId": "req_...", "retryAfter": 60 } ``` Uno por IP tiene la misma forma; cambian la política y la ventana. Este lo medimos contra la API: ``` HTTP/1.1 429 Too Many Requests Content-Type: application/problem+json Retry-After: 300 RateLimit-Limit: 5 RateLimit-Remaining: 0 RateLimit-Reset: 300 RateLimit: "device-enroll";r=0;t=300 RateLimit-Policy: "device-enroll";q=5;w=300 { "type": "https://allsign.io/developers/docs/errors#RATE_LIMITED", "title": "Rate limited", "status": 429, "detail": "Rate limit exceeded. Limit: 5 requests per 300 seconds.", "instance": "/v3/devices/enroll", "code": "RATE_LIMITED", "requestId": "req_...", "retryAfter": 300 } ``` El texto de `detail` cambia con la ventana («per minute», «per 300 seconds»). No lo parsees: decide con `code` y espera `retryAfter`. El `429` es **seguro de reintentar** (idempotency-safe): la petición no se procesó y tampoco gastó cupo, así que reintentar no duplica nada. Aun así, para `POST` combina el reintento con tu `Idempotency-Key` como red de seguridad. ### Cómo distinguir cada 429 | Caso | Cómo lo reconoces | Qué hacer | | --- | --- | --- | | Límite de tu plan | `RateLimit-Policy: "default"` y `Retry-After`. | Espera `Retry-After`. Si te pasa seguido, baja el ritmo o sube de plan. | | Tope propio o cupo por IP | `RateLimit-Policy` con otro nombre (`constancias`, `constancias-verify`, `signing-session`, `device-enroll`, `devassist`) y `Retry-After`. | Espera `Retry-After`. Subir de plan no lo cambia. | | Invitaciones de `send` | `X-RateLimit-Reason: invite-fanout`, `Retry-After` y los miembros `limit`, `requested` y `remaining`. | Espera `Retry-After` o invita a menos firmantes por envío. No se cobró ni se invitó a nadie. | | Recordatorio a firmante | **Sin** `Retry-After` ni `retryAfter`. | No lo reintentes: es un recordatorio cada 4 horas por firmante. | En el de invitaciones, los `RateLimit-*` son los de tu plan y no los del tope de invitaciones, así que `RateLimit-Remaining` no viene en `0`. Lo que dice cuánto esperar es `Retry-After`: ``` HTTP/1.1 429 Too Many Requests Content-Type: application/problem+json Retry-After: 60 X-RateLimit-Limit: 250 X-RateLimit-Remaining: 12 X-RateLimit-Reason: invite-fanout RateLimit-Limit: 100 RateLimit-Remaining: 87 RateLimit-Reset: 60 RateLimit-Policy: "default";q=100;w=60 { "type": "https://allsign.io/developers/docs/errors#RATE_LIMITED", "title": "Rate limited", "status": 429, "detail": "Invitation limit reached: 250 participants per minute. This limit counts participants, not calls, and is independent of your plan's request limit.", "instance": "/v3/documents/doc_.../send", "code": "RATE_LIMITED", "requestId": "req_...", "limit": 250, "requested": 30, "remaining": 12, "retryAfter": 60 } ``` El del **recordatorio a firmante** (`POST /v3/documents/{id}/signers/{signerId}/remind`) también trae los `RateLimit-*` de tu plan, pero la hora en que se libera va solo dentro de `detail`: **no trae `Retry-After`** (ni `retryAfter`, ni un miembro `nextAllowedAt`). No lo reintentes con backoff: guarda el `nextAllowedAt` de la respuesta `200` del recordatorio anterior y no vuelvas a recordar antes de esa hora. ``` HTTP/1.1 429 Too Many Requests Content-Type: application/problem+json RateLimit-Limit: 100 RateLimit-Remaining: 91 RateLimit-Reset: 60 RateLimit-Policy: "default";q=100;w=60 { "type": "https://allsign.io/developers/docs/errors#RATE_LIMITED", "title": "Rate limited", "status": 429, "detail": "Reminder rate-limited. Next allowed at 2026-10-02T21:30:08.632417+00:00.", "instance": "/v3/documents/doc_.../signers/sgr_.../remind", "code": "RATE_LIMITED", "requestId": "req_..." } ``` Sin una API key válida (sin `Authorization`, con una key revocada o mal copiada) la v3 no te mide por minuto: el `401` no trae `RateLimit-*`. Esas peticiones sí cuentan contra un **tope diario por IP**, y su `429` no es problem+json: es un JSON con `"error": "daily_ip_limit_exceeded"`, el header `X-RateLimit-Reason: daily-ip` y un `Retry-After` que puede ser de horas. No lo esperes: arregla la credencial. Con una key válida ese tope no aplica. ## Reintentar con backoff El patrón correcto: al ver `429`, **espera lo que diga `Retry-After`** (equivale a `retryAfter` y a `RateLimit-Reset`), súmale un poco de *jitter* y reintenta. No reintentes antes: el cupo se libera conforme salen de la ventana las peticiones viejas, y mientras tanto solo juntas más `429`. El *jitter* importa si varios *workers* comparten la key: sin él, todos vuelven en el mismo segundo y se topan otra vez. Si el `429` no trae `Retry-After`, no reintentes: es el freno de 4 horas del recordatorio a firmante, y un *backoff* de segundos solo te daría más `429`. ``` async function requestWithRetry(doRequest, { maxRetries = 5 } = {}) { for (let attempt = 0; ; attempt++) { const res = await doRequest(); if (res.status !== 429 || attempt >= maxRetries) return res; const retryAfter = Number(res.headers.get("Retry-After")); if (!Number.isFinite(retryAfter) || retryAfter <= 0) return res; const jitterMs = Math.random() * 1000; await new Promise((r) => setTimeout(r, retryAfter * 1000 + jitterMs)); } } ``` ``` import random import time def request_with_retry(do_request, max_retries=5): for attempt in range(max_retries + 1): resp = do_request() if resp.status_code != 429 or attempt == max_retries: return resp retry_after = resp.headers.get("Retry-After") if retry_after is None: return resp time.sleep(int(retry_after) + random.random()) ``` Para no llegar al `429`: cuando `RateLimit-Remaining` llegue a `0`, espera `RateLimit-Reset` segundos antes de la siguiente petición. ## Diferencia con v2 Si vienes de v2, tres cosas cambiaron en los headers de rate limit: | v2 | v3 | | --- | --- | | `X-RateLimit-Reset` | `RateLimit-Reset` | | `Reset` = **epoch** (timestamp Unix) | `Reset` = **segundos-delta** (cuántos segundos faltan) | | Prefijo `X-` en todos | Se cae el prefijo `X-` (RFC 6648) | Si tu cliente v2 hacía `reset - now()` para calcular la espera, en v3 el valor **ya es** la espera en segundos — úsalo directo. **En v3 ya no viajan los `X-RateLimit-*` de v2**: una respuesta con tu API key trae solo los `RateLimit-*` sin prefijo, también en las constancias y en sus `429`. Quedan dos rastros, los dos en un `429`: el de invitaciones de `POST /v3/documents/{id}/send`, que trae `X-RateLimit-Limit`, `X-RateLimit-Remaining` y `X-RateLimit-Reason: invite-fanout` (sin `X-RateLimit-Reset`); y el tope diario de una petición sin key válida, que trae `X-RateLimit-Reason: daily-ip` y los `X-DailyRateLimit-*`, con un `X-DailyRateLimit-Reset` que es un **epoch**. Para saber cuánto esperar, lee **solo** `Retry-After` (o `retryAfter`) y los `RateLimit-*`. --- Fuente: https://allsign.io/developers/docs/migration.md # Migrar de v2 a v3 La v3 es un **re-ordenamiento, no una reescritura**. Los mismos recursos y la misma lógica de negocio, con un contrato consistente encima. **Tu misma API key funciona** en ambas. La v2 sigue **viva y congelada** en `/v2`: migra a tu ritmo, endpoint por endpoint. ## De un vistazo Nueve cambios, todos mecánicos. Ninguno cambia *qué* hace la API, solo *cómo* la lees: | Área | v2 | v3 | | --- | --- | --- | | [Base URL](#base-url) | `/v2` | `/v3` (v2 sigue viva) | | [Casing](#casing) | snake/camel mezclado | `camelCase` en todo | | [IDs](#ids) | UUID crudos | Prefijados opacos (`doc_`, `tmpl_`…) | | [Errores](#errores) | `{error:{code:E1xxx}}` | RFC 9457 `problem+json` | | [Paginación](#paginacion) | Varias formas | Un envelope cursor unificado | | [Estados](#estados) | `MAYÚSCULAS_ES` | `lowercase_en` | | [Headers](#headers) | Solo `X-RateLimit-*` | Request-id, versión, rate limit IETF… | | [Status codes](#status-codes) | Algunos mal | Corregidos (404 no 500…) | | [Webhooks](#webhooks) | `snake_case` + `X-AllSign-Signature` | Cohorte v3: camelCase + Standard Webhooks (los clásicos **no cambian**) | ## Base URL Cambia el prefijo de la ruta de `/v2` a `/v3`. Eso es todo lo obligatorio para empezar: `https://api.allsign.io/v3/...`. La **misma API key** (`allsign_live_sk_` / `allsign_test_sk_`) autentica en ambas versiones — no generes keys nuevas. La v2 **no se apaga**: está congelada (sin cambios de comportamiento) y puedes migrar un endpoint a la vez. ## Casing La v2 mezclaba `snake_case` y `camelCase` según el endpoint. La v3 es **`camelCase` en todo** — request y response, sin excepciones. Renombra tus claves: `created_at` → `createdAt`, `signer_status` → `signerStatus`, `guest_link` → `guestLink`. ## IDs Los UUID crudos se vuelven **identificadores opacos con prefijo**. Son cadenas *opacas*: no parsees su interior, solo guárdalas y reenvíalas. Un id malformado responde `400` `INVALID_ID` (antes de tocar la base de datos). | Prefijo | Recurso | | --- | --- | | `doc_` | Documento | | `sgr_` | Firmante (signer) | | `fie_` | Campo (field) | | `rol_` | Rol de un documento | | `tmpl_` | Plantilla | | `tv_` | Versión de plantilla | | `fld_` | Carpeta (folder) | | `ses_` | Sesión de firma | | `bat_` | Lote de envío masivo (bulk send) | | `sset_` | *Signer set* | | `cst_` | Constancia NOM-151 | | `usr_` | Usuario | | `ten_` | Tenant | | `org_` | Organización | | `whe_` | Webhook endpoint | | `whd_` | Entrega de webhook | | `evt_` | Evento | | `whsec_` | Secreto de firma de webhook | ## Errores El *envelope* propietario de v2 desaparece. La v3 usa **RFC 9457 `application/problem+json`**, con `code` en `UPPER_SNAKE_CASE` (estable) en vez del `E1xxx` numérico. Programa contra `code`, nunca contra el texto. **Antes (v2)** ``` { "error": { "code": "E1300", "message": "document not found" } } ``` **Ahora (v3)** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND", "title": "Document not found", "status": 404, "detail": "No document exists with id doc_...", "code": "DOCUMENT_NOT_FOUND", "requestId": "req_..." } ``` La validación semántica responde `422` con un arreglo `errors[]`: un objeto por campo con `field` y su `pointer` (JSON Pointer). ``` { "type": "https://allsign.io/developers/docs/errors#VALIDATION_ERROR", "title": "Validation failed", "status": 422, "code": "VALIDATION_ERROR", "errors": [ { "field": "signers[0].email", "pointer": "/signers/0/email", "code": "INVALID_VALUE", "detail": "not a valid email address" } ] } ``` Catálogo completo de códigos en [Errores](https://allsign.io/developers/docs/errors). ## Paginación Las varias formas de paginar de v2 se unifican en **un solo envelope basado en cursor**: los recursos vienen en `data`, con `hasMore` y `nextCursor`. Para la siguiente página, reenvía `nextCursor` como `startingAfter` hasta que `hasMore` sea `false`. ``` GET /v3/documents?limit=20&startingAfter=djF8Y3JlYXRlZEF0fC4uLg { "object": "list", "data": [ { "id": "doc_..." }, { "id": "doc_..." } ], "hasMore": true, "nextCursor": "djF8Y3JlYXRlZEF0fC4uLg", "limit": 20 } ``` ## Estados Los estados pasan de `MAYÚSCULAS_ES` (español) a `lowercase_en` (inglés). Actualiza tus comparaciones y tus *mappings* de UI: | v2 | v3 | | --- | --- | | `ESPERANDO_FIRMAS` | `awaiting_signatures` | | `TODOS_FIRMARON` | `completed` | | `SELLOS_PDF` / `RECOLECTANDO_FIRMANTES` | `draft` | | `ANULADO` | `voided` | Recuerda que `DocumentStatus` y `SignerStatus` son `x-extensible-enum`: maneja valores nuevos sin romperte (ver [Versionado](https://allsign.io/developers/docs/versioning)). ## Headers La v3 agrega headers nuevos (en v2 solo existían los `X-RateLimit-*`). Vale la pena instrumentarlos: | Header | Para qué | | --- | --- | | `AllSign-Request-Id` | Correlación: cítalo al reportar un problema (también en `requestId` del error). | | `AllSign-Version` | La versión fechada activa (ver [Versionado](https://allsign.io/developers/docs/versioning)). | | `RateLimit-*` | Límite, restante y reinicio (ver [Rate limits](https://allsign.io/developers/docs/rate-limits)). | | `Idempotency-Replayed` | `true` cuando una respuesta salió del caché de idempotencia, no de una ejecución nueva. | **Los `X-*` de v2 no viajan en v3.** Una respuesta v3 hecha con tu API key trae `AllSign-Request-Id` y `RateLimit-*`; ya no trae `X-Allsign-Request-Id` ni el trío `X-RateLimit-*`. Si tu cliente v2 leía esos headers, cámbialo al migrar. Los topes propios, como el de `POST /v3/constancias`, también responden con `RateLimit-*` y su `429` es problem+json `RATE_LIMITED` con `Retry-After`. Solo dos `429` conservan algún `X-RateLimit-*`: el del tope de invitaciones de `POST /v3/documents/{id}/send` (`X-RateLimit-Reason: invite-fanout`) y el tope diario por IP de una petición sin key válida, cuyo `X-DailyRateLimit-Reset` es un **epoch** (ver [Rate limits](https://allsign.io/developers/docs/rate-limits)). `RateLimit-Reset` es un **delta en segundos**: para saber cuánto esperar, lee **solo** `Retry-After` y los headers nuevos. ## Status codes Se corrigieron códigos que en v2 mentían. Si tu cliente v2 trataba un `500` como "reintenta", revisa estos: - `templateId` malformado → **`400 INVALID_ID`** (antes `500`). - `GET /v3/users/me` **nunca** responde `403`: si tu key es válida, te ves a ti mismo. - Acceso *cross-tenant* (un id que no es tuyo) → **`404`**, no `403`: no revelamos que el recurso existe. ## Webhooks Migrar tus *llamadas* a `/v3` **no cambia tus webhooks**. El formato de cada endpoint de webhook lo decide su propia versión de contrato (`apiVersion`), y hoy hay dos cohortes: - **Tus destinos existentes son la cohorte clásica (`v2legacy`) y así se quedan:** cuerpo `snake_case` congelado byte a byte, cabeceras `X-AllSign-Event` / `X-AllSign-Event-Id` / `X-AllSign-Timestamp`, y (con HMAC habilitado) la firma `X-AllSign-Signature` = HMAC-SHA256 en **hex** de `"{timestamp}.{body}"`, con los bytes literales del secreto como llave. - **Un endpoint creado con `POST /v3/webhooks` nace en la cohorte v3** (versión fechada `2026-07-11`): sobre `camelCase` y firma [Standard Webhooks](https://allsign.io/developers/docs/webhooks#firma-standard-webhooks) (`webhook-id` / `webhook-timestamp` / `webhook-signature`) — la fórmula *y la llave* de verificación cambian, así que tu verificador clásico no sirve tal cual. - **Uno creado desde el dashboard hereda el formato de tu cuenta:** si ya tienes destinos clásicos, nace `v2legacy` (para no romper el handler que ya tienes); una cuenta que estrena integración nace en v3. - **Cambiar un endpoint de cohorte** hoy solo se puede desde el dashboard — `PATCH /v3/webhooks/{id}` no expone `apiVersion`. - Algunos eventos **cambian de nombre** entre cohortes (p.ej. `signature.reminder_sent` → `signer.reminder_sent`), y ambas cohortes traen la cabecera `AllSign-Livemode` — para la clásica es la única señal de entorno. El detalle completo (tabla lado a lado, nacimiento y reintentos por cohorte) está en [Webhooks → Las dos cohortes](https://allsign.io/developers/docs/webhooks#las-dos-cohortes). ## Checklist Diez pasos para migrar sin sorpresas: 1. Cambia la base URL de `/v2` a `/v3` (la misma API key sigue funcionando). 2. Pasa todas tus claves a `camelCase` (request y response). 3. Trata los IDs como cadenas opacas con prefijo; no parsees su interior. 4. Reescribe el manejo de errores a `problem+json`; programa contra `code`. 5. Lee `errors[]` (con `pointer`) en los `422` de validación. 6. Adopta el envelope cursor: `data` + `hasMore` + `nextCursor`. 7. Remapea los estados a `lowercase_en` y tolera valores nuevos en los enums extensibles. 8. Instrumenta los headers nuevos (`AllSign-Request-Id`, `RateLimit-*`, `Idempotency-Replayed`) y deja de leer los `X-*` legacy (`X-RateLimit-Reset` es epoch, no delta). 9. Ajusta tu manejo de status codes (404 en vez de 500; `/users/me` nunca 403; cross-tenant → 404). 10. Decide la cohorte de tus [webhooks](#webhooks): los destinos clásicos siguen en `v2legacy`; un endpoint nuevo por API nace v3 y se verifica con Standard Webhooks. --- Fuente: https://allsign.io/developers/docs/sdks.md # SDKs Un SDK oficial de TypeScript, **`@allsign/sdk`**, generado del mismo contrato que esta documentación. ¿No es tu lenguaje? **cURL es el fallback universal** y puedes generar tu propio cliente desde el spec público. ## Estado: beta `@allsign/sdk` está en **beta** y **todavía no está publicado en npm**. Su superficie puede cambiar antes del `1.0`. Mientras tanto, cualquier ejemplo de esta documentación funciona tal cual con [cURL](#curl-el-fallback-universal), que no depende del SDK. ## Cómo se genera El SDK **no se escribe a mano**: se **genera del OpenAPI** (`openapi.public.json`), la misma fuente de verdad de la referencia y del contrato de errores. Por eso los tipos, los nombres `camelCase` y los códigos de error del SDK **siempre coinciden** con la API real — no hay *drift* posible. ## Instalación Cuando salga de beta, se instalará desde npm: ``` npm install @allsign/sdk ``` Aún no está publicado en npm. Si quieres probarlo antes, escríbenos para acceso temprano al paquete beta. ## Uso Instancia el cliente con tu API key y usa los recursos tipados: ``` import { AllSign } from "@allsign/sdk"; const allsign = new AllSign({ apiKey: process.env.ALLSIGN_API_KEY }); const doc = await allsign.documents.create({ templateId: "tmpl_...", signers: [{ name: "Ana", email: "ana@empresa.mx" }], }); await allsign.documents.send(doc.id); ``` ## Manejo de errores Las respuestas no-2xx se lanzan como `AllSignError`, con el mismo `code` estable del [`problem+json`](https://allsign.io/developers/docs/errors) y el `requestId` para correlacionar. Programa contra `err.code`, nunca contra el texto del mensaje. ``` import { AllSign, AllSignError } from "@allsign/sdk"; try { await allsign.documents.retrieve("doc_inexistente"); } catch (err) { if (err instanceof AllSignError && err.code === "DOCUMENT_NOT_FOUND") { // err.code calca el problem+json; err.requestId es tu llave para reportar console.error(err.status, err.code, err.requestId); } } ``` ## cURL: el fallback universal No necesitas ningún SDK para usar la API. Es HTTP + JSON: **cualquier lenguaje con un cliente HTTP** funciona, y cada ejemplo de esta documentación se traduce directo a cURL. ``` curl "https://api.allsign.io/v3/documents" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "source": "template", "templateId": "tmpl_...", "signers": [{ "name": "Ana", "email": "ana@empresa.mx" }] }' ``` ## Genera tu propio cliente El spec público está publicado y versionado. Con él generas un cliente tipado **en tu lenguaje** (Python, Go, Java, C#…) usando `openapi-generator` u otro generador: ``` # 1. El spec público, versionado y siempre al día: curl https://allsign.io/developers/docs/openapi.public.json -o openapi.json # 2. Genera un cliente en tu lenguaje con openapi-generator: npx @openapitools/openapi-generator-cli generate \ -i openapi.json -g python -o ./allsign-client ``` ¿Prefieres probar antes de escribir código? Importa la colección de Postman: trae todas las operaciones con `{{baseUrl}}`, la `Idempotency-Key` como `{{$guid}}` y la key en la variable `bearerToken`. [Descargar OpenAPI](https://allsign.io/developers/docs/openapi.public.json)[Colección de Postman](https://allsign.io/developers/docs/allsign-api-v3.postman_collection.json) El mismo spec alimenta nuestro SDK de TS — tu cliente generado sigue exactamente el mismo contrato. --- Fuente: https://allsign.io/developers/docs/guides/pdf-forms.md # Formularios PDF: del widget al documento firmado Llenar es parte de firmar. Con la API v3 subes un PDF que ya trae su formulario (AcroForm), sus widgets se vuelven **campos** con nombre, tipo y posición, le pones a cada campo el **rol** que lo llena, y al emitir el documento decides qué va **prellenado y bloqueado**. El firmante llena lo suyo sobre el mismo PDF y firma; tú lees después campo por campo **quién lo llenó, cuándo y con qué valor**. Seis pasos, todos por API, ninguno en el panel. Prerrequisitos: una API key con scopes `documents:read` y `documents:write` ([Autenticación](https://allsign.io/developers/docs/authentication)). En sandbox (`allsign_test_sk_…`) nada tiene validez legal y no se cobran créditos ([Entornos](https://allsign.io/developers/docs/environments)). Referencia completa de cada llamada en [Templates](https://allsign.io/developers/docs/endpoints/templates) y [Documents](https://allsign.io/developers/docs/endpoints/documents). Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). ## 1 · Subir el PDF y leer los campos detectados Crea la plantilla con el PDF en base64. Si trae formulario, cada widget entra como campo con `source: "acroform"`; la respuesta ya dice cuántos campos hay y cuántos siguen sin rol (`readiness: "pending"` hasta que todos tengan uno). Una caja de firma cuyo nombre sugiere el rol (`firma_cliente`) nace con el rol *Cliente*. ``` curl -X POST "https://api.allsign.io/v3/templates" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{"name":"Contrato de compraventa","file":{"content":"","fileType":"pdf","name":"contrato-compraventa.pdf"}}' ``` **201** — la plantilla: ``` { "object": "template", "id": "tmpl_3f9c1a2b4d5e6f70", "livemode": true, "name": "Contrato de compraventa", "description": null, "fileType": "pdf", "originalFilename": "contrato-compraventa.pdf", "aiEditable": true, "variableCount": 0, "fieldCount": 5, "unassignedFieldCount": 4, "roleCount": 1, "pendingCandidates": 0, "readiness": "pending", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 1, "previewUrl": "/templates/tmpl_3f9c1a2b4d5e6f70/preview", "downloadUrl": "/templates/tmpl_3f9c1a2b4d5e6f70/download", "createdAt": "2026-09-20T17:58:03Z", "updatedAt": "2026-09-20T17:58:03Z" } ``` Lee los campos: `name` es la clave estable (la del widget), `type` es `text`, `date`, `checkbox`, `radio`, `select`, `signature`, `initials` o `stamp`, y `areas` trae página y rectángulo en % del papel (varias áreas = el mismo dato repetido en el PDF). ``` curl "https://api.allsign.io/v3/templates/tmpl_3f9c1a2b4d5e6f70/fields" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **200**: ``` { "object": "list", "templateId": "tmpl_3f9c1a2b4d5e6f70", "pageCount": 1, "data": [ { "object": "template_field", "name": "nombre", "type": "text", "role": null, "required": true, "label": null, "options": null, "group": null, "source": "acroform", "areas": [ { "page": 1, "rect": { "x": 12.5, "y": 18.2, "width": 40, "height": 3.1 } } ] }, { "object": "template_field", "name": "rfc", "type": "text", "role": null, "required": true, "label": null, "options": null, "group": null, "source": "acroform", "areas": [ { "page": 1, "rect": { "x": 12.5, "y": 24.6, "width": 40, "height": 3.1 } } ] }, { "object": "template_field", "name": "fecha", "type": "date", "role": null, "required": true, "label": null, "options": null, "group": null, "source": "acroform", "areas": [ { "page": 1, "rect": { "x": 12.5, "y": 31, "width": 20, "height": 3.1 } } ] }, { "object": "template_field", "name": "acepta", "type": "checkbox", "role": null, "required": true, "label": null, "options": null, "group": null, "source": "acroform", "areas": [ { "page": 1, "rect": { "x": 12.5, "y": 70.4, "width": 2.4, "height": 1.6 } } ] }, { "object": "template_field", "name": "firma_cliente", "type": "signature", "role": "Cliente", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "areas": [ { "page": 1, "rect": { "x": 12.5, "y": 80, "width": 30, "height": 8 } } ] } ], "unassignedCount": 4, "hasMore": false, "livemode": true } ``` Un PDF *XFA* (LiveCycle) responde `422 UNSUPPORTED_FORM_XFA`; un PDF que ya trae una firma digital, `422` con `PDF_ALREADY_SIGNED` — editarlo la invalidaría. Sube la versión sin firmar o un PDF plano. ## 2 · Asignar roles y comprobar que la plantilla está lista El rol es el dueño del campo; el firmante se deriva del rol al emitir. Declara los roles en orden y asigna cada campo con `PATCH …/fields/{name}` (también puedes fijar `required` y una `label` para el firmante). Cuando `unassignedFieldCount` llega a `0`, la plantilla queda `readiness: "ready"`. ``` curl -X PUT "https://api.allsign.io/v3/templates/tmpl_3f9c1a2b4d5e6f70/roles" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{"roles":["Cliente"]}' for name in nombre rfc fecha acepta; do curl -X PATCH "https://api.allsign.io/v3/templates/tmpl_3f9c1a2b4d5e6f70/fields/$name" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{"role":"Cliente"}' done curl "https://api.allsign.io/v3/templates/tmpl_3f9c1a2b4d5e6f70" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **200** — lista para emitir: ``` { "object": "template", "id": "tmpl_3f9c1a2b4d5e6f70", "livemode": true, "name": "Contrato de compraventa", "description": null, "fileType": "pdf", "originalFilename": "contrato-compraventa.pdf", "aiEditable": true, "variableCount": 0, "fieldCount": 5, "unassignedFieldCount": 0, "roleCount": 1, "pendingCandidates": 0, "readiness": "ready", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 1, "previewUrl": "/templates/tmpl_3f9c1a2b4d5e6f70/preview", "downloadUrl": "/templates/tmpl_3f9c1a2b4d5e6f70/download", "createdAt": "2026-09-20T17:58:03Z", "updatedAt": "2026-09-20T18:01:40Z" } ``` Si prefieres versionar el layout completo en tu repositorio, `PUT …/fields` lo reemplaza de forma declarativa e idempotente; y `POST …/fields:from-document` copia los campos y roles de un documento ya armado en el panel ("guardar como plantilla", por API). Copia la estructura, no los datos: los valores que ese documento tenía no pasan a la plantilla. ## 3 · Revisar el PDF con los widgets vivos, o aplanado Antes de emitir, baja el PDF tal como lo verá el firmante. Con `format=form` conserva los widgets del formulario para revisarlos en Acrobat o en tu propio visor; con `format=flat` los hornea (sin campos editables). ``` curl "https://api.allsign.io/v3/templates/tmpl_3f9c1a2b4d5e6f70/file?format=form" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -o contrato-form.pdf curl "https://api.allsign.io/v3/templates/tmpl_3f9c1a2b4d5e6f70/file?format=flat" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -o contrato-flat.pdf ``` **200** — `application/pdf` con `Content-Disposition: attachment`. ## 4 · Crear el documento con valores prellenados y bloqueados por firmante Cada firmante toma un rol por `roleName` y hereda sus campos. `values` prellena campos por nombre (texto, fecha `YYYY-MM-DD` o `true`/`false` en casillas) y `readOnly` lista los que el firmante puede ver pero no cambiar: el caso del asesor que manda el contrato con el RFC ya puesto. Manda `Idempotency-Key` ([Idempotencia](https://allsign.io/developers/docs/idempotency)). ``` curl -X POST "https://api.allsign.io/v3/documents" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d @crear.json ``` Cuerpo (`crear.json`): ``` { "name": "Contrato de compraventa — Ana López", "templateId": "tmpl_3f9c1a2b4d5e6f70", "signers": [ { "roleName": "Cliente", "email": "ana@cliente.mx", "name": "Ana López", "values": { "rfc": "XAXX010101000", "fecha": "2026-09-20" }, "readOnly": [ "rfc" ] } ] } ``` Los campos del documento se materializan en segundos. Los prellenados traen su valor y `filledBy: "owner"`; el resto espera al firmante (`filledBy: null`). ``` curl "https://api.allsign.io/v3/documents/doc_8f2c1e0a9b7d4c6e5f3a2b1c0d9e8f7a/fields" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **200**: ``` { "object": "list", "documentId": "doc_8f2c1e0a9b7d4c6e5f3a2b1c0d9e8f7a", "data": [ { "id": "fie_0f1e2d3c4b5a69788796a5b4c3d2e1f0", "object": "document_field", "name": "nombre", "type": "text", "role": "Cliente", "signerId": "sgr_1b2c3d4e5f60718293a4b5c6d7e8f901", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "readOnly": false, "page": 1, "rect": { "x": 12.5, "y": 18.2, "width": 40, "height": 3.1 }, "position": 1, "status": "pending", "value": { "text": null, "checked": null }, "filledBy": null, "filledAt": null }, { "id": "fie_9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d", "object": "document_field", "name": "rfc", "type": "text", "role": "Cliente", "signerId": "sgr_1b2c3d4e5f60718293a4b5c6d7e8f901", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "readOnly": true, "page": 1, "rect": { "x": 12.5, "y": 24.6, "width": 40, "height": 3.1 }, "position": 1, "status": "pending", "value": { "text": "XAXX010101000", "checked": null }, "filledBy": "owner", "filledAt": "2026-09-20T18:02:11Z" }, { "id": "fie_1a2b3c4d5e6f708192a3b4c5d6e7f809", "object": "document_field", "name": "fecha", "type": "date", "role": "Cliente", "signerId": "sgr_1b2c3d4e5f60718293a4b5c6d7e8f901", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "readOnly": false, "page": 1, "rect": { "x": 12.5, "y": 31, "width": 20, "height": 3.1 }, "position": 1, "status": "pending", "value": { "text": "2026-09-20", "checked": null }, "filledBy": "owner", "filledAt": "2026-09-20T18:02:11Z" }, { "id": "fie_2b3c4d5e6f708192a3b4c5d6e7f8091a", "object": "document_field", "name": "acepta", "type": "checkbox", "role": "Cliente", "signerId": "sgr_1b2c3d4e5f60718293a4b5c6d7e8f901", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "readOnly": false, "page": 1, "rect": { "x": 12.5, "y": 70.4, "width": 2.4, "height": 1.6 }, "position": 1, "status": "pending", "value": { "text": null, "checked": null }, "filledBy": null, "filledAt": null }, { "id": "fie_3c4d5e6f708192a3b4c5d6e7f8091a2b", "object": "document_field", "name": "firma_cliente", "type": "signature", "role": "Cliente", "signerId": "sgr_1b2c3d4e5f60718293a4b5c6d7e8f901", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "readOnly": false, "page": 1, "rect": { "x": 12.5, "y": 80, "width": 30, "height": 8 }, "position": 1, "status": "pending", "value": { "text": null, "checked": null }, "filledBy": null, "filledAt": null } ], "unassignedCount": 0, "hasMore": false, "livemode": true } ``` También puedes prellenar o bloquear después de crear, campo por campo, con `PATCH /v3/documents/{id}/fields/{fieldId} {"value": …, "readOnly": true}`; y dibujar campos nuevos con `POST …/fields`. Luego envía con `POST /v3/documents/{id}/send`. ## 5 · Leer el estado: quién lo llenó, cuándo y con qué valor El firmante llena sus campos sobre el PDF y firma. Cuando el documento llega a `completed` (o en cualquier momento antes), vuelve a leer los campos: `value` trae lo capturado, `status` pasa a `signed`, y `filledBy` / `filledAt` dicen quién puso cada valor y cuándo — `"owner"` lo que prellenaste tú, `"signer"` lo que escribió el firmante dueño (`signerId`). ``` curl "https://api.allsign.io/v3/documents/doc_8f2c1e0a9b7d4c6e5f3a2b1c0d9e8f7a/fields" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **200**: ``` { "object": "list", "documentId": "doc_8f2c1e0a9b7d4c6e5f3a2b1c0d9e8f7a", "data": [ { "id": "fie_0f1e2d3c4b5a69788796a5b4c3d2e1f0", "object": "document_field", "name": "nombre", "type": "text", "role": "Cliente", "signerId": "sgr_1b2c3d4e5f60718293a4b5c6d7e8f901", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "readOnly": false, "page": 1, "rect": { "x": 12.5, "y": 18.2, "width": 40, "height": 3.1 }, "position": 1, "status": "signed", "value": { "text": "Ana López", "checked": null }, "filledBy": "signer", "filledAt": "2026-09-21T10:15:42Z" }, { "id": "fie_9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d", "object": "document_field", "name": "rfc", "type": "text", "role": "Cliente", "signerId": "sgr_1b2c3d4e5f60718293a4b5c6d7e8f901", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "readOnly": true, "page": 1, "rect": { "x": 12.5, "y": 24.6, "width": 40, "height": 3.1 }, "position": 1, "status": "signed", "value": { "text": "XAXX010101000", "checked": null }, "filledBy": "owner", "filledAt": "2026-09-20T18:02:11Z" }, { "id": "fie_1a2b3c4d5e6f708192a3b4c5d6e7f809", "object": "document_field", "name": "fecha", "type": "date", "role": "Cliente", "signerId": "sgr_1b2c3d4e5f60718293a4b5c6d7e8f901", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "readOnly": false, "page": 1, "rect": { "x": 12.5, "y": 31, "width": 20, "height": 3.1 }, "position": 1, "status": "signed", "value": { "text": "2026-09-20", "checked": null }, "filledBy": "owner", "filledAt": "2026-09-20T18:02:11Z" }, { "id": "fie_2b3c4d5e6f708192a3b4c5d6e7f8091a", "object": "document_field", "name": "acepta", "type": "checkbox", "role": "Cliente", "signerId": "sgr_1b2c3d4e5f60718293a4b5c6d7e8f901", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "readOnly": false, "page": 1, "rect": { "x": 12.5, "y": 70.4, "width": 2.4, "height": 1.6 }, "position": 1, "status": "signed", "value": { "text": null, "checked": true }, "filledBy": "signer", "filledAt": "2026-09-21T10:16:03Z" }, { "id": "fie_3c4d5e6f708192a3b4c5d6e7f8091a2b", "object": "document_field", "name": "firma_cliente", "type": "signature", "role": "Cliente", "signerId": "sgr_1b2c3d4e5f60718293a4b5c6d7e8f901", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "readOnly": false, "page": 1, "rect": { "x": 12.5, "y": 80, "width": 30, "height": 8 }, "position": 1, "status": "signed", "value": { "text": null, "checked": null }, "filledBy": null, "filledAt": null } ], "unassignedCount": 0, "hasMore": false, "livemode": true } ``` Un campo firmado es inmutable: `PATCH`, `DELETE` y `fields:assign-role` responden `409 FIELD_CONFLICT`. Para enterarte sin consultar, suscríbete al webhook `document.completed` ([Webhooks](https://allsign.io/developers/docs/webhooks)). ## 6 · Descargar el documento Antes de enviar, `GET /v3/documents/{id}/file?format=form` devuelve el PDF con los widgets vivos y lo prellenado (para que el emisor lo revise) y `format=flat` lo hornea con los valores actuales. Después de la firma, el PDF con validez legal es la **evidencia** (`GET /v3/documents/{id}/evidence`): todos los widgets van horneados con lo capturado, más la constancia NOM-151. ``` curl "https://api.allsign.io/v3/documents/doc_8f2c1e0a9b7d4c6e5f3a2b1c0d9e8f7a/file?format=flat" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -o contrato-flat.pdf ``` **200** — `application/pdf` con `Content-Disposition: attachment`. ## Cuando el PDF cambia: reemplazarlo sin perder los campos Meses después el contrato se actualiza —una cláusula nueva, otro logo— y la plantilla ya tiene sus campos colocados y sus roles asignados. `POST /v3/templates/{id}/file` cambia el archivo y los conserva: las áreas se guardan en **porcentaje de página**, así que un PDF con otro tamaño de hoja no las mueve. El archivo viaja en base64, igual que al crear la plantilla, y solo se acepta PDF sobre PDF (este endpoint no convierte DOCX↔PDF). ``` curl -X POST "https://api.allsign.io/v3/templates/tmpl_3f9c1a2b4d5e6f70/file" \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{"file":{"content":"","fileType":"pdf","name":"contrato-compraventa-2026.pdf"}}' ``` **200** — el archivo nuevo tiene al menos las mismas páginas, así que nada se podó: ``` { "object": "template_file_replace", "template": { "object": "template", "id": "tmpl_3f9c1a2b4d5e6f70", "livemode": true, "name": "Contrato de compraventa", "description": null, "fileType": "pdf", "originalFilename": "contrato-compraventa-2026.pdf", "aiEditable": true, "variableCount": 0, "fieldCount": 5, "unassignedFieldCount": 0, "roleCount": 1, "pendingCandidates": 0, "readiness": "ready", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 2, "previewUrl": "/templates/tmpl_3f9c1a2b4d5e6f70/preview", "downloadUrl": "/templates/tmpl_3f9c1a2b4d5e6f70/download", "createdAt": "2026-09-20T17:58:03Z", "updatedAt": "2026-10-02T09:12:55Z" }, "pageCount": 2, "prunedFields": [], "livemode": true } ``` Lo único que sí puede romperse es el **número de páginas**: un campo que vivía en una página que el archivo nuevo ya no tiene se poda —se borra esa área, nunca la plantilla ni el resto de las áreas de ese campo— y la respuesta lo dice en `prunedFields`. Revísalo siempre: `removed: true` significa que el campo se quedó sin ninguna área y hay que volver a colocarlo. **200** — el PDF nuevo trae una página menos y la casilla que vivía en la segunda se cayó: ``` { "object": "template_file_replace", "template": { "object": "template", "id": "tmpl_3f9c1a2b4d5e6f70", "livemode": true, "name": "Contrato de compraventa", "description": null, "fileType": "pdf", "originalFilename": "contrato-compraventa-corto.pdf", "aiEditable": true, "variableCount": 0, "fieldCount": 4, "unassignedFieldCount": 0, "roleCount": 1, "pendingCandidates": 0, "readiness": "ready", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 3, "previewUrl": "/templates/tmpl_3f9c1a2b4d5e6f70/preview", "downloadUrl": "/templates/tmpl_3f9c1a2b4d5e6f70/download", "createdAt": "2026-09-20T17:58:03Z", "updatedAt": "2026-10-09T11:40:18Z" }, "pageCount": 1, "prunedFields": [ { "name": "acepta", "pages": [ 2 ], "removed": true } ], "livemode": true } ``` Cada reemplazo avanza `currentVersion` y guarda una versión: `GET /v3/templates/{id}/versions` las lista de la más reciente a la más antigua, con el `layout` y un `summary` para comparar dos de un vistazo. Un PDF *XFA* responde `422 UNSUPPORTED_FORM_XFA` y una plantilla DOCX, `422`. ## Qué sigue - [Campos y roles de plantilla](https://allsign.io/developers/docs/endpoints/templates#list-template-fields): crear, mover, reemplazar el layout, importar desde un documento. - [Reemplazar el PDF](https://allsign.io/developers/docs/endpoints/templates#replace-a-pdf-template-s-file) y [las versiones guardadas](https://allsign.io/developers/docs/endpoints/templates#list-template-versions) para volver atrás. - [Campos de documento](https://allsign.io/developers/docs/endpoints/documents#list-document-fields): prellenar, bloquear, reasignar en lote, descargar. - [Webhooks](https://allsign.io/developers/docs/webhooks) para saber cuándo un documento se completó sin consultar. --- Fuente: https://allsign.io/developers/docs/recetas.md # Recetas Tareas completas, paso a paso. Cada receta sale de un test que corre contra el sandbox de AllSign: los requests y las respuestas que ves son los que dio la API, y el sello dice cuándo se verificó por última vez. ## Para agentes y asistentes de IA - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** → [Entornos](https://allsign.io/developers/docs/environments) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) - [Lleva tu primer documento a firma](https://allsign.io/developers/docs/recetas/primer-documento-a-firma) **Objetivo:** Un documento firmado de punta a punta en sandbox, con el aviso de cada firma y el de cierre llegando a tu servidor. Para: integrador · nivel inicial · 10 min · Necesitas: API key de sandbox (allsign\_test\_sk\_…) · Un PDF: contrato.pdf · Un endpoint HTTPS para webhooks · 8 pasos Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:19 (hora CDMX) · test `docs-checks-v3/tests/recetas/primer-documento-a-firma.receta.spec.ts` - [Recuerda, reasigna o anula un documento que nadie firma](https://allsign.io/developers/docs/recetas/anula-recuerda-reasigna) **Objetivo:** Destrabar un documento enviado: recordarle al firmante, pasarle su lugar a otra persona y, si ya no procede, anularlo dejando la razón. Para: soporte · nivel intermedio · 10 min · Necesitas: API key de sandbox · Un documento enviado que sigue sin firmarse · 5 pasos Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/recetas/anula-recuerda-reasigna.receta.spec.ts` - [Descarga el PDF firmado y su evidencia](https://allsign.io/developers/docs/recetas/descarga-el-pdf-firmado-y-la-evidencia) **Objetivo:** El PDF firmado y el PDF de evidencia de un documento completado, con su huella sha256 para archivarlos. Para: integrador · nivel inicial · 5 min · Necesitas: API key de sandbox · 4 pasos Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/descarga-el-pdf-firmado-y-la-evidencia.receta.spec.ts` - [Crea un contrato desde tu plantilla de Word](https://allsign.io/developers/docs/recetas/documento-desde-plantilla-docx) **Objetivo:** Un acuerdo de confidencialidad generado desde tu plantilla DOCX, con los datos de cada parte en su lugar y enviado a firma. Para: integrador · nivel intermedio · 10 min · Necesitas: API key de sandbox · Una plantilla DOCX con variables {{ rol\_\_campo }}: acuerdo.docx · 8 pasos Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/documento-desde-plantilla-docx.receta.spec.ts` - [Pon la firma dentro de tu app](https://allsign.io/developers/docs/recetas/firma-embebida-en-tu-app) **Objetivo:** Tu usuario firma sin salir de tu página, en una sesión de firma que solo se abre desde tu dominio. Para: frontend · nivel intermedio · 15 min · Necesitas: API key de sandbox (solo en tu backend) · Tu dominio, aquí https://app.ejemplo.com · El SDK @allsign/embedded en tu frontend · 6 pasos Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/firma-embebida-en-tu-app.receta.spec.ts` - [Prellena un formulario PDF y mándalo a firma](https://allsign.io/developers/docs/recetas/formulario-pdf-acroform) **Objetivo:** Un formulario PDF con sus campos prellenados, el RFC bloqueado y enviado a la persona que lo llena y firma. Para: integrador · nivel intermedio · 10 min · Necesitas: API key de sandbox · Un PDF con campos de formulario (AcroForm): formulario.pdf · 7 pasos Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/formulario-pdf-acroform.receta.spec.ts` - [Avisa a alguien más y cambia al responsable de un documento](https://allsign.io/developers/docs/recetas/observadores-y-transferencia) **Objetivo:** Una persona de fuera se entera de cómo va un documento sin firmarlo, y otra persona de tu equipo queda como su responsable. Para: admin · nivel inicial · 5 min · Necesitas: API key de sandbox · Un segundo miembro en tu equipo · 7 pasos Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/observadores-y-transferencia.receta.spec.ts` - [Recibe y verifica los webhooks de AllSign](https://allsign.io/developers/docs/recetas/recibe-y-verifica-webhooks) **Objetivo:** Un endpoint que recibe los eventos de AllSign, comprueba que de verdad los firmó AllSign y sobrevive a sus propias caídas y a la rotación del secreto. Para: backend · nivel intermedio · 15 min · Necesitas: API key de sandbox · Un endpoint HTTPS público en tu servidor · 10 pasos Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:20 (hora CDMX) · test `docs-checks-v3/tests/recetas/recibe-y-verifica-webhooks.receta.spec.ts` - [Varios firmantes, uno después de otro](https://allsign.io/developers/docs/recetas/varios-firmantes-en-orden) **Objetivo:** Un contrato que firma primero la vendedora y después el comprador, con el comprador sin poder firmar antes de su turno. Para: integrador · nivel intermedio · 15 min · Necesitas: API key de sandbox · contrato.pdf · Un endpoint HTTPS para webhooks · 9 pasos Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:20 (hora CDMX) · test `docs-checks-v3/tests/recetas/varios-firmantes-en-orden.receta.spec.ts` --- Fuente: https://allsign.io/developers/docs/recetas/primer-documento-a-firma.md Receta # Lleva tu primer documento a firma **Objetivo:** Un documento firmado de punta a punta en sandbox, con el aviso de cada firma y el de cierre llegando a tu servidor. Para: integrador · nivel inicial · 10 min · Necesitas: API key de sandbox (allsign\_test\_sk\_…) · Un PDF: contrato.pdf · Un endpoint HTTPS para webhooks ## Para agentes y asistentes de IA - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** → [Entornos](https://allsign.io/developers/docs/environments) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:19 (hora CDMX) · test `docs-checks-v3/tests/recetas/primer-documento-a-firma.receta.spec.ts` Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). ## 1 Registra tu endpoint de webhooks POST `/webhooks` [ver en la referencia](https://allsign.io/developers/docs/webhooks#create-endpoint) ### Qué haces Primero tu endpoint: así te enteras de cada firma y del cierre sin consultar en bucle. Guarda el secreto whsec\_…, que solo se muestra aquí; con él verificas cada entrega. ### Qué mirar - `id` = `"whe_faed666bbdc5444693acfac1655160a6"` - `secret` = `"whsec_…"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/webhooks' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "url": "https://ejemplo.com/webhooks/allsign", "events": [ "signer.signed", "document.completed" ], "description": "Mi primer contrato" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/webhooks', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://ejemplo.com/webhooks/allsign', events: [ 'signer.signed', 'document.completed', ], description: 'Mi primer contrato', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/webhooks", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "url": "https://ejemplo.com/webhooks/allsign", "events": [ "signer.signed", "document.completed", ], "description": "Mi primer contrato", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "whe_faed666bbdc5444693acfac1655160a6", "object": "webhook_endpoint", "url": "https://ejemplo.com/webhooks/allsign", "events": [ "signer.signed", "document.completed" ], "description": "Mi primer contrato", "status": "enabled", "apiVersion": "2026-07-11", "environment": "test", "secretLast4": "S5w=", "createdAt": "2026-07-11T18:00:00.000000Z", "secret": "whsec_…" } ``` ## 2 Crea el documento con su firmante POST `/documents` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#create-document) ### Qué haces Crear no envía nada: el documento nace en draft y todavía lo puedes cambiar. signer-pending@sandbox.allsign.io es un firmante de prueba que no recibe correo y nunca firma solo: espera a que tú firmes por él. ### Qué mirar en sandbox - `id` = `"doc_fdde14eeff0b4444acec4d6b10736068"` - `status` = `"draft"` - `signerCount` = `1` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "file": { "content": "'"$(base64 < contrato.pdf | tr -d '\n')"'", "fileType": "pdf", "name": "contrato.pdf" }, "name": "Mi primer contrato (sandbox)", "signers": [ { "email": "signer-pending@sandbox.allsign.io", "name": "Ana Torres" } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/documents', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ file: { content: readFileSync('contrato.pdf').toString('base64'), fileType: 'pdf', name: 'contrato.pdf', }, name: 'Mi primer contrato (sandbox)', signers: [ { email: 'signer-pending@sandbox.allsign.io', name: 'Ana Torres', }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/documents", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "file": { "content": base64.b64encode(Path("contrato.pdf").read_bytes()).decode(), "fileType": "pdf", "name": "contrato.pdf", }, "name": "Mi primer contrato (sandbox)", "signers": [ { "email": "signer-pending@sandbox.allsign.io", "name": "Ana Torres", }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "doc_fdde14eeff0b4444acec4d6b10736068", "object": "document", "name": "Mi primer contrato (sandbox)", "status": "draft", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_08f394a003014001b429ca03db05b7f7", "orgId": "f4d8bad5-5bb0-451c-a917-2ae6b489d38f", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:01:02.144000Z", "updatedAt": "2026-07-11T18:01:02.144000Z" } ``` ## 3 Envíalo a firma POST `/documents/{document_id}/send` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#send-document) ### Qué haces Ahora el documento espera firmas. Desde aquí ya no se edita: si algo estaba mal, lo anulas y creas otro. ### Qué mirar - `status` = `"awaiting_signatures"` ### En producción Aquí AllSign le manda la invitación a tu firmante por correo o WhatsApp, y con una key live el envío consume créditos de tu saldo; en sandbox no se cobra nada. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068/send' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068/send', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068/send", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={}, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_fdde14eeff0b4444acec4d6b10736068", "object": "document", "name": "Mi primer contrato (sandbox)", "status": "awaiting_signatures", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_08f394a003014001b429ca03db05b7f7", "orgId": "f4d8bad5-5bb0-451c-a917-2ae6b489d38f", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:01:02.144000Z", "updatedAt": "2026-07-11T18:01:05.421000Z" } ``` ## 4 Consulta a tu firmante GET `/documents/{document_id}/signers` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#list-signers) ### Qué haces Cada firmante tiene su id sgr\_… y su propio status. Con ese id le recuerdas, lo reasignas o, en sandbox, firmas por él. ### Qué mirar - `data.0.id` = `"sgr_d64e1ca6bcf64eebac2d35da21d23c88"` - `data.0.status` = `"sent"` **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068/signers' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068/signers', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068/signers", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "sgr_d64e1ca6bcf64eebac2d35da21d23c88", "object": "signer", "documentId": "doc_fdde14eeff0b4444acec4d6b10736068", "email": "signer-pending@sandbox.allsign.io", "phone": null, "name": "Ana Torres", "status": "sent", "signedAt": null, "routingOrder": null, "delivery": null } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": null } ``` ## 5 Firma por tu firmante (solo en sandbox) POST `/sandbox/signers/{signer_id}/sign` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#sign-as-a-signer-sandbox-only) ### Qué haces En sandbox no hay nadie del otro lado: esta operación firma como si tu firmante hubiera abierto su liga, y dispara los mismos eventos que una firma real. ### Qué mirar - `status` = `"signed"` - `signedAt` = `"2026-07-11T18:01:06.969000Z"` ### En producción Aquí firma tu cliente: abre la liga del correo o del WhatsApp, revisa el documento y firma. Esta operación solo funciona en sandbox. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/sandbox/signers/sgr_d64e1ca6bcf64eebac2d35da21d23c88/sign' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/sandbox/signers/sgr_d64e1ca6bcf64eebac2d35da21d23c88/sign', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/sandbox/signers/sgr_d64e1ca6bcf64eebac2d35da21d23c88/sign", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "sgr_d64e1ca6bcf64eebac2d35da21d23c88", "object": "signer", "documentId": "doc_fdde14eeff0b4444acec4d6b10736068", "email": "signer-pending@sandbox.allsign.io", "phone": null, "name": "Ana Torres", "status": "signed", "signedAt": "2026-07-11T18:01:06.969000Z", "routingOrder": null, "delivery": null } ``` ## 6 Espera el webhook signer.signed POST `tu endpoint` · evento `signer.signed` ### Qué haces Llega en cuanto firma cada persona. Verifica la firma de la entrega antes de confiar en su contenido. AllSign le hace `POST` a la URL que registraste, con el evento `signer.signed`. Responde `2xx` rápido y, antes de confiar en el cuerpo, verifica la firma: `webhook-signature` es un HMAC-SHA256 de `webhook-id.webhook-timestamp.cuerpo` con el secreto del webhook ([Webhooks](https://allsign.io/developers/docs/webhooks)). Esta entrega llegó de verdad al receptor del test y su firma se verificó con el secreto del webhook. ### Qué mirar - `eventType` = `"signer.signed"` - `data.documentId` = `"doc_fdde14eeff0b4444acec4d6b10736068"` **Entrega recibida · Cuerpo** ``` { "data": { "name": "Mi primer contrato (sandbox)", "signer": { "name": "Ana Torres", "email": "signer-pending@sandbox.allsign.io", "phone": null, "signedAt": "2026-07-11T18:01:06.969000Z", "signerId": "sgr_d64e1ca6bcf64eebac2d35da21d23c88", "authMethod": "POR_DEFINIR" }, "documentId": "doc_fdde14eeff0b4444acec4d6b10736068", "signedCount": 1, "totalSigners": 1 }, "eventId": "evt_b65f702e300448f9906987d0e9aa0ec0", "livemode": false, "tenantId": "d521e797-924d-41c6-be8b-a3dcd62f5b6f", "eventType": "signer.signed", "apiVersion": "2026-07-11", "occurredAt": "2026-07-11T18:01:07.025Z" } ``` **Entrega recibida · Cabeceras** ``` { "content-type": "application/json", "allsign-event": "signer.signed", "allsign-livemode": "false", "webhook-id": "b65f702e-3004-48f9-9069-87d0e9aa0ec0", "webhook-timestamp": "1783792867", "webhook-signature": "v1,", "allsign-delivery-id": "7553835d-3b51-4c47-a79b-a036813e0d92" } ``` **Verifica la firma · Node** ``` import { createHmac, timingSafeEqual } from 'node:crypto'; export function verificarWebhook(cuerpoCrudo, headers, secreto) { const id = headers['webhook-id']; const timestamp = headers['webhook-timestamp']; if (!id || !/^\d+$/.test(timestamp ?? '') || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const llave = Buffer.from(secreto.replace(/^whsec_/, ''), 'base64'); const esperada = Buffer.from(createHmac('sha256', llave).update(`${id}.${timestamp}.${cuerpoCrudo}`).digest('base64')); return (headers['webhook-signature'] ?? '').split(' ').some((token) => { const [version, firma = ''] = token.split(','); const candidata = Buffer.from(firma); return version === 'v1' && candidata.length === esperada.length && timingSafeEqual(candidata, esperada); }); } ``` **Verifica la firma · Python** ``` import base64 import hashlib import hmac import time def verificar_webhook(cuerpo_crudo: bytes, headers: dict, secreto: str) -> bool: id_ = headers.get("webhook-id") timestamp = headers.get("webhook-timestamp", "") if not id_ or not (timestamp.isascii() and timestamp.isdigit()) or abs(time.time() - int(timestamp)) > 300: return False llave = base64.b64decode(secreto.removeprefix("whsec_")) contenido = f"{id_}.{timestamp}.".encode() + cuerpo_crudo esperada = base64.b64encode(hmac.new(llave, contenido, hashlib.sha256).digest()) return any( version == "v1" and hmac.compare_digest(firma.encode(), esperada) for version, _, firma in (token.partition(",") for token in headers.get("webhook-signature", "").split()) ) ``` ## 7 Espera el webhook document.completed POST `tu endpoint` · evento `document.completed` ### Qué haces Llega cuando firmó la última persona y la evidencia ya está lista para descargar. Es la señal para guardar el PDF firmado. AllSign le hace `POST` a la URL que registraste, con el evento `document.completed`. Responde `2xx` rápido y, antes de confiar en el cuerpo, verifica la firma: `webhook-signature` es un HMAC-SHA256 de `webhook-id.webhook-timestamp.cuerpo` con el secreto del webhook ([Webhooks](https://allsign.io/developers/docs/webhooks)). Esta entrega llegó de verdad al receptor del test y su firma se verificó con el secreto del webhook. ### Qué mirar - `eventType` = `"document.completed"` - `data.status` = `"completed"` **Entrega recibida · Cuerpo** ``` { "data": { "name": "Mi primer contrato (sandbox)", "nom151": null, "status": "completed", "signers": [ { "name": "Ana Torres", "email": "signer-pending@sandbox.allsign.io", "signedAt": "2026-07-11T18:01:06.969000Z", "signerId": "sgr_d64e1ca6bcf64eebac2d35da21d23c88", "authMethod": "POR_DEFINIR" } ], "documentId": "doc_fdde14eeff0b4444acec4d6b10736068", "completedAt": "2026-07-11T18:01:08.816000Z", "evidencePdf": { "url": "https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068/evidence", "sha256": "ed345419cea24ebbee27d9226b383468c70176e89b2391c5dec89bd4b7df4682", "mimeType": "application/pdf", "sizeBytes": 81606 } }, "eventId": "evt_118e25115c1c4b0b859b732e5ca40301", "livemode": false, "tenantId": "d521e797-924d-41c6-be8b-a3dcd62f5b6f", "eventType": "document.completed", "apiVersion": "2026-07-11", "occurredAt": "2026-07-11T18:01:08.818Z" } ``` **Entrega recibida · Cabeceras** ``` { "content-type": "application/json", "allsign-event": "document.completed", "allsign-livemode": "false", "webhook-id": "118e2511-5c1c-4b0b-859b-732e5ca40301", "webhook-timestamp": "1783792869", "webhook-signature": "v1,", "allsign-delivery-id": "8510c66d-8d0a-417a-b767-f1d78612b3ec" } ``` **Verifica la firma · Node** ``` import { createHmac, timingSafeEqual } from 'node:crypto'; export function verificarWebhook(cuerpoCrudo, headers, secreto) { const id = headers['webhook-id']; const timestamp = headers['webhook-timestamp']; if (!id || !/^\d+$/.test(timestamp ?? '') || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const llave = Buffer.from(secreto.replace(/^whsec_/, ''), 'base64'); const esperada = Buffer.from(createHmac('sha256', llave).update(`${id}.${timestamp}.${cuerpoCrudo}`).digest('base64')); return (headers['webhook-signature'] ?? '').split(' ').some((token) => { const [version, firma = ''] = token.split(','); const candidata = Buffer.from(firma); return version === 'v1' && candidata.length === esperada.length && timingSafeEqual(candidata, esperada); }); } ``` **Verifica la firma · Python** ``` import base64 import hashlib import hmac import time def verificar_webhook(cuerpo_crudo: bytes, headers: dict, secreto: str) -> bool: id_ = headers.get("webhook-id") timestamp = headers.get("webhook-timestamp", "") if not id_ or not (timestamp.isascii() and timestamp.isdigit()) or abs(time.time() - int(timestamp)) > 300: return False llave = base64.b64decode(secreto.removeprefix("whsec_")) contenido = f"{id_}.{timestamp}.".encode() + cuerpo_crudo esperada = base64.b64encode(hmac.new(llave, contenido, hashlib.sha256).digest()) return any( version == "v1" and hmac.compare_digest(firma.encode(), esperada) for version, _, firma in (token.partition(",") for token in headers.get("webhook-signature", "").split()) ) ``` ## 8 Confirma el estado final GET `/documents/{document_id}` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#retrieve-document) ### Qué haces Una sola consulta, después del webhook y nunca en un bucle: el documento está completed y todas las firmas cuentan. ### Qué mirar - `status` = `"completed"` - `signedCount` = `1` **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_fdde14eeff0b4444acec4d6b10736068", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_fdde14eeff0b4444acec4d6b10736068", "object": "document", "name": "Mi primer contrato (sandbox)", "status": "completed", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 1, "ownerId": "usr_08f394a003014001b429ca03db05b7f7", "orgId": "f4d8bad5-5bb0-451c-a917-2ae6b489d38f", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:01:02.144000Z", "updatedAt": "2026-07-11T18:01:05.421000Z" } ``` ## Errores comunes de esta receta Errores reales que el test de esta receta provocó a propósito en sandbox, sobre sus mismos pasos. Cada uno enlaza a su explicación en [Errores](https://allsign.io/developers/docs/errors). `IDEMPOTENCY_KEY_REQUIRED` · 400 · en el paso 2 (Crea el documento con su firmante) Crear sin Idempotency-Key. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_REQUIRED) **Respuesta · 400 · IDEMPOTENCY_KEY_REQUIRED** ``` { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_REQUIRED", "title": "Bad Request", "status": 400, "detail": "POST requests that create or charge require a unique Idempotency-Key (UUID v4).", "instance": "/v3/documents", "code": "IDEMPOTENCY_KEY_REQUIRED", "requestId": "req_48ab6b50c363414c8d0e41850d05d5f5" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:19 (hora CDMX) · test `docs-checks-v3/tests/recetas/primer-documento-a-firma.receta.spec.ts` `IDEMPOTENCY_KEY_INVALID` · 400 · en el paso 2 (Crea el documento con su firmante) Crear con una Idempotency-Key que no es UUID v4. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_INVALID) **Respuesta · 400 · IDEMPOTENCY_KEY_INVALID** ``` { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_INVALID", "title": "Bad Request", "status": 400, "detail": "Idempotency-Key must be a UUID v4.", "instance": "/v3/documents", "code": "IDEMPOTENCY_KEY_INVALID", "requestId": "req_e289ae908d284f7bbbdfc6abc2178d32" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:19 (hora CDMX) · test `docs-checks-v3/tests/recetas/primer-documento-a-firma.receta.spec.ts` `DOCUMENT_NOT_FOUND` · 404 · en el paso 8 (Confirma el estado final) Consultar un documento que no existe o es de otra cuenta. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND) **Respuesta · 404 · DOCUMENT_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND", "title": "Document not found", "status": 404, "detail": "No document was found with that id.", "instance": "/v3/documents/doc_dd56d81575b84eb790373c6dff92cb95", "code": "DOCUMENT_NOT_FOUND", "requestId": "req_c72bfc8e0f1f4fc69bcc8111e77dc52d" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:19 (hora CDMX) · test `docs-checks-v3/tests/recetas/primer-documento-a-firma.receta.spec.ts` `INVALID_STATE_TRANSITION` · 409 · en el paso 5 (Firma por tu firmante (solo en sandbox)) Firmar en sandbox un documento que todavía no envías. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#INVALID_STATE_TRANSITION) **Respuesta · 409 · INVALID_STATE_TRANSITION** ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_STATE_TRANSITION", "title": "Conflict", "status": 409, "detail": "The document has not been sent yet. Send it first with POST /v3/documents/{documentId}/send.", "instance": "/v3/sandbox/signers/sgr_592c7816786d4e70bb676a8be0648b5b/sign", "code": "INVALID_STATE_TRANSITION", "requestId": "req_c1f762446ffa48c2a620e3c8297daa1f", "reason": "never_invited" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:19 (hora CDMX) · test `docs-checks-v3/tests/recetas/primer-documento-a-firma.receta.spec.ts` ## Siguiente - [Descarga el PDF firmado y su evidencia](https://allsign.io/developers/docs/recetas/descarga-el-pdf-firmado-y-la-evidencia) — El PDF firmado y el PDF de evidencia de un documento completado, con su huella sha256 para archivarlos. - [Recibe y verifica los webhooks de AllSign](https://allsign.io/developers/docs/recetas/recibe-y-verifica-webhooks) — Un endpoint que recibe los eventos de AllSign, comprueba que de verdad los firmó AllSign y sobrevive a sus propias caídas y a la rotación del secreto. - [Varios firmantes, uno después de otro](https://allsign.io/developers/docs/recetas/varios-firmantes-en-orden) — Un contrato que firma primero la vendedora y después el comprador, con el comprador sin poder firmar antes de su turno. --- Fuente: https://allsign.io/developers/docs/recetas/anula-recuerda-reasigna.md Receta # Recuerda, reasigna o anula un documento que nadie firma **Objetivo:** Destrabar un documento enviado: recordarle al firmante, pasarle su lugar a otra persona y, si ya no procede, anularlo dejando la razón. Para: soporte · nivel intermedio · 10 min · Necesitas: API key de sandbox · Un documento enviado que sigue sin firmarse ## Para agentes y asistentes de IA - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** → [Entornos](https://allsign.io/developers/docs/environments) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:16 (hora CDMX) · test `docs-checks-v3/tests/recetas/anula-recuerda-reasigna.receta.spec.ts` Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). ## 1 Ubica al firmante que no ha firmado GET `/documents/{document_id}/signers` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#list-signers) ### Qué haces Parte de un documento ya enviado. Cada firmante trae su id sgr\_…, su status y cómo le llegó su invitación. ### Qué mirar - `data.0.id` = `"sgr_80189c3a228a438cbb36757ed5f4738c"` - `data.0.status` = `"sent"` - `data.0.delivery` = `null` **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "sgr_80189c3a228a438cbb36757ed5f4738c", "object": "signer", "documentId": "doc_d0a1551c68d6457294e702fb25d07e4e", "email": "signer-pending@sandbox.allsign.io", "phone": null, "name": "Ana Torres", "status": "sent", "signedAt": null, "routingOrder": null, "delivery": null } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": null } ``` ## 2 Recuérdale que firme POST `/documents/{document_id}/signers/{signer_id}/remind` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#remind-signer) ### Qué haces Le reenvía su invitación por el mismo canal. En sandbox ningún mensaje sale a @sandbox.allsign.io: delivered viene en false y nextAllowedAt es el mismo instante, así que no corre ninguna espera. ### Qué mirar - `channel` = `"email"` - `delivered` = `false` - `nextAllowedAt` = `"2026-07-11T18:00:00.000000Z"` ### En producción Le llega de nuevo el correo o el WhatsApp y delivered viene en true. El siguiente recordatorio a esa persona queda bloqueado 4 horas: antes de nextAllowedAt la API responde 429, así que no lo reintentes en un bucle. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/remind' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/remind', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/remind", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={}, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "documentId": "doc_d0a1551c68d6457294e702fb25d07e4e", "signerId": "sgr_80189c3a228a438cbb36757ed5f4738c", "sentAt": "2026-07-11T18:00:00.000000Z", "nextAllowedAt": "2026-07-11T18:00:00.000000Z", "channel": "email", "delivered": false } ``` ## 3 Pásale su lugar a otra persona POST `/documents/{document_id}/signers/{signer_id}/reassign` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#reassign-signer) ### Qué haces Conserva el lugar: mismo id sgr\_, mismo rol, mismas cajas y mismo turno; cambia la persona. La liga de la persona anterior deja de servir. Si ya había capturado algo, la API te pide confirm: true para descartarlo. ### Qué mirar en sandbox - `id` = `"sgr_80189c3a228a438cbb36757ed5f4738c"` - `email` = `"signer-pending+relevo@sandbox.allsign.io"` - `name` = `"Luis Ramírez"` - `status` = `"pending"` ### En producción La persona nueva recibe su propia invitación. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/reassign' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "email": "signer-pending+relevo@sandbox.allsign.io", "name": "Luis Ramírez", "reason": "Cambió el representante legal" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/reassign', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ email: 'signer-pending+relevo@sandbox.allsign.io', name: 'Luis Ramírez', reason: 'Cambió el representante legal', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/signers/sgr_80189c3a228a438cbb36757ed5f4738c/reassign", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "email": "signer-pending+relevo@sandbox.allsign.io", "name": "Luis Ramírez", "reason": "Cambió el representante legal", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "sgr_80189c3a228a438cbb36757ed5f4738c", "object": "signer", "documentId": "doc_d0a1551c68d6457294e702fb25d07e4e", "email": "signer-pending+relevo@sandbox.allsign.io", "phone": null, "name": "Luis Ramírez", "status": "pending", "signedAt": null, "routingOrder": null, "delivery": null } ``` ## 4 Anula el documento POST `/documents/{document_id}/void` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#void-document) ### Qué haces Anular es definitivo: el documento queda voided, nadie más puede firmarlo y la razón queda en su bitácora. ### Qué mirar - `status` = `"voided"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/void' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "reason": "Se firmará otra versión con el monto corregido" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/void', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ reason: 'Se firmará otra versión con el monto corregido', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/void", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "reason": "Se firmará otra versión con el monto corregido", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_d0a1551c68d6457294e702fb25d07e4e", "object": "document", "name": "Contrato de arrendamiento — Ana Torres", "status": "voided", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_024a2896938b4a6d81723e882bd556a9", "orgId": "f2cfe411-e1ac-4881-bf0c-a252d08e423a", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T17:59:57.129000Z", "updatedAt": "2026-07-11T18:00:17.507000Z" } ``` ## 5 Si lo anulas otra vez POST `/documents/{document_id}/void` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#void-document) ### Qué haces Anular un documento ya anulado responde 200 con el mismo documento y no cambia nada: es así a propósito, para que un reintento no falle. No cuentes con un error para saber si ya estaba anulado: revisa status. ### Qué mirar - `status` = `"voided"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/void' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "reason": "Se firmará otra versión con el monto corregido" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/void', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ reason: 'Se firmará otra versión con el monto corregido', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_d0a1551c68d6457294e702fb25d07e4e/void", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "reason": "Se firmará otra versión con el monto corregido", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_d0a1551c68d6457294e702fb25d07e4e", "object": "document", "name": "Contrato de arrendamiento — Ana Torres", "status": "voided", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_024a2896938b4a6d81723e882bd556a9", "orgId": "f2cfe411-e1ac-4881-bf0c-a252d08e423a", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T17:59:57.129000Z", "updatedAt": "2026-07-11T18:00:17.507000Z" } ``` ## Errores comunes de esta receta Errores reales que el test de esta receta provocó a propósito en sandbox, sobre sus mismos pasos. Cada uno enlaza a su explicación en [Errores](https://allsign.io/developers/docs/errors). `SIGNER_NOT_FOUND` · 404 · en el paso 2 (Recuérdale que firme) Recuérdale a un firmante que no es de ese documento. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#SIGNER_NOT_FOUND) **Respuesta · 404 · SIGNER_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#SIGNER_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Signer '8bc0129e-c73d-4d49-b5d2-c0335a209b8d' not found in this document.", "instance": "/v3/documents/doc_ca783e90f0444172a7fbe75400ab5ebc/signers/sgr_8bc0129ec73d4d49b5d2c0335a209b8d/remind", "code": "SIGNER_NOT_FOUND", "requestId": "req_aa688e8a2b7e46ab88bb8e67a281ba61" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/anula-recuerda-reasigna.receta.spec.ts` `INVALID_STATE_TRANSITION` · 409 · en el paso 2 (Recuérdale que firme) Recuérdale a alguien de un documento anulado. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#INVALID_STATE_TRANSITION) **Respuesta · 409 · INVALID_STATE_TRANSITION** ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_STATE_TRANSITION", "title": "Conflict", "status": 409, "detail": "Document is in terminal state 'ANULADO' — cannot remind.", "instance": "/v3/documents/doc_ca783e90f0444172a7fbe75400ab5ebc/signers/sgr_f70f3376d8fc450a97111874e086f6b6/remind", "code": "INVALID_STATE_TRANSITION", "requestId": "req_18baf385999e4893975db73d70c38511", "reason": "document_terminal" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/anula-recuerda-reasigna.receta.spec.ts` `INVALID_STATE_TRANSITION` · 409 · en el paso 3 (Pásale su lugar a otra persona) Reasigna a alguien que ya firmó. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#INVALID_STATE_TRANSITION) **Respuesta · 409 · INVALID_STATE_TRANSITION** ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_STATE_TRANSITION", "title": "Conflict", "status": 409, "detail": "No se puede reasignar a un firmante que ya firmó.", "instance": "/v3/documents/doc_b7b43220a55940578a9feaef6c618cf5/signers/sgr_7d4f43e179d1487aab88d212de9def03/reassign", "code": "INVALID_STATE_TRANSITION", "requestId": "req_560c08b2d31f4128b8762eeec30fbf4e", "reason": "already_signed" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/anula-recuerda-reasigna.receta.spec.ts` ## Siguiente - [Lleva tu primer documento a firma](https://allsign.io/developers/docs/recetas/primer-documento-a-firma) — Un documento firmado de punta a punta en sandbox, con el aviso de cada firma y el de cierre llegando a tu servidor. - [Recibe y verifica los webhooks de AllSign](https://allsign.io/developers/docs/recetas/recibe-y-verifica-webhooks) — Un endpoint que recibe los eventos de AllSign, comprueba que de verdad los firmó AllSign y sobrevive a sus propias caídas y a la rotación del secreto. --- Fuente: https://allsign.io/developers/docs/recetas/descarga-el-pdf-firmado-y-la-evidencia.md Receta # Descarga el PDF firmado y su evidencia **Objetivo:** El PDF firmado y el PDF de evidencia de un documento completado, con su huella sha256 para archivarlos. Para: integrador · nivel inicial · 5 min · Necesitas: API key de sandbox ## Para agentes y asistentes de IA - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** → [Entornos](https://allsign.io/developers/docs/environments) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/descarga-el-pdf-firmado-y-la-evidencia.receta.spec.ts` Ejemplo de sandbox: se grabó con una key de prueba y parte de lo que ves solo pasa así en sandbox; en producción el resultado puede ser distinto. Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). ## 1 Crea el documento con su firmante POST `/documents` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#create-document) ### Qué haces El documento nace en draft. Guarda su id: lo vas a necesitar para pedir la evidencia y el PDF. ### Qué mirar en sandbox - `id` = `"doc_c18e3aaf505d4e8aa839591f6d38f7b9"` - `status` = `"draft"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Contrato de servicios (sandbox)", "file": { "content": "'"$(base64 < contrato.pdf | tr -d '\n')"'", "fileType": "pdf", "name": "contrato.pdf" }, "signers": [ { "email": "signer-success@sandbox.allsign.io", "name": "Ana Torres" } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/documents', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Contrato de servicios (sandbox)', file: { content: readFileSync('contrato.pdf').toString('base64'), fileType: 'pdf', name: 'contrato.pdf', }, signers: [ { email: 'signer-success@sandbox.allsign.io', name: 'Ana Torres', }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/documents", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "Contrato de servicios (sandbox)", "file": { "content": base64.b64encode(Path("contrato.pdf").read_bytes()).decode(), "fileType": "pdf", "name": "contrato.pdf", }, "signers": [ { "email": "signer-success@sandbox.allsign.io", "name": "Ana Torres", }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "doc_c18e3aaf505d4e8aa839591f6d38f7b9", "object": "document", "name": "Contrato de servicios (sandbox)", "status": "draft", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_d6f2ef7bc72d4754940f12e71ad1964f", "orgId": "cc2dd4db-c027-496a-924f-bcdbb9b51387", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` ## 2 Envíalo a firma POST `/documents/{document_id}/send` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#send-document) ### Qué mirar - `status` = `"completed"` ### En producción Aquí verás awaiting\_signatures y la evidencia estará lista cuando firme la última persona; te enterarás por el webhook document.completed. En sandbox signer-success@sandbox.allsign.io firma en segundos. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/send' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/send', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/send", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={}, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_c18e3aaf505d4e8aa839591f6d38f7b9", "object": "document", "name": "Contrato de servicios (sandbox)", "status": "completed", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 1, "ownerId": "usr_d6f2ef7bc72d4754940f12e71ad1964f", "orgId": "cc2dd4db-c027-496a-924f-bcdbb9b51387", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:01.978000Z" } ``` ## 3 Pide la evidencia GET `/documents/{document_id}/evidence` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#get-evidence-bundle) ### Qué haces Con available en true, evidencePdf.url es una liga firmada para descargar el PDF de evidencia sin la API key; vence, así que pídela cuando la vayas a usar. Guarda el sha256 junto al archivo para comprobar después que nadie lo cambió. ### Qué mirar - `available` = `true` - `evidencePdf.url` = `""` - `evidencePdf.sha256` = `"8a6bf4a711f5204bcce37981400b4a4030d969b33d24e7f2ad78748ca1e68725"` **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/evidence' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/evidence', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/evidence", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "documentId": "doc_c18e3aaf505d4e8aa839591f6d38f7b9", "available": true, "evidencePdf": { "url": "", "sha256": "8a6bf4a711f5204bcce37981400b4a4030d969b33d24e7f2ad78748ca1e68725" }, "nom151": null, "reason": null, "signedCount": 1, "totalSigners": 1 } ``` ## 4 Descarga el PDF firmado GET `/documents/{document_id}/file` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#download-document-file) ### Qué haces El documento con las firmas estampadas. La respuesta es el PDF mismo, no JSON: guárdalo tal cual. **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/file' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` import { writeFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/file', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); writeFileSync('descarga.pdf', Buffer.from(await respuesta.arrayBuffer())); ``` **Python** ``` import os from pathlib import Path import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_c18e3aaf505d4e8aa839591f6d38f7b9/file", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) Path("descarga.pdf").write_bytes(respuesta.content) ``` **Respuesta · 200** ``` ``` ## Errores comunes de esta receta Errores reales que el test de esta receta provocó a propósito en sandbox, sobre sus mismos pasos. Cada uno enlaza a su explicación en [Errores](https://allsign.io/developers/docs/errors). `INVALID_ID` · 400 · en el paso 3 (Pide la evidencia) Pide la evidencia con un id incompleto. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#INVALID_ID) **Respuesta · 400 · INVALID_ID** ``` { "type": "https://allsign.io/developers/docs/errors#INVALID_ID", "title": "Malformed identifier", "status": 400, "detail": "Invalid document id.", "instance": "/v3/documents/doc_123/evidence", "code": "INVALID_ID", "requestId": "req_8a1bc74e22d945a6ad9358d2f8b27ec2" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/descarga-el-pdf-firmado-y-la-evidencia.receta.spec.ts` `DOCUMENT_NOT_FOUND` · 404 · en el paso 4 (Descarga el PDF firmado) Descarga el PDF de un documento que no existe o es de otra cuenta. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND) **Respuesta · 404 · DOCUMENT_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND", "title": "Document not found", "status": 404, "detail": "Document not found", "instance": "/v3/documents/doc_9c9f2f6749794c8c8717b0fd5458f074/file", "code": "DOCUMENT_NOT_FOUND", "requestId": "req_9099fea09e7e4822a33e00dde0d7dc14" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/descarga-el-pdf-firmado-y-la-evidencia.receta.spec.ts` `DOCUMENT_NOT_FOUND` · 404 · en el paso 3 (Pide la evidencia) Pide la evidencia de un documento que no existe o es de otra cuenta. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND) **Respuesta · 404 · DOCUMENT_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND", "title": "Document not found", "status": 404, "detail": "No document was found with that id.", "instance": "/v3/documents/doc_9c9f2f6749794c8c8717b0fd5458f074/evidence", "code": "DOCUMENT_NOT_FOUND", "requestId": "req_f004c5b00c5e41eba0d4a03491fdf499" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/descarga-el-pdf-firmado-y-la-evidencia.receta.spec.ts` --- Fuente: https://allsign.io/developers/docs/recetas/documento-desde-plantilla-docx.md Receta # Crea un contrato desde tu plantilla de Word **Objetivo:** Un acuerdo de confidencialidad generado desde tu plantilla DOCX, con los datos de cada parte en su lugar y enviado a firma. Para: integrador · nivel intermedio · 10 min · Necesitas: API key de sandbox · Una plantilla DOCX con variables {{ rol\_\_campo }}: acuerdo.docx ## Para agentes y asistentes de IA - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** → [Entornos](https://allsign.io/developers/docs/environments) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/documento-desde-plantilla-docx.receta.spec.ts` Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). ## 1 Da de alta tu plantilla de Word POST `/templates` [ver en la referencia](https://allsign.io/developers/docs/endpoints/templates#create-template) ### Qué haces AllSign lee cada llave {{ }} del Word y la vuelve una variable. Las que llevan doble guion bajo, como parte\_a\_\_nombre, pertenecen a un rol (Parte A); las demás, como folio\_acuerdo, son del documento. La das de alta una vez y la reusas en cada contrato. ### Qué mirar - `id` = `"tmpl_67fb98658fe548d3a71eae28087d7060"` - `variableCount` = `22` - `readiness` = `"pending"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Acuerdo de confidencialidad", "file": { "content": "'"$(base64 < acuerdo.docx | tr -d '\n')"'", "fileType": "docx", "name": "acuerdo.docx" } }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/templates', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Acuerdo de confidencialidad', file: { content: readFileSync('acuerdo.docx').toString('base64'), fileType: 'docx', name: 'acuerdo.docx', }, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/templates", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "Acuerdo de confidencialidad", "file": { "content": base64.b64encode(Path("acuerdo.docx").read_bytes()).decode(), "fileType": "docx", "name": "acuerdo.docx", }, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "tmpl_67fb98658fe548d3a71eae28087d7060", "object": "template", "name": "Acuerdo de confidencialidad", "description": null, "fileType": "docx", "originalFilename": "acuerdo.docx", "aiEditable": true, "variableCount": 22, "fieldCount": 0, "unassignedFieldCount": 0, "roleCount": 0, "pendingCandidates": 38, "readiness": "pending", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 1, "previewUrl": null, "downloadUrl": "/templates/tmpl_67fb98658fe548d3a71eae28087d7060/download", "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` ## 2 Revisa qué variables pide y de quién es cada una GET `/templates/{template_id}/variables` [ver en la referencia](https://allsign.io/developers/docs/endpoints/templates#list-template-variables) ### Qué haces role ya viene resuelto desde el nombre de la llave: no parseas nada. Revisa también type: AllSign lo deduce del nombre, y aquí monto\_pena\_letra salió como currency aunque lleva el monto escrito con letra. ### Qué mirar - `data.5.type` = `"currency"` - `data.9.role` = `"Parte A"` **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "templateId": "tmpl_67fb98658fe548d3a71eae28087d7060", "data": [ { "name": "ciudad_celebracion", "label": "Ciudad Celebracion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "fecha_celebracion", "label": "Fecha Celebracion", "type": "date", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "folio_acuerdo", "label": "Folio Acuerdo", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "jurisdiccion", "label": "Jurisdiccion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "monto_pena", "label": "Monto Pena", "type": "currency", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "monto_pena_letra", "label": "Monto Pena Letra", "type": "currency", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "objeto_relacion", "label": "Objeto Relacion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__domicilio", "label": "Domicilio", "type": "textarea", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__email", "label": "Email", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__nombre", "label": "Nombre", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__representante", "label": "Representante", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__rfc", "label": "Rfc", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__telefono", "label": "Telefono", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__domicilio", "label": "Domicilio", "type": "textarea", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__email", "label": "Email", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__nombre", "label": "Nombre", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__representante", "label": "Representante", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__rfc", "label": "Rfc", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__telefono", "label": "Telefono", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "tipo_informacion_confidencial", "label": "Tipo Informacion Confidencial", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "vigencia", "label": "Vigencia", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "vigencia_post_terminacion", "label": "Vigencia Post Terminacion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null } ], "hasMore": false } ``` ## 3 Corrige el tipo que se dedujo mal PATCH `/templates/{template_id}/variables` [ver en la referencia](https://allsign.io/developers/docs/endpoints/templates#update-template-variable) ### Qué haces Responde la lista completa de variables, ya con monto\_pena\_letra como text. Si no lo corriges, cada documento que mande el monto con letra se rechaza como INVALID\_AMOUNT. ### Qué mirar - `data.5.type` = `"text"` **cURL** ``` curl -X PATCH 'https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "name": "monto_pena_letra", "type": "text" }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'monto_pena_letra', type: 'text', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.patch( "https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/variables", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "name": "monto_pena_letra", "type": "text", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "templateId": "tmpl_67fb98658fe548d3a71eae28087d7060", "data": [ { "name": "ciudad_celebracion", "label": "Ciudad Celebracion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "fecha_celebracion", "label": "Fecha Celebracion", "type": "date", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "folio_acuerdo", "label": "Folio Acuerdo", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "jurisdiccion", "label": "Jurisdiccion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "monto_pena", "label": "Monto Pena", "type": "currency", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "monto_pena_letra", "label": "Monto Pena Letra", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "objeto_relacion", "label": "Objeto Relacion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__domicilio", "label": "Domicilio", "type": "textarea", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__email", "label": "Email", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__nombre", "label": "Nombre", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__representante", "label": "Representante", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__rfc", "label": "Rfc", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_a__telefono", "label": "Telefono", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte A", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__domicilio", "label": "Domicilio", "type": "textarea", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__email", "label": "Email", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__nombre", "label": "Nombre", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__representante", "label": "Representante", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__rfc", "label": "Rfc", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "parte_b__telefono", "label": "Telefono", "type": "text", "required": true, "defaultValue": null, "options": null, "role": "Parte B", "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "tipo_informacion_confidencial", "label": "Tipo Informacion Confidencial", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "vigencia", "label": "Vigencia", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null }, { "name": "vigencia_post_terminacion", "label": "Vigencia Post Terminacion", "type": "text", "required": true, "defaultValue": null, "options": null, "role": null, "source": null, "page": null, "rect": null, "slots": null, "filledBy": null, "snippet": null, "analysisIndex": null } ], "hasMore": false } ``` ## 4 Prueba unos valores sin crear nada POST `/templates/{template_id}/validate-values` [ver en la referencia](https://allsign.io/developers/docs/endpoints/templates#validate-template-values) ### Qué haces Valídalo antes de crear: es gratis y no crea nada. Si lo llamas mientras tu usuario escribe, hazlo con debounce para no gastar tu límite de 100 solicitudes por minuto. Responde 200 aunque encuentre problemas, porque es un diagnóstico, no un rechazo: errors dice qué variable falla y por qué, e ignored las llaves que mandaste y la plantilla no tiene. ### Qué mirar - `valid` = `false` - `errors` = `[{"name":"fecha_celebracion","code":"INVALID_DATE","detail":"Write a date as YYYY-MM-DD or DD/MM/YYYY."},{"name":"monto_pena","code":"INVALID_AMOUNT","detail":"Write a numeric amount. Example: 15000 or $15,000.00."},{"name":"numero_cliente","code":"UNKNOWN_VARIABLE","detail":"The template has no variable with this name. Check the spelling in GET /v3/templates/{id}/variables."}]` - `ignored` = `["numero_cliente"]` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/validate-values' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "templateValues": { "folio_acuerdo": "NDA-2026-014", "fecha_celebracion": "mañana", "ciudad_celebracion": "Ciudad de México", "objeto_relacion": "una integración de firma electrónica", "tipo_informacion_confidencial": "técnica, comercial y financiera", "vigencia": "2 años", "vigencia_post_terminacion": "3 años", "monto_pena": "cien mil", "monto_pena_letra": "doscientos cincuenta mil pesos 00/100 M.N.", "jurisdiccion": "Ciudad de México", "parte_a__nombre": "Comercializadora Ejemplo SA de CV", "parte_a__rfc": "CEJ200101AB1", "parte_a__domicilio": "Av. Reforma 100, Cuauhtémoc, Ciudad de México", "parte_a__representante": "Ana Torres", "parte_a__email": "contacto@ejemplo.com", "parte_a__telefono": "+525555550001", "parte_b__nombre": "Servicios Muestra SC", "parte_b__rfc": "SMU190505CD2", "parte_b__domicilio": "Insurgentes Sur 200, Benito Juárez, Ciudad de México", "parte_b__representante": "Luis Ramírez", "parte_b__email": "legal@ejemplo.com", "parte_b__telefono": "+525555550002", "numero_cliente": "C-0042" } }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/validate-values', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ templateValues: { folio_acuerdo: 'NDA-2026-014', fecha_celebracion: 'mañana', ciudad_celebracion: 'Ciudad de México', objeto_relacion: 'una integración de firma electrónica', tipo_informacion_confidencial: 'técnica, comercial y financiera', vigencia: '2 años', vigencia_post_terminacion: '3 años', monto_pena: 'cien mil', monto_pena_letra: 'doscientos cincuenta mil pesos 00/100 M.N.', jurisdiccion: 'Ciudad de México', parte_a__nombre: 'Comercializadora Ejemplo SA de CV', parte_a__rfc: 'CEJ200101AB1', parte_a__domicilio: 'Av. Reforma 100, Cuauhtémoc, Ciudad de México', parte_a__representante: 'Ana Torres', parte_a__email: 'contacto@ejemplo.com', parte_a__telefono: '+525555550001', parte_b__nombre: 'Servicios Muestra SC', parte_b__rfc: 'SMU190505CD2', parte_b__domicilio: 'Insurgentes Sur 200, Benito Juárez, Ciudad de México', parte_b__representante: 'Luis Ramírez', parte_b__email: 'legal@ejemplo.com', parte_b__telefono: '+525555550002', numero_cliente: 'C-0042', }, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/validate-values", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "templateValues": { "folio_acuerdo": "NDA-2026-014", "fecha_celebracion": "mañana", "ciudad_celebracion": "Ciudad de México", "objeto_relacion": "una integración de firma electrónica", "tipo_informacion_confidencial": "técnica, comercial y financiera", "vigencia": "2 años", "vigencia_post_terminacion": "3 años", "monto_pena": "cien mil", "monto_pena_letra": "doscientos cincuenta mil pesos 00/100 M.N.", "jurisdiccion": "Ciudad de México", "parte_a__nombre": "Comercializadora Ejemplo SA de CV", "parte_a__rfc": "CEJ200101AB1", "parte_a__domicilio": "Av. Reforma 100, Cuauhtémoc, Ciudad de México", "parte_a__representante": "Ana Torres", "parte_a__email": "contacto@ejemplo.com", "parte_a__telefono": "+525555550001", "parte_b__nombre": "Servicios Muestra SC", "parte_b__rfc": "SMU190505CD2", "parte_b__domicilio": "Insurgentes Sur 200, Benito Juárez, Ciudad de México", "parte_b__representante": "Luis Ramírez", "parte_b__email": "legal@ejemplo.com", "parte_b__telefono": "+525555550002", "numero_cliente": "C-0042", }, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "template_values_validation", "templateId": "tmpl_67fb98658fe548d3a71eae28087d7060", "valid": false, "errors": [ { "name": "fecha_celebracion", "code": "INVALID_DATE", "detail": "Write a date as YYYY-MM-DD or DD/MM/YYYY." }, { "name": "monto_pena", "code": "INVALID_AMOUNT", "detail": "Write a numeric amount. Example: 15000 or $15,000.00." }, { "name": "numero_cliente", "code": "UNKNOWN_VARIABLE", "detail": "The template has no variable with this name. Check the spelling in GET /v3/templates/{id}/variables." } ], "warnings": [], "ignored": [ "numero_cliente" ] } ``` ## 5 Valida los valores completos POST `/templates/{template_id}/validate-values` [ver en la referencia](https://allsign.io/developers/docs/endpoints/templates#validate-template-values) ### Qué haces Con valid en true, ya puedes crear el documento con estos mismos valores. ### Qué mirar - `valid` = `true` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/validate-values' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "templateValues": { "folio_acuerdo": "NDA-2026-014", "fecha_celebracion": "2026-07-11", "ciudad_celebracion": "Ciudad de México", "objeto_relacion": "una integración de firma electrónica", "tipo_informacion_confidencial": "técnica, comercial y financiera", "vigencia": "2 años", "vigencia_post_terminacion": "3 años", "monto_pena": "250000", "monto_pena_letra": "doscientos cincuenta mil pesos 00/100 M.N.", "jurisdiccion": "Ciudad de México", "parte_a__nombre": "Comercializadora Ejemplo SA de CV", "parte_a__rfc": "CEJ200101AB1", "parte_a__domicilio": "Av. Reforma 100, Cuauhtémoc, Ciudad de México", "parte_a__representante": "Ana Torres", "parte_a__email": "contacto@ejemplo.com", "parte_a__telefono": "+525555550001", "parte_b__nombre": "Servicios Muestra SC", "parte_b__rfc": "SMU190505CD2", "parte_b__domicilio": "Insurgentes Sur 200, Benito Juárez, Ciudad de México", "parte_b__representante": "Luis Ramírez", "parte_b__email": "legal@ejemplo.com", "parte_b__telefono": "+525555550002" } }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/validate-values', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ templateValues: { folio_acuerdo: 'NDA-2026-014', fecha_celebracion: '2026-07-11', ciudad_celebracion: 'Ciudad de México', objeto_relacion: 'una integración de firma electrónica', tipo_informacion_confidencial: 'técnica, comercial y financiera', vigencia: '2 años', vigencia_post_terminacion: '3 años', monto_pena: '250000', monto_pena_letra: 'doscientos cincuenta mil pesos 00/100 M.N.', jurisdiccion: 'Ciudad de México', parte_a__nombre: 'Comercializadora Ejemplo SA de CV', parte_a__rfc: 'CEJ200101AB1', parte_a__domicilio: 'Av. Reforma 100, Cuauhtémoc, Ciudad de México', parte_a__representante: 'Ana Torres', parte_a__email: 'contacto@ejemplo.com', parte_a__telefono: '+525555550001', parte_b__nombre: 'Servicios Muestra SC', parte_b__rfc: 'SMU190505CD2', parte_b__domicilio: 'Insurgentes Sur 200, Benito Juárez, Ciudad de México', parte_b__representante: 'Luis Ramírez', parte_b__email: 'legal@ejemplo.com', parte_b__telefono: '+525555550002', }, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.post( "https://api.allsign.io/v3/templates/tmpl_67fb98658fe548d3a71eae28087d7060/validate-values", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "templateValues": { "folio_acuerdo": "NDA-2026-014", "fecha_celebracion": "2026-07-11", "ciudad_celebracion": "Ciudad de México", "objeto_relacion": "una integración de firma electrónica", "tipo_informacion_confidencial": "técnica, comercial y financiera", "vigencia": "2 años", "vigencia_post_terminacion": "3 años", "monto_pena": "250000", "monto_pena_letra": "doscientos cincuenta mil pesos 00/100 M.N.", "jurisdiccion": "Ciudad de México", "parte_a__nombre": "Comercializadora Ejemplo SA de CV", "parte_a__rfc": "CEJ200101AB1", "parte_a__domicilio": "Av. Reforma 100, Cuauhtémoc, Ciudad de México", "parte_a__representante": "Ana Torres", "parte_a__email": "contacto@ejemplo.com", "parte_a__telefono": "+525555550001", "parte_b__nombre": "Servicios Muestra SC", "parte_b__rfc": "SMU190505CD2", "parte_b__domicilio": "Insurgentes Sur 200, Benito Juárez, Ciudad de México", "parte_b__representante": "Luis Ramírez", "parte_b__email": "legal@ejemplo.com", "parte_b__telefono": "+525555550002", }, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "template_values_validation", "templateId": "tmpl_67fb98658fe548d3a71eae28087d7060", "valid": true, "errors": [], "warnings": [], "ignored": [] } ``` ## 6 Crea el documento con un firmante por rol POST `/documents` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#create-document) ### Qué haces roleName conecta a cada firmante con su rol de la plantilla. Los valores van en templateValues con las mismas llaves que validaste. Crear no envía nada: el documento nace en draft y todavía lo puedes revisar. ### Qué mirar en sandbox - `id` = `"doc_b5dcbda2f82c4b4fbec9cc944a7b2ea2"` - `status` = `"draft"` - `templateId` = `"tmpl_67fb98658fe548d3a71eae28087d7060"` - `signerCount` = `2` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "templateId": "tmpl_67fb98658fe548d3a71eae28087d7060", "name": "Acuerdo de confidencialidad NDA-2026-014", "templateValues": { "folio_acuerdo": "NDA-2026-014", "fecha_celebracion": "2026-07-11", "ciudad_celebracion": "Ciudad de México", "objeto_relacion": "una integración de firma electrónica", "tipo_informacion_confidencial": "técnica, comercial y financiera", "vigencia": "2 años", "vigencia_post_terminacion": "3 años", "monto_pena": "250000", "monto_pena_letra": "doscientos cincuenta mil pesos 00/100 M.N.", "jurisdiccion": "Ciudad de México", "parte_a__nombre": "Comercializadora Ejemplo SA de CV", "parte_a__rfc": "CEJ200101AB1", "parte_a__domicilio": "Av. Reforma 100, Cuauhtémoc, Ciudad de México", "parte_a__representante": "Ana Torres", "parte_a__email": "contacto@ejemplo.com", "parte_a__telefono": "+525555550001", "parte_b__nombre": "Servicios Muestra SC", "parte_b__rfc": "SMU190505CD2", "parte_b__domicilio": "Insurgentes Sur 200, Benito Juárez, Ciudad de México", "parte_b__representante": "Luis Ramírez", "parte_b__email": "legal@ejemplo.com", "parte_b__telefono": "+525555550002" }, "signers": [ { "email": "signer-success@sandbox.allsign.io", "name": "Ana Torres", "roleName": "Parte A" }, { "email": "ana@ejemplo.com", "name": "Luis Ramírez", "roleName": "Parte B" } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ templateId: 'tmpl_67fb98658fe548d3a71eae28087d7060', name: 'Acuerdo de confidencialidad NDA-2026-014', templateValues: { folio_acuerdo: 'NDA-2026-014', fecha_celebracion: '2026-07-11', ciudad_celebracion: 'Ciudad de México', objeto_relacion: 'una integración de firma electrónica', tipo_informacion_confidencial: 'técnica, comercial y financiera', vigencia: '2 años', vigencia_post_terminacion: '3 años', monto_pena: '250000', monto_pena_letra: 'doscientos cincuenta mil pesos 00/100 M.N.', jurisdiccion: 'Ciudad de México', parte_a__nombre: 'Comercializadora Ejemplo SA de CV', parte_a__rfc: 'CEJ200101AB1', parte_a__domicilio: 'Av. Reforma 100, Cuauhtémoc, Ciudad de México', parte_a__representante: 'Ana Torres', parte_a__email: 'contacto@ejemplo.com', parte_a__telefono: '+525555550001', parte_b__nombre: 'Servicios Muestra SC', parte_b__rfc: 'SMU190505CD2', parte_b__domicilio: 'Insurgentes Sur 200, Benito Juárez, Ciudad de México', parte_b__representante: 'Luis Ramírez', parte_b__email: 'legal@ejemplo.com', parte_b__telefono: '+525555550002', }, signers: [ { email: 'signer-success@sandbox.allsign.io', name: 'Ana Torres', roleName: 'Parte A', }, { email: 'ana@ejemplo.com', name: 'Luis Ramírez', roleName: 'Parte B', }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "templateId": "tmpl_67fb98658fe548d3a71eae28087d7060", "name": "Acuerdo de confidencialidad NDA-2026-014", "templateValues": { "folio_acuerdo": "NDA-2026-014", "fecha_celebracion": "2026-07-11", "ciudad_celebracion": "Ciudad de México", "objeto_relacion": "una integración de firma electrónica", "tipo_informacion_confidencial": "técnica, comercial y financiera", "vigencia": "2 años", "vigencia_post_terminacion": "3 años", "monto_pena": "250000", "monto_pena_letra": "doscientos cincuenta mil pesos 00/100 M.N.", "jurisdiccion": "Ciudad de México", "parte_a__nombre": "Comercializadora Ejemplo SA de CV", "parte_a__rfc": "CEJ200101AB1", "parte_a__domicilio": "Av. Reforma 100, Cuauhtémoc, Ciudad de México", "parte_a__representante": "Ana Torres", "parte_a__email": "contacto@ejemplo.com", "parte_a__telefono": "+525555550001", "parte_b__nombre": "Servicios Muestra SC", "parte_b__rfc": "SMU190505CD2", "parte_b__domicilio": "Insurgentes Sur 200, Benito Juárez, Ciudad de México", "parte_b__representante": "Luis Ramírez", "parte_b__email": "legal@ejemplo.com", "parte_b__telefono": "+525555550002", }, "signers": [ { "email": "signer-success@sandbox.allsign.io", "name": "Ana Torres", "roleName": "Parte A", }, { "email": "ana@ejemplo.com", "name": "Luis Ramírez", "roleName": "Parte B", }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "doc_b5dcbda2f82c4b4fbec9cc944a7b2ea2", "object": "document", "name": "Acuerdo de confidencialidad NDA-2026-014", "status": "draft", "documentType": "EDITABLE", "signerCount": 2, "signedCount": 0, "ownerId": "usr_d7bbf7f00d864ba08c91c084a095f54a", "orgId": "d324ee92-d990-4d77-871e-f021a69be102", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": "tmpl_67fb98658fe548d3a71eae28087d7060", "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:04.929000Z", "updatedAt": "2026-07-11T18:00:04.929000Z" } ``` ## 7 Envíalo a firma POST `/documents/{document_id}/send` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#send-document) ### Qué haces Aquí salen las invitaciones a las dos partes, cada una a su correo. ### Qué mirar - `status` = `"awaiting_signatures"` ### En producción Con una key live este paso consume créditos de tu saldo; en sandbox no se cobra nada. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_b5dcbda2f82c4b4fbec9cc944a7b2ea2/send' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_b5dcbda2f82c4b4fbec9cc944a7b2ea2/send', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_b5dcbda2f82c4b4fbec9cc944a7b2ea2/send", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={}, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_b5dcbda2f82c4b4fbec9cc944a7b2ea2", "object": "document", "name": "Acuerdo de confidencialidad NDA-2026-014", "status": "awaiting_signatures", "documentType": "EDITABLE", "signerCount": 2, "signedCount": 1, "ownerId": "usr_d7bbf7f00d864ba08c91c084a095f54a", "orgId": "d324ee92-d990-4d77-871e-f021a69be102", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": "tmpl_67fb98658fe548d3a71eae28087d7060", "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:04.929000Z", "updatedAt": "2026-07-11T18:00:09.623000Z" } ``` ## 8 Revisa quién ya firmó GET `/documents/{document_id}/signers` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#list-signers) ### Qué haces Cada firmante trae su propio status. No consultes esto en bucle: para enterarte cuando firme cada quien, suscríbete a los webhooks signer.signed y document.completed. ### Qué mirar - `data.0.status` = `"signed"` - `data.1.status` = `"sent"` ### En producción La Parte A firmó sola porque usamos signer-success@sandbox.allsign.io. Con firmantes reales, las dos quedan pendientes hasta que cada quien firme desde su invitación. **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_b5dcbda2f82c4b4fbec9cc944a7b2ea2/signers' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_b5dcbda2f82c4b4fbec9cc944a7b2ea2/signers', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_b5dcbda2f82c4b4fbec9cc944a7b2ea2/signers", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "sgr_520ef61be35f422387dcd6e215346e57", "object": "signer", "documentId": "doc_b5dcbda2f82c4b4fbec9cc944a7b2ea2", "email": "signer-success@sandbox.allsign.io", "phone": null, "name": "Ana Torres", "status": "signed", "signedAt": "2026-07-11T18:00:12.733000Z", "routingOrder": null, "delivery": null }, { "livemode": false, "id": "sgr_1788e01c8b924179ae95ca736bbd2663", "object": "signer", "documentId": "doc_b5dcbda2f82c4b4fbec9cc944a7b2ea2", "email": "ana@ejemplo.com", "phone": null, "name": "Luis Ramírez", "status": "sent", "signedAt": null, "routingOrder": null, "delivery": null } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": null } ``` ## Errores comunes de esta receta Errores reales que el test de esta receta provocó a propósito en sandbox, sobre sus mismos pasos. Cada uno enlaza a su explicación en [Errores](https://allsign.io/developers/docs/errors). `VALIDATION_ERROR` · 422 · en el paso 6 (Crea el documento con un firmante por rol) Crea el documento sin llenar todas las variables. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#VALIDATION_ERROR) **Respuesta · 422 · VALIDATION_ERROR** ``` { "type": "https://allsign.io/developers/docs/errors#VALIDATION_ERROR", "title": "Validation failed", "status": 422, "detail": "The values are not valid. Fix the fields listed in `errors`. You can test a set of values for free with POST /v3/templates/{id}/validate-values.", "instance": "/v3/documents", "code": "VALIDATION_ERROR", "requestId": "req_71750d833b65413ea2c9794e81405461", "errors": [ { "field": "templateValues.ciudad_celebracion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.fecha_celebracion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.folio_acuerdo", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.jurisdiccion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.monto_pena", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.monto_pena_letra", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.objeto_relacion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__domicilio", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__email", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__representante", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__rfc", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_a__telefono", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__domicilio", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__email", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__nombre", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__representante", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__rfc", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.parte_b__telefono", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.tipo_informacion_confidencial", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.vigencia", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." }, { "field": "templateValues.vigencia_post_terminacion", "code": "MISSING_REQUIRED_VARIABLE", "detail": "This required variable is missing. Send it with a non-empty value." } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/documento-desde-plantilla-docx.receta.spec.ts` `TEMPLATE_NOT_FOUND` · 404 · en el paso 6 (Crea el documento con un firmante por rol) Usa una plantilla que no existe. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#TEMPLATE_NOT_FOUND) **Respuesta · 404 · TEMPLATE_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#TEMPLATE_NOT_FOUND", "title": "Template not found", "status": 404, "detail": "Template not found or has been deleted.", "instance": "/v3/documents", "code": "TEMPLATE_NOT_FOUND", "requestId": "req_252364b6627a443285dda69be2eb4213" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/documento-desde-plantilla-docx.receta.spec.ts` `IDEMPOTENCY_KEY_REUSED` · 409 · en el paso 6 (Crea el documento con un firmante por rol) Reintenta con la misma Idempotency-Key pero otro cuerpo. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_REUSED) Este 409 solo sale si antes mandaste esa misma Idempotency-Key con otro cuerpo: aquí, la del request que creó el acuerdo NDA-2026-015, que no aparece en este bloque. Como el ejemplo genera una llave nueva, copiado tal cual no da este error. Si reintentas con la misma llave y exactamente el mismo cuerpo, recibes el mismo documento en lugar de uno duplicado; si cambiaste el cuerpo, es otro documento y lleva una llave nueva. **Respuesta · 409 · IDEMPOTENCY_KEY_REUSED** ``` { "type": "https://allsign.io/developers/docs/errors#IDEMPOTENCY_KEY_REUSED", "title": "Idempotency key reused", "status": 409, "detail": "This Idempotency-Key was already used with a different request.", "instance": "/v3/documents", "code": "IDEMPOTENCY_KEY_REUSED", "requestId": "req_7a71eb8164e14a01a6760499df342f6f" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/documento-desde-plantilla-docx.receta.spec.ts` ## Siguiente - [Recibe y verifica los webhooks de AllSign](https://allsign.io/developers/docs/recetas/recibe-y-verifica-webhooks) — Un endpoint que recibe los eventos de AllSign, comprueba que de verdad los firmó AllSign y sobrevive a sus propias caídas y a la rotación del secreto. --- Fuente: https://allsign.io/developers/docs/recetas/firma-embebida-en-tu-app.md Receta # Pon la firma dentro de tu app **Objetivo:** Tu usuario firma sin salir de tu página, en una sesión de firma que solo se abre desde tu dominio. Para: frontend · nivel intermedio · 15 min · Necesitas: API key de sandbox (solo en tu backend) · Tu dominio, aquí https://app.ejemplo.com · El SDK @allsign/embedded en tu frontend ## Para agentes y asistentes de IA - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** → [Entornos](https://allsign.io/developers/docs/environments) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:17 (hora CDMX) · test `docs-checks-v3/tests/recetas/firma-embebida-en-tu-app.receta.spec.ts` Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). ## 1 Crea el documento con su firmante y su caja de firma POST `/documents` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#create-document) ### Qué haces Todo lo que lleva tu API key pasa en tu backend: crear, enviar y abrir la sesión. El navegador nunca ve la key. ### Qué mirar - `id` = `"doc_e6d57f83692e4ff5ad07f57f6bd6a117"` - `status` = `"draft"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Contrato de servicios — Ana Torres", "file": { "content": "'"$(base64 < contrato.pdf | tr -d '\n')"'", "fileType": "pdf", "name": "contrato.pdf" }, "signers": [ { "email": "ana@ejemplo.com", "name": "Ana Torres" } ], "fields": [ { "email": "ana@ejemplo.com", "pageNumber": 1, "position": { "x": 80, "y": 620 } } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/documents', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Contrato de servicios — Ana Torres', file: { content: readFileSync('contrato.pdf').toString('base64'), fileType: 'pdf', name: 'contrato.pdf', }, signers: [ { email: 'ana@ejemplo.com', name: 'Ana Torres', }, ], fields: [ { email: 'ana@ejemplo.com', pageNumber: 1, position: { x: 80, y: 620, }, }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/documents", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "Contrato de servicios — Ana Torres", "file": { "content": base64.b64encode(Path("contrato.pdf").read_bytes()).decode(), "fileType": "pdf", "name": "contrato.pdf", }, "signers": [ { "email": "ana@ejemplo.com", "name": "Ana Torres", }, ], "fields": [ { "email": "ana@ejemplo.com", "pageNumber": 1, "position": { "x": 80, "y": 620, }, }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "doc_e6d57f83692e4ff5ad07f57f6bd6a117", "object": "document", "name": "Contrato de servicios — Ana Torres", "status": "draft", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_3ec54866cfb843df8a85d593a4f4e474", "orgId": "949b6951-4058-4a55-8931-6ba302d6824f", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` ## 2 Envíalo sin mandar invitaciones POST `/documents/{document_id}/send` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#send-document) ### Qué haces recipients vacío pasa el documento a awaiting\_signatures sin mandarle correo a nadie: la firma va a pasar dentro de tu página. Si lo envías sin recipients, AllSign invita por correo a cada firmante. ### Qué mirar - `status` = `"awaiting_signatures"` ### En producción Con una key live este envío consume créditos aunque no invite a nadie; en sandbox no se cobra nada. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_e6d57f83692e4ff5ad07f57f6bd6a117/send' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "recipients": [] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_e6d57f83692e4ff5ad07f57f6bd6a117/send', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ recipients: [], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_e6d57f83692e4ff5ad07f57f6bd6a117/send", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "recipients": [], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_e6d57f83692e4ff5ad07f57f6bd6a117", "object": "document", "name": "Contrato de servicios — Ana Torres", "status": "awaiting_signatures", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_3ec54866cfb843df8a85d593a4f4e474", "orgId": "949b6951-4058-4a55-8931-6ba302d6824f", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:01.126000Z" } ``` ## 3 Confirma que nadie recibió invitación GET `/documents/{document_id}/signers` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#list-signers) ### Qué haces pending: el firmante está en espera y no se le mandó nada. Con un envío normal aquí verías sent. ### Qué mirar - `data.0.status` = `"pending"` **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_e6d57f83692e4ff5ad07f57f6bd6a117/signers' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_e6d57f83692e4ff5ad07f57f6bd6a117/signers', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_e6d57f83692e4ff5ad07f57f6bd6a117/signers", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "sgr_fdcf573b43b24b2484b1a795ffff848f", "object": "signer", "documentId": "doc_e6d57f83692e4ff5ad07f57f6bd6a117", "email": "ana@ejemplo.com", "phone": null, "name": "Ana Torres", "status": "pending", "signedAt": null, "routingOrder": null, "delivery": null } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": null } ``` ## 4 Abre una sesión de firma para ese firmante POST `/signing-sessions` [ver en la referencia](https://allsign.io/developers/docs/endpoints/signing-sessions#create-signing-session) ### Qué haces allowedOrigins dice qué páginas pueden mostrar la firma. A tu frontend solo le pasas el clientSecret: dura 15 minutos y sirve para una sola sesión, así que pídelo cuando tu usuario vaya a firmar. ### Qué mirar - `id` = `"ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a"` - `clientSecret` = `""` - `expiresAt` = `"2026-07-11T18:15:01.869000Z"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/signing-sessions' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "documentId": "doc_e6d57f83692e4ff5ad07f57f6bd6a117", "signerEmail": "ana@ejemplo.com", "allowedOrigins": [ "https://app.ejemplo.com" ], "successUrl": "https://app.ejemplo.com/firmado", "cancelUrl": "https://app.ejemplo.com/cancelado" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/signing-sessions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ documentId: 'doc_e6d57f83692e4ff5ad07f57f6bd6a117', signerEmail: 'ana@ejemplo.com', allowedOrigins: [ 'https://app.ejemplo.com', ], successUrl: 'https://app.ejemplo.com/firmado', cancelUrl: 'https://app.ejemplo.com/cancelado', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/signing-sessions", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "documentId": "doc_e6d57f83692e4ff5ad07f57f6bd6a117", "signerEmail": "ana@ejemplo.com", "allowedOrigins": [ "https://app.ejemplo.com", ], "successUrl": "https://app.ejemplo.com/firmado", "cancelUrl": "https://app.ejemplo.com/cancelado", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a", "object": "signing_session", "status": "pending", "clientSecret": "", "document": { "id": "doc_e6d57f83692e4ff5ad07f57f6bd6a117", "title": "Contrato de servicios — Ana Torres" }, "signer": { "email": "ana@ejemplo.com", "name": "ana@ejemplo.com" }, "expiresAt": "2026-07-11T18:15:01.869000Z", "createdAt": null, "mountedAt": null, "completedAt": null, "signature": null, "evidence": null } ``` ## 5 AllSign hace el equivalente por ti: revisa la política de orígenes GET `/signing-sessions/{session_id}/policy` sin API key [ver en la referencia](https://allsign.io/developers/docs/endpoints/signing-sessions#get-session-policy) ### Qué haces Tu código no llama esta operación. Cuando el SDK abre la firma, AllSign consulta el equivalente desde su servidor y lo convierte en el encabezado Content-Security-Policy: frame-ancestors del iframe, que es lo que hace cumplir el navegador: tu página puede enmarcar la firma y cualquier otra no. Se muestra para que entiendas esa política y para depurar; localhost aparece para que pruebes en tu máquina. ### Qué mirar - `allowedOrigins` = `["https://app.ejemplo.com"]` - `frameAncestors` = `"https://app.ejemplo.com http://localhost:*"` ### En producción Si la llamas desde el navegador de tu página, falla en el preflight de CORS con un 400: la API v3 solo acepta peticiones de navegador desde orígenes de AllSign. **cURL** ``` curl 'https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/policy' \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/policy', { headers: { 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import requests respuesta = requests.get( "https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/policy", headers={ "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "sessionId": "ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a", "allowedOrigins": [ "https://app.ejemplo.com" ], "frameAncestors": "https://app.ejemplo.com http://localhost:*" } ``` ## 6 AllSign hace el equivalente por ti: inicia la sesión POST `/signing-sessions/{session_id}/init` sin API key [ver en la referencia](https://allsign.io/developers/docs/endpoints/signing-sessions#init-signing-session) ### Qué haces Tampoco la llama tu código. Cuando ejecutas `AllSign.init({ clientSecret })`, el iframe de AllSign inicia la sesión con una operación equivalente que revisa lo mismo que ves aquí: que el clientSecret sea válido y que la página que abre la firma (parentOrigin) esté en tus allowedOrigins. Se muestra para que entiendas qué se revisa y para depurar; en tu frontend basta con `AllSign.init({ clientSecret })` y luego `session.modal()`, `session.inline('#contenedor')` o `session.slider()`. ### Qué mirar - `signatureId` = `"sgr_fdcf573b43b24b2484b1a795ffff848f"` - `signer` = `{"email":"ana@ejemplo.com","name":"ana@ejemplo.com"}` - `document` = `{"id":"doc_e6d57f83692e4ff5ad07f57f6bd6a117","title":"Contrato de servicios — Ana Torres"}` ### En producción Igual en producción. Llamarla desde el navegador de tu página falla en el preflight de CORS con un 400: de iniciar la sesión se encarga el SDK. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/init' \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "clientSecret": "", "parentOrigin": "https://app.ejemplo.com" }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/init', { method: 'POST', headers: { 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ clientSecret: '', parentOrigin: 'https://app.ejemplo.com', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import requests respuesta = requests.post( "https://api.allsign.io/v3/signing-sessions/ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a/init", headers={ "AllSign-Version": "2026-07-11", }, json={ "clientSecret": "", "parentOrigin": "https://app.ejemplo.com", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "sessionId": "ses_b702eecc5b8d4dd0a6b58d5a5f2c8f5a", "signatureId": "sgr_fdcf573b43b24b2484b1a795ffff848f", "signerId": "usr_7e3a6c7366b54e50a5f49fbc1f0e8b9c", "guestToken": "", "signer": { "email": "ana@ejemplo.com", "name": "ana@ejemplo.com" }, "document": { "id": "doc_e6d57f83692e4ff5ad07f57f6bd6a117", "title": "Contrato de servicios — Ana Torres" }, "brandProfileId": null, "appearance": { "primaryColor": "#e54848", "primaryTextColor": "#000000", "borderRadius": 24, "fontFamily": null, "poweredByAllSign": true }, "locale": "es", "successUrl": "https://app.ejemplo.com/firmado", "cancelUrl": "https://app.ejemplo.com/cancelado" } ``` ## Errores comunes de esta receta Errores reales que el test de esta receta provocó a propósito en sandbox, sobre sus mismos pasos. Cada uno enlaza a su explicación en [Errores](https://allsign.io/developers/docs/errors). `RESOURCE_NOT_FOUND` · 404 · en el paso 4 (Abre una sesión de firma para ese firmante) Abre una sesión para alguien que no firma el documento. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#RESOURCE_NOT_FOUND) **Respuesta · 404 · RESOURCE_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#RESOURCE_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "No participant with email nadie@ejemplo.com on document c2af0193-88b8-4767-be86-e9bc40106542", "instance": "/v3/signing-sessions", "code": "RESOURCE_NOT_FOUND", "requestId": "req_243a82a23c1a45aba4757d8ed3758d31" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/firma-embebida-en-tu-app.receta.spec.ts` `PERMISSION_DENIED` · 403 · en el paso 6 (AllSign hace el equivalente por ti: inicia la sesión) Abre la firma en una página que no está en allowedOrigins. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#PERMISSION_DENIED) Así responde la API cuando la página que abre la firma no está en allowedOrigins; en tu página lo vas a ver de otra forma. Lo normal es que el navegador ni cargue el iframe y la consola muestre un error de Content-Security-Policy frame-ancestors. Si llega a cargar, la llamada con la que el iframe inicia la sesión responde 403 con el código ORIGIN\_NOT\_ALLOWED. En los dos casos, agrega ese origen exacto (esquema, dominio y puerto) a allowedOrigins al abrir la sesión. **Respuesta · 403 · PERMISSION_DENIED** ``` { "type": "https://allsign.io/developers/docs/errors#PERMISSION_DENIED", "title": "Permission denied", "status": 403, "detail": "The origin 'https://otro.ejemplo.com' is not authorized to embed this session.", "instance": "/v3/signing-sessions/ses_ee38d79b09ad45b9ad094034c1910ecc/init", "code": "PERMISSION_DENIED", "requestId": "req_beaface9931b45e4a4eb9e51c8b13f9c" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/firma-embebida-en-tu-app.receta.spec.ts` ## Siguiente - [Recibe y verifica los webhooks de AllSign](https://allsign.io/developers/docs/recetas/recibe-y-verifica-webhooks) — Un endpoint que recibe los eventos de AllSign, comprueba que de verdad los firmó AllSign y sobrevive a sus propias caídas y a la rotación del secreto. --- Fuente: https://allsign.io/developers/docs/recetas/formulario-pdf-acroform.md Receta # Prellena un formulario PDF y mándalo a firma **Objetivo:** Un formulario PDF con sus campos prellenados, el RFC bloqueado y enviado a la persona que lo llena y firma. Para: integrador · nivel intermedio · 10 min · Necesitas: API key de sandbox · Un PDF con campos de formulario (AcroForm): formulario.pdf ## Para agentes y asistentes de IA - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** → [Entornos](https://allsign.io/developers/docs/environments) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/formulario-pdf-acroform.receta.spec.ts` Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). ## 1 Da de alta el formulario como plantilla POST `/templates` [ver en la referencia](https://allsign.io/developers/docs/endpoints/templates#create-template) ### Qué haces AllSign importa cada campo del formulario (los widgets del PDF) con su nombre y su tipo. Ya trae un rol (roleCount: 1) porque el PDF tiene una caja de firma llamada firma\_cliente. readiness en pending quiere decir que hay campos que todavía no son de nadie. ### Qué mirar - `id` = `"tmpl_39701d80589b412dbd39e02b48679840"` - `fieldCount` = `5` - `roleCount` = `1` - `readiness` = `"pending"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/templates' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Solicitud de alta de cliente", "file": { "content": "'"$(base64 < formulario.pdf | tr -d '\n')"'", "fileType": "pdf", "name": "formulario.pdf" } }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/templates', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Solicitud de alta de cliente', file: { content: readFileSync('formulario.pdf').toString('base64'), fileType: 'pdf', name: 'formulario.pdf', }, }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/templates", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "Solicitud de alta de cliente", "file": { "content": base64.b64encode(Path("formulario.pdf").read_bytes()).decode(), "fileType": "pdf", "name": "formulario.pdf", }, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "tmpl_39701d80589b412dbd39e02b48679840", "object": "template", "name": "Solicitud de alta de cliente", "description": null, "fileType": "pdf", "originalFilename": "formulario.pdf", "aiEditable": true, "variableCount": 0, "fieldCount": 5, "unassignedFieldCount": 4, "roleCount": 1, "pendingCandidates": 0, "readiness": "pending", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 1, "previewUrl": "/templates/tmpl_39701d80589b412dbd39e02b48679840/preview", "downloadUrl": "/templates/tmpl_39701d80589b412dbd39e02b48679840/download", "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` ## 2 Revisa los campos que importó GET `/templates/{template_id}/fields` [ver en la referencia](https://allsign.io/developers/docs/endpoints/templates#list-template-fields) ### Qué haces La caja de firma ya es del rol Cliente, así que no tienes que declarar roles. Los otros cuatro campos (nombre, rfc, fecha y acepta) esperan a que digas quién los llena. ### Qué mirar - `unassignedCount` = `4` - `data.4.role` = `"Cliente"` **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840/fields' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840/fields', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840/fields", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "list", "templateId": "tmpl_39701d80589b412dbd39e02b48679840", "pageCount": 1, "data": [ { "object": "template_field", "name": "nombre", "type": "text", "role": null, "required": true, "label": "Nombre", "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 10.101, "width": 40.8497, "height": 3.0303 } } ] }, { "object": "template_field", "name": "rfc", "type": "text", "role": null, "required": true, "label": "Rfc", "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 15.1515, "width": 40.8497, "height": 3.0303 } } ] }, { "object": "template_field", "name": "fecha", "type": "date", "role": null, "required": true, "label": "Fecha", "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 20.202, "width": 40.8497, "height": 3.0303 } } ] }, { "object": "template_field", "name": "acepta", "type": "checkbox", "role": null, "required": false, "label": "Acepta", "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 25.2525, "width": 3.5948, "height": 2.7778 } } ] }, { "object": "template_field", "name": "firma_cliente", "type": "signature", "role": "Cliente", "required": true, "label": null, "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 39.1414, "width": 40.8497, "height": 7.5758 } } ] } ], "unassignedCount": 4, "hasMore": false } ``` ## 3 Asigna un campo al rol PATCH `/templates/{template_id}/fields/{name}` [ver en la referencia](https://allsign.io/developers/docs/endpoints/templates#update-template-field) ### Qué haces Repite esta llamada con rfc, fecha y acepta: cada campo dice qué rol lo llena. Un rol es un lugar en el formulario, no una persona; la persona se asigna al crear cada documento. ### Qué mirar - `name` = `"nombre"` - `role` = `"Cliente"` **cURL** ``` curl -X PATCH 'https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840/fields/nombre' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "role": "Cliente" }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840/fields/nombre', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ role: 'Cliente', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.patch( "https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840/fields/nombre", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "role": "Cliente", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "template_field", "name": "nombre", "type": "text", "role": "Cliente", "required": true, "label": "Nombre", "options": null, "group": null, "source": "acroform", "value": null, "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "areas": [ { "page": 1, "rect": { "x": 8.1699, "y": 10.101, "width": 40.8497, "height": 3.0303 } } ] } ``` ## 4 Confirma que la plantilla quedó lista GET `/templates/{template_id}` [ver en la referencia](https://allsign.io/developers/docs/endpoints/templates#retrieve-template) ### Qué haces Con todos los campos asignados, readiness pasa a ready. ### Qué mirar - `readiness` = `"ready"` **cURL** ``` curl 'https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/templates/tmpl_39701d80589b412dbd39e02b48679840", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "tmpl_39701d80589b412dbd39e02b48679840", "object": "template", "name": "Solicitud de alta de cliente", "description": null, "fileType": "pdf", "originalFilename": "formulario.pdf", "aiEditable": true, "variableCount": 0, "fieldCount": 5, "unassignedFieldCount": 0, "roleCount": 1, "pendingCandidates": 0, "readiness": "ready", "tags": [], "category": null, "usageCount": 0, "lastUsedAt": null, "currentVersion": 1, "previewUrl": "/templates/tmpl_39701d80589b412dbd39e02b48679840/preview", "downloadUrl": "/templates/tmpl_39701d80589b412dbd39e02b48679840/download", "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` ## 5 Crea el documento con valores y un campo bloqueado POST `/documents` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#create-document) ### Qué haces values prellena los campos por su nombre (texto o casilla) y readOnly bloquea los que el firmante no debe cambiar: aquí el RFC, que ya validaste de tu lado. Un nombre de campo que no existe se ignora sin error, así que revisa la ortografía contra la lista de campos. ### Qué mirar - `id` = `"doc_c8fee66bd4fb43ad9d928183b9ed7b38"` - `status` = `"draft"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "templateId": "tmpl_39701d80589b412dbd39e02b48679840", "name": "Alta de cliente — Ana Torres", "signers": [ { "roleName": "Cliente", "email": "ana@ejemplo.com", "name": "Ana Torres", "values": { "nombre": "Ana Torres", "rfc": "XAXX010101000", "acepta": true }, "readOnly": [ "rfc" ] } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ templateId: 'tmpl_39701d80589b412dbd39e02b48679840', name: 'Alta de cliente — Ana Torres', signers: [ { roleName: 'Cliente', email: 'ana@ejemplo.com', name: 'Ana Torres', values: { nombre: 'Ana Torres', rfc: 'XAXX010101000', acepta: true, }, readOnly: [ 'rfc', ], }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "templateId": "tmpl_39701d80589b412dbd39e02b48679840", "name": "Alta de cliente — Ana Torres", "signers": [ { "roleName": "Cliente", "email": "ana@ejemplo.com", "name": "Ana Torres", "values": { "nombre": "Ana Torres", "rfc": "XAXX010101000", "acepta": True, }, "readOnly": [ "rfc", ], }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "doc_c8fee66bd4fb43ad9d928183b9ed7b38", "object": "document", "name": "Alta de cliente — Ana Torres", "status": "draft", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_106b50e8748a4364b28ac86e4bf5fe4f", "orgId": "5a0700dc-3921-4b6f-ace7-6f404f037808", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": "tmpl_39701d80589b412dbd39e02b48679840", "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:03.973000Z", "updatedAt": "2026-07-11T18:00:03.973000Z" } ``` ## 6 Revisa cómo quedaron los campos GET `/documents/{document_id}/fields` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#list-document-fields) ### Qué haces filledBy en owner dice que el valor lo puso quien envía; la fecha queda vacía para que la llene el firmante. ### Qué mirar - `data.1.value` = `{"text":"XAXX010101000","checked":null}` - `data.1.readOnly` = `true` - `data.1.filledBy` = `"owner"` - `data.2.filledBy` = `null` **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_c8fee66bd4fb43ad9d928183b9ed7b38/fields' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c8fee66bd4fb43ad9d928183b9ed7b38/fields', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_c8fee66bd4fb43ad9d928183b9ed7b38/fields", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "list", "documentId": "doc_c8fee66bd4fb43ad9d928183b9ed7b38", "data": [ { "id": "fie_8f82e5aaf2314a5a8a672431087526c8", "object": "document_field", "name": "nombre", "type": "text", "role": "Cliente", "signerId": "sgr_3ecb998cf6cc48c6825fd665d73a2f07", "required": true, "label": "Nombre", "options": null, "group": null, "source": "template", "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 8.1699, "y": 10.101, "width": 40.8497, "height": 3.0303 }, "position": 1, "status": "pending", "value": { "text": "Ana Torres", "checked": null }, "filledBy": "owner", "filledAt": "2026-07-11T18:00:04.611000Z" }, { "id": "fie_7fcc5ad17fed4d1f90d1a5a209f5ce48", "object": "document_field", "name": "rfc", "type": "text", "role": "Cliente", "signerId": "sgr_3ecb998cf6cc48c6825fd665d73a2f07", "required": true, "label": "Rfc", "options": null, "group": null, "source": "template", "readOnly": true, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 8.1699, "y": 15.1515, "width": 40.8497, "height": 3.0303 }, "position": 1, "status": "pending", "value": { "text": "XAXX010101000", "checked": null }, "filledBy": "owner", "filledAt": "2026-07-11T18:00:04.611000Z" }, { "id": "fie_e8291f03304c4ec7a815c3d93d55234d", "object": "document_field", "name": "fecha", "type": "date", "role": "Cliente", "signerId": "sgr_3ecb998cf6cc48c6825fd665d73a2f07", "required": true, "label": "Fecha", "options": null, "group": null, "source": "template", "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 8.1699, "y": 20.202, "width": 40.8497, "height": 3.0303 }, "position": 1, "status": "pending", "value": { "text": null, "checked": null }, "filledBy": null, "filledAt": null }, { "id": "fie_73c88511f9e3402c851a89be02643c27", "object": "document_field", "name": "acepta", "type": "checkbox", "role": "Cliente", "signerId": "sgr_3ecb998cf6cc48c6825fd665d73a2f07", "required": false, "label": "Acepta", "options": null, "group": null, "source": "template", "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 8.1699, "y": 25.2525, "width": 3.5948, "height": 2.7778 }, "position": 1, "status": "pending", "value": { "text": null, "checked": true }, "filledBy": "owner", "filledAt": "2026-07-11T18:00:04.611000Z" }, { "id": "fie_7d1dd7756041414193bc9f8c999cf86b", "object": "document_field", "name": "firma_cliente", "type": "signature", "role": "Cliente", "signerId": "sgr_3ecb998cf6cc48c6825fd665d73a2f07", "required": true, "label": null, "options": null, "group": null, "source": "template", "readOnly": false, "fixedWidth": false, "placeholder": null, "maxLength": null, "page": 1, "rect": { "x": 8.1699, "y": 39.1414, "width": 40.8497, "height": 7.5758 }, "position": 1, "status": "pending", "value": { "text": null, "checked": null }, "filledBy": null, "filledAt": null } ], "unassignedCount": 0, "hasMore": false } ``` ## 7 Envíalo a firma POST `/documents/{document_id}/send` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#send-document) ### Qué haces La persona recibe su invitación, llena la fecha, ve el RFC sin poder cambiarlo y firma. ### Qué mirar - `status` = `"awaiting_signatures"` ### En producción Con una key live este paso consume créditos de tu saldo; en sandbox no se cobra nada. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_c8fee66bd4fb43ad9d928183b9ed7b38/send' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c8fee66bd4fb43ad9d928183b9ed7b38/send', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_c8fee66bd4fb43ad9d928183b9ed7b38/send", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={}, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_c8fee66bd4fb43ad9d928183b9ed7b38", "object": "document", "name": "Alta de cliente — Ana Torres", "status": "awaiting_signatures", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_106b50e8748a4364b28ac86e4bf5fe4f", "orgId": "5a0700dc-3921-4b6f-ace7-6f404f037808", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": "tmpl_39701d80589b412dbd39e02b48679840", "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:03.973000Z", "updatedAt": "2026-07-11T18:00:06.682000Z" } ``` ## Errores comunes de esta receta Errores reales que el test de esta receta provocó a propósito en sandbox, sobre sus mismos pasos. Cada uno enlaza a su explicación en [Errores](https://allsign.io/developers/docs/errors). `FIELD_NOT_FOUND` · 404 · en el paso 3 (Asigna un campo al rol) Asigna un campo que no existe. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#FIELD_NOT_FOUND) **Respuesta · 404 · FIELD_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#FIELD_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Field 'telefono_movil' not found in this template.", "instance": "/v3/templates/tmpl_24c55a589976432da1a168e3c4859ce6/fields/telefono_movil", "code": "FIELD_NOT_FOUND", "requestId": "req_06aa3c8f7ed843d0afa10acac48240e5" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/formulario-pdf-acroform.receta.spec.ts` `DOCUMENT_CONFLICT` · 409 · en `PATCH /v3/documents/doc_6b7fc763d2bc430980ae397679f7a465/fields/fie_fb20c7c439fc4902b6b6bbc9bbca55cc` Corrige un campo de un documento anulado. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#DOCUMENT_CONFLICT) **Respuesta · 409 · DOCUMENT_CONFLICT** ``` { "type": "https://allsign.io/developers/docs/errors#DOCUMENT_CONFLICT", "title": "Conflict", "status": 409, "detail": "Este documento ya terminó (ANULADO): sus campos ya no se pueden crear, mover, reasignar ni borrar.", "instance": "/v3/documents/doc_6b7fc763d2bc430980ae397679f7a465/fields/fie_fb20c7c439fc4902b6b6bbc9bbca55cc", "code": "DOCUMENT_CONFLICT", "requestId": "req_55b09c55654746338f3f811e0ced82a7", "reason": "document_terminal" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/formulario-pdf-acroform.receta.spec.ts` ## Siguiente - [Recibe y verifica los webhooks de AllSign](https://allsign.io/developers/docs/recetas/recibe-y-verifica-webhooks) — Un endpoint que recibe los eventos de AllSign, comprueba que de verdad los firmó AllSign y sobrevive a sus propias caídas y a la rotación del secreto. - [Crea un contrato desde tu plantilla de Word](https://allsign.io/developers/docs/recetas/documento-desde-plantilla-docx) — Un acuerdo de confidencialidad generado desde tu plantilla DOCX, con los datos de cada parte en su lugar y enviado a firma. --- Fuente: https://allsign.io/developers/docs/recetas/observadores-y-transferencia.md Receta # Avisa a alguien más y cambia al responsable de un documento **Objetivo:** Una persona de fuera se entera de cómo va un documento sin firmarlo, y otra persona de tu equipo queda como su responsable. Para: admin · nivel inicial · 5 min · Necesitas: API key de sandbox · Un segundo miembro en tu equipo ## Para agentes y asistentes de IA - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** → [Entornos](https://allsign.io/developers/docs/environments) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/observadores-y-transferencia.receta.spec.ts` Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). ## 1 Crea el documento POST `/documents` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#create-document) ### Qué haces ownerId es la persona de tu equipo responsable del documento: hoy eres tú, porque es tu key. ### Qué mirar - `id` = `"doc_f2344cc1b9b24ba2954715812b3d7242"` - `ownerId` = `"usr_550487c74d4a4061ba2af6b802dcacde"` ### En producción Los observadores y el traspaso de dueño se activan por cuenta y en producción pueden venir apagados. Mientras no estén activos en la tuya, esas llamadas responden 403 FEATURE\_NOT\_AVAILABLE y no cambian nada; pide que los activen. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Convenio de colaboración", "file": { "content": "'"$(base64 < contrato.pdf | tr -d '\n')"'", "fileType": "pdf", "name": "contrato.pdf" }, "signers": [ { "email": "ana@ejemplo.com", "name": "Ana Torres" } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/documents', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Convenio de colaboración', file: { content: readFileSync('contrato.pdf').toString('base64'), fileType: 'pdf', name: 'contrato.pdf', }, signers: [ { email: 'ana@ejemplo.com', name: 'Ana Torres', }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/documents", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "Convenio de colaboración", "file": { "content": base64.b64encode(Path("contrato.pdf").read_bytes()).decode(), "fileType": "pdf", "name": "contrato.pdf", }, "signers": [ { "email": "ana@ejemplo.com", "name": "Ana Torres", }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "doc_f2344cc1b9b24ba2954715812b3d7242", "object": "document", "name": "Convenio de colaboración", "status": "draft", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_550487c74d4a4061ba2af6b802dcacde", "orgId": "c313ca9b-d63c-4c74-a3ab-2d9f2f98a61b", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:00.000000Z", "updatedAt": "2026-07-11T18:00:00.000000Z" } ``` ## 2 Agrega a alguien que solo observa POST `/documents/{document_id}/observers` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#add-document-observer) ### Qué haces Un observador nunca firma ni recibe campos: solo los avisos que eliges en events. En cuanto lo agregas le llega un correo que se lo dice; con notify\_and\_view ese correo trae la liga para abrir el documento, y con notify (el predeterminado) solo recibe los avisos. ### Qué mirar - `observer.id` = `"rol_9f8c7b2441a646b5aacbd925b2d3b6d9"` - `observer.capability` = `"notify_and_view"` - `created` = `true` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Content-Type: application/json" \ -d '{ "email": "luis@ejemplo.com", "name": "Luis Ramírez", "capability": "notify_and_view", "events": [ "document.completed", "document.voided" ] }' ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Content-Type': 'application/json', }, body: JSON.stringify({ email: 'luis@ejemplo.com', name: 'Luis Ramírez', capability: 'notify_and_view', events: [ 'document.completed', 'document.voided', ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, json={ "email": "luis@ejemplo.com", "name": "Luis Ramírez", "capability": "notify_and_view", "events": [ "document.completed", "document.voided", ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "observer": { "object": "document_observer", "id": "rol_9f8c7b2441a646b5aacbd925b2d3b6d9", "origin": "document", "email": "luis@ejemplo.com", "name": "Luis Ramírez", "capability": "notify_and_view", "events": [ "document.completed", "document.voided" ], "locked": false, "status": "active", "createdAt": "2026-07-11T18:00:01.027000Z" }, "created": true } ``` ## 3 Revisa quién observa el documento GET `/documents/{document_id}/observers` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#list-document-observers) ### Qué haces Los observadores no aparecen entre los firmantes ni cuentan en signerCount. ### Qué mirar - `data` = `[{"object":"document_observer","id":"rol_9f8c7b2441a646b5aacbd925b2d3b6d9","origin":"document","email":"luis@ejemplo.com","name":"Luis Ramírez","capability":"notify_and_view","events":["document.completed","document.voided"],"locked":false,"status":"active","createdAt":"2026-07-11T18:00:01.027000Z"}]` **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "list", "documentId": "doc_f2344cc1b9b24ba2954715812b3d7242", "data": [ { "object": "document_observer", "id": "rol_9f8c7b2441a646b5aacbd925b2d3b6d9", "origin": "document", "email": "luis@ejemplo.com", "name": "Luis Ramírez", "capability": "notify_and_view", "events": [ "document.completed", "document.voided" ], "locked": false, "status": "active", "createdAt": "2026-07-11T18:00:01.027000Z" } ], "hasMore": false } ``` ## 4 Quita al observador DELETE `/documents/{document_id}/observers/{observer_id}` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#remove-document-observer) ### Qué haces Desde este momento ya no recibe avisos de este documento. ### Qué mirar - `deleted` = `true` **cURL** ``` curl -X DELETE 'https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers/rol_9f8c7b2441a646b5aacbd925b2d3b6d9' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers/rol_9f8c7b2441a646b5aacbd925b2d3b6d9', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.delete( "https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/observers/rol_9f8c7b2441a646b5aacbd925b2d3b6d9", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "object": "document_observer", "id": "rol_9f8c7b2441a646b5aacbd925b2d3b6d9", "deleted": true } ``` ## 5 Busca a quién pasarle el documento GET `/users/team` [ver en la referencia](https://allsign.io/developers/docs/endpoints/users#list-team-members) ### Qué haces El nuevo responsable tiene que ser miembro de tu equipo: toma su id de esta lista. ### Qué mirar - `data` = `[{"id":"usr_550487c74d4a4061ba2af6b802dcacde","name":"Sofía Herrera","email":"sofia@ejemplo.com","role":"owner"},{"id":"usr_b478f972e8574a739be5fb7364382c33","name":"Carlos Méndez","email":"carlos@ejemplo.com","role":"developer"}]` **cURL** ``` curl 'https://api.allsign.io/v3/users/team' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/users/team', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/users/team", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "id": "usr_550487c74d4a4061ba2af6b802dcacde", "name": "Sofía Herrera", "email": "sofia@ejemplo.com", "role": "owner" }, { "id": "usr_b478f972e8574a739be5fb7364382c33", "name": "Carlos Méndez", "email": "carlos@ejemplo.com", "role": "developer" } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": null } ``` ## 6 Traspasa el documento POST `/documents/{document_id}/transfer-owner` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#transfer-document-owner) ### Qué haces El documento, sus firmantes y su historial no cambian: solo quién es su responsable. reason queda en la bitácora del documento. ### Qué mirar - `ownerId` = `"usr_b478f972e8574a739be5fb7364382c33"` - `previousOwnerId` = `"usr_550487c74d4a4061ba2af6b802dcacde"` - `transferred` = `true` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/transfer-owner' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "ownerUserId": "usr_b478f972e8574a739be5fb7364382c33", "reason": "Cambio de responsable de la cuenta" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/transfer-owner', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ ownerUserId: 'usr_b478f972e8574a739be5fb7364382c33', reason: 'Cambio de responsable de la cuenta', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_f2344cc1b9b24ba2954715812b3d7242/transfer-owner", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "ownerUserId": "usr_b478f972e8574a739be5fb7364382c33", "reason": "Cambio de responsable de la cuenta", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "documentId": "doc_f2344cc1b9b24ba2954715812b3d7242", "ownerId": "usr_b478f972e8574a739be5fb7364382c33", "previousOwnerId": "usr_550487c74d4a4061ba2af6b802dcacde", "transferred": true, "documentIds": [ "doc_f2344cc1b9b24ba2954715812b3d7242" ] } ``` ## 7 Traspasa varios de una vez POST `/documents/transfer-owner` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#transfer-documents-owner) ### Qué haces Manda documentIds con la lista que quieres mover, o fromUserId para mover todos los documentos de una persona, por ejemplo cuando deja la empresa. keepPreviousOwnerAccess es true por defecto: el dueño anterior conserva el acceso; mándalo en false para quitárselo. ### Qué mirar - `transferredCount` = `1` - `items` = `[{"documentId":"doc_f2344cc1b9b24ba2954715812b3d7242","status":"transferred","error":null}]` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/transfer-owner' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "toUserId": "usr_550487c74d4a4061ba2af6b802dcacde", "documentIds": [ "doc_f2344cc1b9b24ba2954715812b3d7242" ], "reason": "Regresa a su responsable original" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/transfer-owner', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ toUserId: 'usr_550487c74d4a4061ba2af6b802dcacde', documentIds: [ 'doc_f2344cc1b9b24ba2954715812b3d7242', ], reason: 'Regresa a su responsable original', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/transfer-owner", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "toUserId": "usr_550487c74d4a4061ba2af6b802dcacde", "documentIds": [ "doc_f2344cc1b9b24ba2954715812b3d7242", ], "reason": "Regresa a su responsable original", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "batchId": "7dd2209a-5de4-4e64-ac28-c0d62da264b9", "totalCount": 1, "transferredCount": 1, "unchangedCount": 0, "errorCount": 0, "items": [ { "documentId": "doc_f2344cc1b9b24ba2954715812b3d7242", "status": "transferred", "error": null } ] } ``` ## Errores comunes de esta receta Errores reales que el test de esta receta provocó a propósito en sandbox, sobre sus mismos pasos. Cada uno enlaza a su explicación en [Errores](https://allsign.io/developers/docs/errors). `OBSERVER_IS_SIGNER` · 409 · en el paso 2 (Agrega a alguien que solo observa) Agrega como observador a quien ya firma. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#OBSERVER_IS_SIGNER) **Respuesta · 409 · OBSERVER_IS_SIGNER** ``` { "type": "https://allsign.io/developers/docs/errors#OBSERVER_IS_SIGNER", "title": "Conflict", "status": 409, "detail": "ana@ejemplo.com already signs this document; a signer cannot also be an observer.", "instance": "/v3/documents/doc_5dbd1cc097404878a5668c6a00774830/observers", "code": "OBSERVER_IS_SIGNER", "requestId": "req_68de996d2d6442b981652319043cc610" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/observadores-y-transferencia.receta.spec.ts` `OBSERVER_NOT_FOUND` · 404 · en el paso 4 (Quita al observador) Quita un observador que no existe. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#OBSERVER_NOT_FOUND) **Respuesta · 404 · OBSERVER_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#OBSERVER_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "Observer not found in this document. Organization observers are removed from Settings → Observers.", "instance": "/v3/documents/doc_5dbd1cc097404878a5668c6a00774830/observers/rol_245aa9e7229547b5b3e5cf30afe54094", "code": "OBSERVER_NOT_FOUND", "requestId": "req_22602288f4704ad2857ebd2b3dfa1468" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/observadores-y-transferencia.receta.spec.ts` `VALIDATION_ERROR` · 422 · en el paso 6 (Traspasa el documento) Traspasa a alguien fuera de tu equipo. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#VALIDATION_ERROR) **Respuesta · 422 · VALIDATION_ERROR** ``` { "type": "https://allsign.io/developers/docs/errors#VALIDATION_ERROR", "title": "Validation failed", "status": 422, "detail": "The new owner must be a member of this workspace.", "instance": "/v3/documents/doc_5dbd1cc097404878a5668c6a00774830/transfer-owner", "code": "VALIDATION_ERROR", "requestId": "req_156c01d6c0fd40f982ae571ecb18cce4" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:18 (hora CDMX) · test `docs-checks-v3/tests/recetas/observadores-y-transferencia.receta.spec.ts` ## Siguiente - [Recibe y verifica los webhooks de AllSign](https://allsign.io/developers/docs/recetas/recibe-y-verifica-webhooks) — Un endpoint que recibe los eventos de AllSign, comprueba que de verdad los firmó AllSign y sobrevive a sus propias caídas y a la rotación del secreto. --- Fuente: https://allsign.io/developers/docs/recetas/recibe-y-verifica-webhooks.md Receta # Recibe y verifica los webhooks de AllSign **Objetivo:** Un endpoint que recibe los eventos de AllSign, comprueba que de verdad los firmó AllSign y sobrevive a sus propias caídas y a la rotación del secreto. Para: backend · nivel intermedio · 15 min · Necesitas: API key de sandbox · Un endpoint HTTPS público en tu servidor ## Para agentes y asistentes de IA - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** → [Entornos](https://allsign.io/developers/docs/environments) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:20 (hora CDMX) · test `docs-checks-v3/tests/recetas/recibe-y-verifica-webhooks.receta.spec.ts` Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). ## 1 Consulta qué eventos puedes recibir GET `/webhooks/events` [ver en la referencia](https://allsign.io/developers/docs/webhooks#list-events) ### Qué haces Suscribe tu endpoint solo a los eventos que vas a procesar: cada uno es una llamada a tu servidor. ### Qué mirar - `data.0.event` = `"document.created"` - `data.0.description` = `"A document was created via the API."` **cURL** ``` curl 'https://api.allsign.io/v3/webhooks/events' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/webhooks/events', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/webhooks/events", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "event": "document.created", "description": "A document was created via the API.", "category": "Documents", "apiVersion": "2026-07-11", "status": "active" }, { "event": "document.sent", "description": "A document left creation and entered the signing cycle (first invitations dispatched).", "category": "Documents", "apiVersion": "2026-07-11", "status": "active" }, { "event": "document.completed", "description": "All parties signed and the evidence PDF is ready (delivered by stable API URL, not inline).", "category": "Documents", "apiVersion": "2026-07-11", "status": "active" }, { "event": "document.voided", "description": "A document was voided (annulled). NOM-151 retention keeps the record.", "category": "Documents", "apiVersion": "2026-07-11", "status": "active" }, { "event": "document.expired", "description": "A document reached its expiry date without being completed. Carries who did manage to sign.", "category": "Documents", "apiVersion": "2026-07-11", "status": "active" }, { "event": "document.owner_transferred", "description": "A document changed owner inside its workspace (manual transfer, bulk transfer or member offboarding). Carries the previous and the new owner.", "category": "Documents", "apiVersion": "2026-07-11", "status": "active" }, { "event": "document.fill_started", "description": "DEPRECATED — variables are filled by the sender only, so a new document no longer waits for signers to fill data. Only the legacy fill flow still emits it, and it will stop; it stays in the catalog so existing subscriptions remain valid.", "category": "Documents", "apiVersion": "2026-07-11", "status": "active" }, { "event": "document.ready_to_sign", "description": "The PDF was materialized with the filled data and is ready to sign.", "category": "Documents", "apiVersion": "2026-07-11", "status": "active" }, { "event": "signer.turn_started", "description": "Sequential signing: a later stage opened because the previous one closed, and the signer's invitation was dispatched. Not emitted for the first stage on send — use document.sent for that.", "category": "Signers", "apiVersion": "2026-07-11", "status": "active" }, { "event": "document.stage_advanced", "description": "Sequential signing: every signer of a stage signed and the next stage was invited.", "category": "Documents", "apiVersion": "2026-07-11", "status": "active" }, { "event": "signer.signed", "description": "One signer completed their signature. Carries the running progress.", "category": "Signers", "apiVersion": "2026-07-11", "status": "active" }, { "event": "signer.fill_completed", "description": "DEPRECATED — signers no longer fill variables; what a signer fills goes in PDF form fields. Only the legacy fill flow still emits it, and it will stop; it stays in the catalog so existing subscriptions remain valid.", "category": "Signers", "apiVersion": "2026-07-11", "status": "active" }, { "event": "signer.reminder_sent", "description": "A signing reminder was sent to a signer (email or WhatsApp).", "category": "Signers", "apiVersion": "2026-07-11", "status": "active" }, { "event": "signer.delivery_failed", "description": "A signing invitation could not be delivered to a signer (WhatsApp failure or email bounce).", "category": "Signers", "apiVersion": "2026-07-11", "status": "active" }, { "event": "signer.declined", "description": "A signer declined to sign. RESERVED — the contract is frozen; it does not fire yet.", "category": "Signers", "apiVersion": "2026-07-11", "status": "reserved" }, { "event": "document.observer_added", "description": "An observer was added to a document (or its capability/events changed). Observers never sign.", "category": "Documents", "apiVersion": "2026-07-11", "status": "active" }, { "event": "document.observer_removed", "description": "An observer was removed from a document; its read-only links were revoked.", "category": "Documents", "apiVersion": "2026-07-11", "status": "active" }, { "event": "nom151.constancia.issued", "description": "The NOM-151 conservation constancia was issued for a completed document.", "category": "Compliance", "apiVersion": "2026-07-11", "status": "active" } ] } ``` ## 2 Registra tu endpoint POST `/webhooks` [ver en la referencia](https://allsign.io/developers/docs/webhooks#create-endpoint) ### Qué haces La respuesta trae el secreto whsec\_… una sola vez: guárdalo como secreto de tu servidor, nunca en el frontend. Con él vas a comprobar que cada entrega viene de AllSign. ### Qué mirar - `id` = `"whe_27226709c9124d798a44032672fe2c9a"` - `secret` = `"whsec_…"` - `status` = `"enabled"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/webhooks' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "url": "https://ejemplo.com/webhooks/allsign", "events": [ "document.created", "document.completed" ], "description": "Avisos de firma" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/webhooks', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://ejemplo.com/webhooks/allsign', events: [ 'document.created', 'document.completed', ], description: 'Avisos de firma', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/webhooks", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "url": "https://ejemplo.com/webhooks/allsign", "events": [ "document.created", "document.completed", ], "description": "Avisos de firma", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "whe_27226709c9124d798a44032672fe2c9a", "object": "webhook_endpoint", "url": "https://ejemplo.com/webhooks/allsign", "events": [ "document.created", "document.completed" ], "description": "Avisos de firma", "status": "enabled", "apiVersion": "2026-07-11", "environment": "test", "secretLast4": "1NM=", "createdAt": "2026-07-11T18:00:00.000000Z", "secret": "whsec_…" } ``` ## 3 Crea un documento para que haya un evento POST `/documents` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#create-document) ### Qué haces Cualquier documento sirve: al crearlo, AllSign le avisa a tu endpoint con document.created. ### Qué mirar en sandbox - `id` = `"doc_8a9708721fb54fcfabc32715b87f5518"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Contrato para probar webhooks", "file": { "content": "'"$(base64 < contrato.pdf | tr -d '\n')"'", "fileType": "pdf", "name": "contrato.pdf" }, "signers": [ { "email": "signer-success@sandbox.allsign.io", "name": "Ana Torres" } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/documents', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Contrato para probar webhooks', file: { content: readFileSync('contrato.pdf').toString('base64'), fileType: 'pdf', name: 'contrato.pdf', }, signers: [ { email: 'signer-success@sandbox.allsign.io', name: 'Ana Torres', }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/documents", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "Contrato para probar webhooks", "file": { "content": base64.b64encode(Path("contrato.pdf").read_bytes()).decode(), "fileType": "pdf", "name": "contrato.pdf", }, "signers": [ { "email": "signer-success@sandbox.allsign.io", "name": "Ana Torres", }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "doc_8a9708721fb54fcfabc32715b87f5518", "object": "document", "name": "Contrato para probar webhooks", "status": "draft", "documentType": "EDITABLE", "signerCount": 1, "signedCount": 0, "ownerId": "usr_f5fea48ae9ad4c5aafdc56145882d508", "orgId": "9424f74e-a378-4b13-9aff-7f05edbb5946", "folderId": null, "expiresAt": null, "signingOrder": "parallel", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:03.135000Z", "updatedAt": "2026-07-11T18:00:03.135000Z" } ``` ## 4 Recibe la entrega y verifica su firma POST `tu endpoint` · evento `document.created` ### Qué haces Antes de creerle al cuerpo, verifica la firma: webhook-signature trae v1, de `webhook-id.webhook-timestamp.cuerpo crudo`, con tu secreto sin el prefijo whsec\_ y decodificado de base64. Rechaza timestamps de más de 5 minutos y usa webhook-id para descartar duplicados. Responde 2xx en cuanto la guardes y procésala después. AllSign le hace `POST` a la URL que registraste, con el evento `document.created`. Responde `2xx` rápido y, antes de confiar en el cuerpo, verifica la firma: `webhook-signature` es un HMAC-SHA256 de `webhook-id.webhook-timestamp.cuerpo` con el secreto del webhook ([Webhooks](https://allsign.io/developers/docs/webhooks)). Esta entrega llegó de verdad al receptor del test y su firma se verificó con el secreto del webhook. ### Qué mirar - `eventType` = `"document.created"` - `data.documentId` = `"doc_8a9708721fb54fcfabc32715b87f5518"` ### En producción Igual con una key live. La cabecera allsign-livemode y el campo livemode te dicen si el evento es de producción o de sandbox. **Entrega recibida · Cuerpo** ``` { "data": { "name": "Contrato para probar webhooks", "status": "draft", "signers": [ { "name": "Ana Torres", "email": "signer-success@sandbox.allsign.io", "phone": null, "status": "pending", "signerId": "sgr_9d3c8ed3d60b440d9899437955abceb0" } ], "createdAt": "2026-07-11T18:00:04.005000Z", "documentId": "doc_8a9708721fb54fcfabc32715b87f5518", "createdViaApi": true }, "eventId": "evt_0766cee1be874732a7b5b4a28bf9c025", "livemode": false, "tenantId": "058291f5-cba8-4243-a3db-cb2af4fb0058", "eventType": "document.created", "apiVersion": "2026-07-11", "occurredAt": "2026-07-11T18:00:04.007Z" } ``` **Entrega recibida · Cabeceras** ``` { "content-type": "application/json", "allsign-event": "document.created", "allsign-livemode": "false", "webhook-id": "0766cee1-be87-4732-a7b5-b4a28bf9c025", "webhook-timestamp": "1783792804", "webhook-signature": "v1,", "allsign-delivery-id": "63de1310-00af-4375-8af9-d3de235e6f98" } ``` **Verifica la firma · Node** ``` import { createHmac, timingSafeEqual } from 'node:crypto'; export function verificarWebhook(cuerpoCrudo, headers, secreto) { const id = headers['webhook-id']; const timestamp = headers['webhook-timestamp']; if (!id || !/^\d+$/.test(timestamp ?? '') || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const llave = Buffer.from(secreto.replace(/^whsec_/, ''), 'base64'); const esperada = Buffer.from(createHmac('sha256', llave).update(`${id}.${timestamp}.${cuerpoCrudo}`).digest('base64')); return (headers['webhook-signature'] ?? '').split(' ').some((token) => { const [version, firma = ''] = token.split(','); const candidata = Buffer.from(firma); return version === 'v1' && candidata.length === esperada.length && timingSafeEqual(candidata, esperada); }); } ``` **Verifica la firma · Python** ``` import base64 import hashlib import hmac import time def verificar_webhook(cuerpo_crudo: bytes, headers: dict, secreto: str) -> bool: id_ = headers.get("webhook-id") timestamp = headers.get("webhook-timestamp", "") if not id_ or not (timestamp.isascii() and timestamp.isdigit()) or abs(time.time() - int(timestamp)) > 300: return False llave = base64.b64decode(secreto.removeprefix("whsec_")) contenido = f"{id_}.{timestamp}.".encode() + cuerpo_crudo esperada = base64.b64encode(hmac.new(llave, contenido, hashlib.sha256).digest()) return any( version == "v1" and hmac.compare_digest(firma.encode(), esperada) for version, _, firma in (token.partition(",") for token in headers.get("webhook-signature", "").split()) ) ``` ## 5 Registra un endpoint que falla POST `/webhooks` [ver en la referencia](https://allsign.io/developers/docs/webhooks#create-endpoint) ### Qué haces Para ver qué pasa cuando tu servidor falla, este segundo endpoint de pruebas responde 500 a propósito. ### Qué mirar - `id` = `"whe_67554d50996d47b9b68963f6cb2fffac"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/webhooks' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "url": "https://ejemplo.com/webhooks/allsign?status=500", "events": [ "document.created" ], "description": "Endpoint caído" }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/webhooks', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://ejemplo.com/webhooks/allsign?status=500', events: [ 'document.created', ], description: 'Endpoint caído', }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/webhooks", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "url": "https://ejemplo.com/webhooks/allsign?status=500", "events": [ "document.created", ], "description": "Endpoint caído", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "whe_67554d50996d47b9b68963f6cb2fffac", "object": "webhook_endpoint", "url": "https://ejemplo.com/webhooks/allsign?status=500", "events": [ "document.created" ], "description": "Endpoint caído", "status": "enabled", "apiVersion": "2026-07-11", "environment": "test", "secretLast4": "lbs=", "createdAt": "2026-07-11T18:00:04.771000Z", "secret": "whsec_…" } ``` ## 6 Revisa las entregas que fallaron GET `/webhooks/{webhook_id}/deliveries` [ver en la referencia](https://allsign.io/developers/docs/webhooks#list-deliveries) ### Qué haces Cada entrega guarda sus intentos en attemptHistory, con el status que respondió tu servidor. Un 5xx, un timeout o un error de red se reintentan con espera creciente durante 3 días, y mientras tanto la entrega sigue en PENDING; un 4xx la deja en FAILED de inmediato, porque AllSign entiende que tu servidor la rechazó a propósito. Tu endpoint recibe los eventos de toda tu cuenta, no solo del documento que acabas de crear, así que esta lista también puede traer entregas de otros documentos. Es lo primero que revisas cuando «no llegan los webhooks». ### Qué mirar - `data.0.status` = `"PENDING"` - `data.0.attempts` = `1` - `data.0.attemptHistory.0.statusCode` = `500` - `data.0.attemptHistory.0.outcome` = `"TRANSIENT"` **cURL** ``` curl 'https://api.allsign.io/v3/webhooks/whe_67554d50996d47b9b68963f6cb2fffac/deliveries?limit=5' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/webhooks/whe_67554d50996d47b9b68963f6cb2fffac/deliveries?limit=5', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/webhooks/whe_67554d50996d47b9b68963f6cb2fffac/deliveries?limit=5", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "whd_174b06165c7f4b728a112b5886fb4aa5", "object": "webhook_delivery", "eventId": "evt_f1033b81d73e49f3bc14b9a091a7d359", "eventType": "document.created", "status": "PENDING", "targetUrl": "https://ejemplo.com/webhooks/allsign?status=500", "attempts": 1, "lastStatusCode": null, "lastError": null, "createdAt": "2026-07-11T18:00:06.960000Z", "sentAt": null, "attemptHistory": [ { "attemptNumber": 1, "outcome": "TRANSIENT", "statusCode": 500, "error": "Receiver returned 500", "durationMs": 144, "attemptedAt": "2026-07-11T18:00:07.250000Z" } ] }, { "livemode": false, "id": "whd_46a2ada9f93e401995a8a5cda575d84a", "object": "webhook_delivery", "eventId": "evt_c4f89267d84c43bc9b983a73eb9bdb05", "eventType": "document.created", "status": "PENDING", "targetUrl": "https://ejemplo.com/webhooks/allsign?status=500", "attempts": 1, "lastStatusCode": null, "lastError": null, "createdAt": "2026-07-11T18:00:06.741000Z", "sentAt": null, "attemptHistory": [ { "attemptNumber": 1, "outcome": "TRANSIENT", "statusCode": 500, "error": "Receiver returned 500", "durationMs": 110, "attemptedAt": "2026-07-11T18:00:07.061000Z" } ] } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": 5 } ``` ## 7 Rota el secreto POST `/webhooks/{webhook_id}/rotate-secret` [ver en la referencia](https://allsign.io/developers/docs/webhooks#rotate-secret) ### Qué haces Rota en cuanto sospeches que el secreto se filtró. La respuesta trae el secreto nuevo una sola vez; el anterior sigue valiendo 24 horas para que despliegues el cambio sin perder entregas. ### Qué mirar - `secret` = `"whsec_…"` **cURL** ``` LLAVE=$(uuidgen) curl -X POST 'https://api.allsign.io/v3/webhooks/whe_27226709c9124d798a44032672fe2c9a/rotate-secret' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $LLAVE" ``` **Node** ``` import { randomUUID } from 'node:crypto'; const llave = randomUUID(); const respuesta = await fetch('https://api.allsign.io/v3/webhooks/whe_27226709c9124d798a44032672fe2c9a/rotate-secret', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': llave, }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests llave = str(uuid.uuid4()) respuesta = requests.post( "https://api.allsign.io/v3/webhooks/whe_27226709c9124d798a44032672fe2c9a/rotate-secret", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": llave, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "whe_27226709c9124d798a44032672fe2c9a", "object": "webhook_endpoint", "url": "https://ejemplo.com/webhooks/allsign", "events": [ "document.created", "document.completed" ], "description": "Avisos de firma", "status": "enabled", "apiVersion": "2026-07-11", "environment": "test", "secretLast4": "lVs=", "createdAt": "2026-07-11T18:00:00.000000Z", "secret": "whsec_…" } ``` ## 8 Reintenta la rotación con la misma Idempotency-Key POST `/webhooks/{webhook_id}/rotate-secret` [ver en la referencia](https://allsign.io/developers/docs/webhooks#rotate-secret) Reúsa la `Idempotency-Key` del paso 7: córrelo justo después de ese paso, en la misma terminal o el mismo script. ### Qué haces Si se cae la red y reintentas con la misma llave, no rota dos veces: recibes el mismo secreto y la cabecera Idempotency-Replayed: true. ### Qué mirar - `secret` = `"whsec_…"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/webhooks/whe_27226709c9124d798a44032672fe2c9a/rotate-secret' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $LLAVE" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/webhooks/whe_27226709c9124d798a44032672fe2c9a/rotate-secret', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': llave, }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.post( "https://api.allsign.io/v3/webhooks/whe_27226709c9124d798a44032672fe2c9a/rotate-secret", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": llave, }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "id": "whe_27226709c9124d798a44032672fe2c9a", "url": "https://ejemplo.com/webhooks/allsign", "events": [ "document.created", "document.completed" ], "object": "webhook_endpoint", "secret": "whsec_…", "status": "enabled", "livemode": false, "createdAt": "2026-07-11T18:00:00.000000Z", "apiVersion": "2026-07-11", "description": "Avisos de firma", "environment": "test", "secretLast4": "lVs=" } ``` ## 9 Verifica una entrega con el secreto nuevo POST `tu endpoint` · evento `document.created` ### Qué haces Durante esas 24 horas webhook-signature trae dos firmas separadas por espacio, una por secreto. Acepta la entrega si cualquiera cuadra con el secreto que tienes. AllSign le hace `POST` a la URL que registraste, con el evento `document.created`. Responde `2xx` rápido y, antes de confiar en el cuerpo, verifica la firma: `webhook-signature` es un HMAC-SHA256 de `webhook-id.webhook-timestamp.cuerpo` con el secreto del webhook ([Webhooks](https://allsign.io/developers/docs/webhooks)). Esta entrega llegó de verdad al receptor del test y su firma se verificó con el secreto del webhook. ### Qué mirar - `data.documentId` = `"doc_47e66108a3c64e1385ff314c388b7d6b"` **Entrega recibida · Cuerpo** ``` { "data": { "name": "Contrato después de rotar el secreto", "status": "draft", "signers": [ { "name": "Ana Torres", "email": "signer-success@sandbox.allsign.io", "phone": null, "status": "pending", "signerId": "sgr_6b457b7c97e34880908b9057b37c0f39" } ], "createdAt": "2026-07-11T18:00:09.212000Z", "documentId": "doc_47e66108a3c64e1385ff314c388b7d6b", "createdViaApi": true }, "eventId": "evt_57fca20414f34acb829dfc3c6d46eed8", "livemode": false, "tenantId": "058291f5-cba8-4243-a3db-cb2af4fb0058", "eventType": "document.created", "apiVersion": "2026-07-11", "occurredAt": "2026-07-11T18:00:09.214Z" } ``` **Entrega recibida · Cabeceras** ``` { "content-type": "application/json", "allsign-event": "document.created", "allsign-livemode": "false", "webhook-id": "57fca204-14f3-4acb-829d-fc3c6d46eed8", "webhook-timestamp": "1783792809", "webhook-signature": "v1, v1,", "allsign-delivery-id": "cc6efb79-3777-457a-ae95-77d2b3d8967f" } ``` **Verifica la firma · Node** ``` import { createHmac, timingSafeEqual } from 'node:crypto'; export function verificarWebhook(cuerpoCrudo, headers, secreto) { const id = headers['webhook-id']; const timestamp = headers['webhook-timestamp']; if (!id || !/^\d+$/.test(timestamp ?? '') || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const llave = Buffer.from(secreto.replace(/^whsec_/, ''), 'base64'); const esperada = Buffer.from(createHmac('sha256', llave).update(`${id}.${timestamp}.${cuerpoCrudo}`).digest('base64')); return (headers['webhook-signature'] ?? '').split(' ').some((token) => { const [version, firma = ''] = token.split(','); const candidata = Buffer.from(firma); return version === 'v1' && candidata.length === esperada.length && timingSafeEqual(candidata, esperada); }); } ``` **Verifica la firma · Python** ``` import base64 import hashlib import hmac import time def verificar_webhook(cuerpo_crudo: bytes, headers: dict, secreto: str) -> bool: id_ = headers.get("webhook-id") timestamp = headers.get("webhook-timestamp", "") if not id_ or not (timestamp.isascii() and timestamp.isdigit()) or abs(time.time() - int(timestamp)) > 300: return False llave = base64.b64decode(secreto.removeprefix("whsec_")) contenido = f"{id_}.{timestamp}.".encode() + cuerpo_crudo esperada = base64.b64encode(hmac.new(llave, contenido, hashlib.sha256).digest()) return any( version == "v1" and hmac.compare_digest(firma.encode(), esperada) for version, _, firma in (token.partition(",") for token in headers.get("webhook-signature", "").split()) ) ``` ## 10 Borra el endpoint cuando ya no lo uses DELETE `/webhooks/{webhook_id}` [ver en la referencia](https://allsign.io/developers/docs/webhooks#delete-endpoint) ### Qué haces Borrarlo detiene las entregas de inmediato; si solo quieres pausarlo, usa PATCH con disabled: true. **cURL** ``` curl -X DELETE 'https://api.allsign.io/v3/webhooks/whe_27226709c9124d798a44032672fe2c9a' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/webhooks/whe_27226709c9124d798a44032672fe2c9a', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status); ``` **Python** ``` import os import requests respuesta = requests.delete( "https://api.allsign.io/v3/webhooks/whe_27226709c9124d798a44032672fe2c9a", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code) ``` **Respuesta · 204** *Sin cuerpo.* ## Errores comunes de esta receta Errores reales que el test de esta receta provocó a propósito en sandbox, sobre sus mismos pasos. Cada uno enlaza a su explicación en [Errores](https://allsign.io/developers/docs/errors). `WEBHOOK_URL_INVALID` · 422 · en el paso 2 (Registra tu endpoint) Registra un endpoint con http:// en vez de https://. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#WEBHOOK_URL_INVALID) **Respuesta · 422 · WEBHOOK_URL_INVALID** ``` { "type": "https://allsign.io/developers/docs/errors#WEBHOOK_URL_INVALID", "title": "Unprocessable Entity", "status": 422, "detail": "The webhook url must be an absolute https:// URL.", "instance": "/v3/webhooks", "code": "WEBHOOK_URL_INVALID", "requestId": "req_b2c680669f144fcc806cb8366bae5594", "errors": [ { "field": "url", "code": "INVALID_VALUE", "detail": "Must start with https://" } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:19 (hora CDMX) · test `docs-checks-v3/tests/recetas/recibe-y-verifica-webhooks.receta.spec.ts` `UNKNOWN_EVENT_TYPE` · 422 · en el paso 2 (Registra tu endpoint) Suscríbete a un evento que no existe. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#UNKNOWN_EVENT_TYPE) **Respuesta · 422 · UNKNOWN_EVENT_TYPE** ``` { "type": "https://allsign.io/developers/docs/errors#UNKNOWN_EVENT_TYPE", "title": "Unprocessable Entity", "status": 422, "detail": "One or more event types are not in the v3 catalog.", "instance": "/v3/webhooks", "code": "UNKNOWN_EVENT_TYPE", "requestId": "req_45571c3852cb4ac6bae34f64dc886a28", "errors": [ { "field": "events[0]", "code": "UNKNOWN_EVENT_TYPE", "detail": "'documento.firmado' is not a valid v3 event type." } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:19 (hora CDMX) · test `docs-checks-v3/tests/recetas/recibe-y-verifica-webhooks.receta.spec.ts` `WEBHOOK_NOT_FOUND` · 404 · en `GET /v3/webhooks/whe_54c1d95de8c64a369095bc044665b5b3` Consulta un endpoint que ya borraste. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#WEBHOOK_NOT_FOUND) **Respuesta · 404 · WEBHOOK_NOT_FOUND** ``` { "type": "https://allsign.io/developers/docs/errors#WEBHOOK_NOT_FOUND", "title": "Not Found", "status": 404, "detail": "No webhook endpoint was found with that id.", "instance": "/v3/webhooks/whe_54c1d95de8c64a369095bc044665b5b3", "code": "WEBHOOK_NOT_FOUND", "requestId": "req_e424fdd005fc4298b0231312f2126597" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:19 (hora CDMX) · test `docs-checks-v3/tests/recetas/recibe-y-verifica-webhooks.receta.spec.ts` ## Siguiente - [Lleva tu primer documento a firma](https://allsign.io/developers/docs/recetas/primer-documento-a-firma) — Un documento firmado de punta a punta en sandbox, con el aviso de cada firma y el de cierre llegando a tu servidor. - [Descarga el PDF firmado y su evidencia](https://allsign.io/developers/docs/recetas/descarga-el-pdf-firmado-y-la-evidencia) — El PDF firmado y el PDF de evidencia de un documento completado, con su huella sha256 para archivarlos. --- Fuente: https://allsign.io/developers/docs/recetas/varios-firmantes-en-orden.md Receta # Varios firmantes, uno después de otro **Objetivo:** Un contrato que firma primero la vendedora y después el comprador, con el comprador sin poder firmar antes de su turno. Para: integrador · nivel intermedio · 15 min · Necesitas: API key de sandbox · contrato.pdf · Un endpoint HTTPS para webhooks ## Para agentes y asistentes de IA - **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks) - **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency) - **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** → [Entornos](https://allsign.io/developers/docs/environments) - **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors) - **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits) Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:20 (hora CDMX) · test `docs-checks-v3/tests/recetas/varios-firmantes-en-orden.receta.spec.ts` Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`). ## 1 Registra tu endpoint de webhooks POST `/webhooks` [ver en la referencia](https://allsign.io/developers/docs/webhooks#create-endpoint) ### Qué haces Con dos firmantes, cada firma te llega como signer.signed y el cierre como document.completed. ### Qué mirar - `id` = `"whe_f3ae04f2292e4bb78fc753c4229ba55e"` - `secret` = `"whsec_…"` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/webhooks' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "url": "https://ejemplo.com/webhooks/allsign", "events": [ "signer.signed", "document.completed" ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/webhooks', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://ejemplo.com/webhooks/allsign', events: [ 'signer.signed', 'document.completed', ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/webhooks", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "url": "https://ejemplo.com/webhooks/allsign", "events": [ "signer.signed", "document.completed", ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "whe_f3ae04f2292e4bb78fc753c4229ba55e", "object": "webhook_endpoint", "url": "https://ejemplo.com/webhooks/allsign", "events": [ "signer.signed", "document.completed" ], "description": null, "status": "enabled", "apiVersion": "2026-07-11", "environment": "test", "secretLast4": "TvQ=", "createdAt": "2026-07-11T18:00:00.000000Z", "secret": "whsec_…" } ``` ## 2 Crea el documento con el orden de firma POST `/documents` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#create-document) ### Qué haces signingOrder: sequential exige routingOrder en cada firmante: la etapa 1 firma primero y la 2 no recibe nada hasta que la 1 termina. Firmantes con el mismo número firman a la vez. Cualquier correo de @sandbox.allsign.io que no sea signer-success@ ni signer-declined@ se queda pendiente y no recibe correo; aquí usamos dos. ### Qué mirar en sandbox - `id` = `"doc_c0d4a131b744480689aa24b2f39215cf"` - `signingOrder` = `"sequential"` - `signerCount` = `2` **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Contrato de compraventa — firma en orden", "file": { "content": "'"$(base64 < contrato.pdf | tr -d '\n')"'", "fileType": "pdf", "name": "contrato.pdf" }, "signingOrder": "sequential", "signers": [ { "email": "signer-pending+1@sandbox.allsign.io", "name": "Ana Torres", "roleName": "Vendedora", "routingOrder": 1 }, { "email": "signer-pending+2@sandbox.allsign.io", "name": "Luis Ramírez", "roleName": "Comprador", "routingOrder": 2 } ], "fields": [ { "email": "signer-pending+1@sandbox.allsign.io", "pageNumber": 1, "position": { "x": 80, "y": 620 } }, { "email": "signer-pending+2@sandbox.allsign.io", "pageNumber": 1, "position": { "x": 80, "y": 520 } } ] }' ``` **Node** ``` import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; const respuesta = await fetch('https://api.allsign.io/v3/documents', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Contrato de compraventa — firma en orden', file: { content: readFileSync('contrato.pdf').toString('base64'), fileType: 'pdf', name: 'contrato.pdf', }, signingOrder: 'sequential', signers: [ { email: 'signer-pending+1@sandbox.allsign.io', name: 'Ana Torres', roleName: 'Vendedora', routingOrder: 1, }, { email: 'signer-pending+2@sandbox.allsign.io', name: 'Luis Ramírez', roleName: 'Comprador', routingOrder: 2, }, ], fields: [ { email: 'signer-pending+1@sandbox.allsign.io', pageNumber: 1, position: { x: 80, y: 620, }, }, { email: 'signer-pending+2@sandbox.allsign.io', pageNumber: 1, position: { x: 80, y: 520, }, }, ], }), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import base64 import os import uuid from pathlib import Path import requests respuesta = requests.post( "https://api.allsign.io/v3/documents", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "Contrato de compraventa — firma en orden", "file": { "content": base64.b64encode(Path("contrato.pdf").read_bytes()).decode(), "fileType": "pdf", "name": "contrato.pdf", }, "signingOrder": "sequential", "signers": [ { "email": "signer-pending+1@sandbox.allsign.io", "name": "Ana Torres", "roleName": "Vendedora", "routingOrder": 1, }, { "email": "signer-pending+2@sandbox.allsign.io", "name": "Luis Ramírez", "roleName": "Comprador", "routingOrder": 2, }, ], "fields": [ { "email": "signer-pending+1@sandbox.allsign.io", "pageNumber": 1, "position": { "x": 80, "y": 620, }, }, { "email": "signer-pending+2@sandbox.allsign.io", "pageNumber": 1, "position": { "x": 80, "y": 520, }, }, ], }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 201** ``` { "livemode": false, "id": "doc_c0d4a131b744480689aa24b2f39215cf", "object": "document", "name": "Contrato de compraventa — firma en orden", "status": "draft", "documentType": "EDITABLE", "signerCount": 2, "signedCount": 0, "ownerId": "usr_7dccf5fa2358441c929704873612a523", "orgId": "be1b49d9-fa2f-4921-988e-e61974df4739", "folderId": null, "expiresAt": null, "signingOrder": "sequential", "currentStage": null, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:01.291000Z", "updatedAt": "2026-07-11T18:00:01.291000Z" } ``` ## 3 Envíalo a firma POST `/documents/{document_id}/send` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#send-document) ### Qué haces Solo la etapa activa recibe su invitación: por ahora, la vendedora. ### Qué mirar - `status` = `"awaiting_signatures"` - `currentStage` = `1` ### En producción Con una key live el envío consume créditos de tu saldo; en sandbox no se cobra nada. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/send' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/send', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/send", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={}, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "doc_c0d4a131b744480689aa24b2f39215cf", "object": "document", "name": "Contrato de compraventa — firma en orden", "status": "awaiting_signatures", "documentType": "EDITABLE", "signerCount": 2, "signedCount": 0, "ownerId": "usr_7dccf5fa2358441c929704873612a523", "orgId": "be1b49d9-fa2f-4921-988e-e61974df4739", "folderId": null, "expiresAt": null, "signingOrder": "sequential", "currentStage": 1, "expirationReminders": null, "templateId": null, "templateVersionId": null, "parentDocumentId": null, "createdAt": "2026-07-11T18:00:01.291000Z", "updatedAt": "2026-07-11T18:00:03.517000Z" } ``` ## 4 Revisa a quién le toca GET `/documents/{document_id}/signers` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#list-signers) ### Qué haces El comprador está en waiting\_turn: existe, pero todavía no tiene liga ni puede firmar. ### Qué mirar - `data.0.status` = `"waiting_turn"` - `data.0.routingOrder` = `2` - `data.1.status` = `"sent"` - `data.1.routingOrder` = `1` **cURL** ``` curl 'https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" ``` **Node** ``` const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers', { headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import requests respuesta = requests.get( "https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "object": "list", "data": [ { "livemode": false, "id": "sgr_da9664ed9de849219e7bd3defea3700e", "object": "signer", "documentId": "doc_c0d4a131b744480689aa24b2f39215cf", "email": "signer-pending+2@sandbox.allsign.io", "phone": null, "name": "Luis Ramírez", "status": "waiting_turn", "signedAt": null, "routingOrder": 2, "delivery": null }, { "livemode": false, "id": "sgr_c182f41f3cc14d3bac3131e1096a291b", "object": "signer", "documentId": "doc_c0d4a131b744480689aa24b2f39215cf", "email": "signer-pending+1@sandbox.allsign.io", "phone": null, "name": "Ana Torres", "status": "sent", "signedAt": null, "routingOrder": 1, "delivery": null } ], "hasMore": false, "nextCursor": null, "previousCursor": null, "limit": null } ``` ## 5 Intenta firmar por el comprador antes de su turno POST `/sandbox/signers/{signer_id}/sign` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#sign-as-a-signer-sandbox-only) ### Qué haces Mientras su etapa no abre, nadie firma por el comprador: el orden lo hace cumplir la API, no tu interfaz. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 409** ``` { "type": "https://allsign.io/developers/docs/errors#NOT_YOUR_TURN", "title": "Conflict", "status": 409, "detail": "Signer belongs to stage 2 and stage 1 has not finished — sign that stage first.", "instance": "/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign", "code": "NOT_YOUR_TURN", "requestId": "req_d29e50a055114b728bdde26ba79637cb", "reason": "not_your_turn" } ``` ## 6 Firma por la vendedora (solo en sandbox) POST `/sandbox/signers/{signer_id}/sign` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#sign-as-a-signer-sandbox-only) ### Qué haces Al terminar la etapa 1, AllSign abre la 2 e invita al comprador. ### Qué mirar - `status` = `"signed"` - `signedAt` = `"2026-07-11T18:00:05.610000Z"` ### En producción Aquí firma la vendedora desde su correo o WhatsApp. Esta operación solo funciona en sandbox. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/sandbox/signers/sgr_c182f41f3cc14d3bac3131e1096a291b/sign' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/sandbox/signers/sgr_c182f41f3cc14d3bac3131e1096a291b/sign', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/sandbox/signers/sgr_c182f41f3cc14d3bac3131e1096a291b/sign", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "sgr_c182f41f3cc14d3bac3131e1096a291b", "object": "signer", "documentId": "doc_c0d4a131b744480689aa24b2f39215cf", "email": "signer-pending+1@sandbox.allsign.io", "phone": null, "name": "Ana Torres", "status": "signed", "signedAt": "2026-07-11T18:00:05.610000Z", "routingOrder": 1, "delivery": null } ``` ## 7 Recuérdale al comprador que ya le toca POST `/documents/{document_id}/signers/{signer_id}/remind` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#remind-signer) ### Qué haces La etapa 2 se abrió en la misma llamada en que firmó la vendedora. Si el comprador tarda, recuérdale: le reenvía su invitación. En sandbox ningún mensaje sale a @sandbox.allsign.io: delivered viene en false y nextAllowedAt es el mismo instante. ### Qué mirar - `delivered` = `false` - `nextAllowedAt` = `"2026-07-11T18:00:06.241000Z"` ### En producción Le llega de nuevo el correo o el WhatsApp y delivered viene en true. El siguiente recordatorio a esa persona espera 4 horas: antes de nextAllowedAt la API responde 429. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers/sgr_da9664ed9de849219e7bd3defea3700e/remind' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers/sgr_da9664ed9de849219e7bd3defea3700e/remind', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers/sgr_da9664ed9de849219e7bd3defea3700e/remind", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, json={}, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "documentId": "doc_c0d4a131b744480689aa24b2f39215cf", "signerId": "sgr_da9664ed9de849219e7bd3defea3700e", "sentAt": "2026-07-11T18:00:06.241000Z", "nextAllowedAt": "2026-07-11T18:00:06.241000Z", "channel": "email", "delivered": false } ``` ## 8 Firma por el comprador (solo en sandbox) POST `/sandbox/signers/{signer_id}/sign` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#sign-as-a-signer-sandbox-only) ### Qué haces Era la última firma: el documento se cierra y su evidencia se genera. ### Qué mirar - `status` = `"signed"` ### En producción Aquí firma el comprador desde su invitación. **cURL** ``` curl -X POST 'https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign' \ -H "Authorization: Bearer $ALLSIGN_API_KEY" \ -H "AllSign-Version: 2026-07-11" \ -H "Idempotency-Key: $(uuidgen)" ``` **Node** ``` import { randomUUID } from 'node:crypto'; const respuesta = await fetch('https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`, 'AllSign-Version': '2026-07-11', 'Idempotency-Key': randomUUID(), }, }); console.log(respuesta.status, await respuesta.json()); ``` **Python** ``` import os import uuid import requests respuesta = requests.post( "https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign", headers={ "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}", "AllSign-Version": "2026-07-11", "Idempotency-Key": str(uuid.uuid4()), }, ) print(respuesta.status_code, respuesta.json()) ``` **Respuesta · 200** ``` { "livemode": false, "id": "sgr_da9664ed9de849219e7bd3defea3700e", "object": "signer", "documentId": "doc_c0d4a131b744480689aa24b2f39215cf", "email": "signer-pending+2@sandbox.allsign.io", "phone": null, "name": "Luis Ramírez", "status": "signed", "signedAt": "2026-07-11T18:00:25.649000Z", "routingOrder": 2, "delivery": null } ``` ## 9 Espera el webhook document.completed POST `tu endpoint` · evento `document.completed` ### Qué haces Llega cuando firmó la última etapa y la evidencia ya está lista. AllSign le hace `POST` a la URL que registraste, con el evento `document.completed`. Responde `2xx` rápido y, antes de confiar en el cuerpo, verifica la firma: `webhook-signature` es un HMAC-SHA256 de `webhook-id.webhook-timestamp.cuerpo` con el secreto del webhook ([Webhooks](https://allsign.io/developers/docs/webhooks)). Esta entrega llegó de verdad al receptor del test y su firma se verificó con el secreto del webhook. ### Qué mirar - `data.status` = `"completed"` **Entrega recibida · Cuerpo** ``` { "data": { "name": "Contrato de compraventa — firma en orden", "nom151": null, "status": "completed", "signers": [ { "name": "Luis Ramírez", "email": "signer-pending+2@sandbox.allsign.io", "signedAt": "2026-07-11T18:00:25.649000Z", "signerId": "sgr_da9664ed9de849219e7bd3defea3700e", "authMethod": "POR_DEFINIR" }, { "name": "Ana Torres", "email": "signer-pending+1@sandbox.allsign.io", "signedAt": "2026-07-11T18:00:05.610000Z", "signerId": "sgr_c182f41f3cc14d3bac3131e1096a291b", "authMethod": "POR_DEFINIR" } ], "documentId": "doc_c0d4a131b744480689aa24b2f39215cf", "completedAt": "2026-07-11T18:00:27.331000Z", "evidencePdf": { "url": "https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/evidence", "sha256": "3fc00ce4f2b91af9fd713156aa72dd3d9817f9e9fefaa3558cb1de5813f114e4", "mimeType": "application/pdf", "sizeBytes": 92231 } }, "eventId": "evt_052553103d704209bbf919105be6846a", "livemode": false, "tenantId": "6999476b-ded8-46c5-8147-096f3b3b3fa8", "eventType": "document.completed", "apiVersion": "2026-07-11", "occurredAt": "2026-07-11T18:00:27.333Z" } ``` **Entrega recibida · Cabeceras** ``` { "content-type": "application/json", "allsign-event": "document.completed", "allsign-livemode": "false", "webhook-id": "05255310-3d70-4209-bbf9-19105be6846a", "webhook-timestamp": "1783792827", "webhook-signature": "v1,", "allsign-delivery-id": "7752e38e-f845-42a5-9e86-0c2117c30845" } ``` **Verifica la firma · Node** ``` import { createHmac, timingSafeEqual } from 'node:crypto'; export function verificarWebhook(cuerpoCrudo, headers, secreto) { const id = headers['webhook-id']; const timestamp = headers['webhook-timestamp']; if (!id || !/^\d+$/.test(timestamp ?? '') || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const llave = Buffer.from(secreto.replace(/^whsec_/, ''), 'base64'); const esperada = Buffer.from(createHmac('sha256', llave).update(`${id}.${timestamp}.${cuerpoCrudo}`).digest('base64')); return (headers['webhook-signature'] ?? '').split(' ').some((token) => { const [version, firma = ''] = token.split(','); const candidata = Buffer.from(firma); return version === 'v1' && candidata.length === esperada.length && timingSafeEqual(candidata, esperada); }); } ``` **Verifica la firma · Python** ``` import base64 import hashlib import hmac import time def verificar_webhook(cuerpo_crudo: bytes, headers: dict, secreto: str) -> bool: id_ = headers.get("webhook-id") timestamp = headers.get("webhook-timestamp", "") if not id_ or not (timestamp.isascii() and timestamp.isdigit()) or abs(time.time() - int(timestamp)) > 300: return False llave = base64.b64decode(secreto.removeprefix("whsec_")) contenido = f"{id_}.{timestamp}.".encode() + cuerpo_crudo esperada = base64.b64encode(hmac.new(llave, contenido, hashlib.sha256).digest()) return any( version == "v1" and hmac.compare_digest(firma.encode(), esperada) for version, _, firma in (token.partition(",") for token in headers.get("webhook-signature", "").split()) ) ``` ## Errores comunes de esta receta Errores reales que el test de esta receta provocó a propósito en sandbox, sobre sus mismos pasos. Cada uno enlaza a su explicación en [Errores](https://allsign.io/developers/docs/errors). `VALIDATION_ERROR` · 422 · en el paso 2 (Crea el documento con el orden de firma) Pide sequential y olvida el routingOrder de un firmante. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#VALIDATION_ERROR) **Respuesta · 422 · VALIDATION_ERROR** ``` { "type": "https://allsign.io/developers/docs/errors#VALIDATION_ERROR", "title": "Validation failed", "status": 422, "detail": "One or more fields are invalid.", "instance": "/v3/documents", "code": "VALIDATION_ERROR", "requestId": "req_6a8f96e3b2cc40ffa35ad785489ef781", "errors": [ { "code": "INVALID_VALUE", "detail": "signingOrder='sequential' requires 'routingOrder' on every signer; missing at signers[1]." } ] } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:20 (hora CDMX) · test `docs-checks-v3/tests/recetas/varios-firmantes-en-orden.receta.spec.ts` `NOT_YOUR_TURN` · 409 · en el paso 7 (Recuérdale al comprador que ya le toca) Recuérdale al comprador antes de su turno. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#NOT_YOUR_TURN) **Respuesta · 409 · NOT_YOUR_TURN** ``` { "type": "https://allsign.io/developers/docs/errors#NOT_YOUR_TURN", "title": "Conflict", "status": 409, "detail": "Signer belongs to stage 2 and stage 1 has not finished — nothing to remind yet.", "instance": "/v3/documents/doc_0b2b0e22e2f74967b71978464dc385c7/signers/sgr_9986142474b44b3782418f09da8b366f/remind", "code": "NOT_YOUR_TURN", "requestId": "req_f75b6541f8f540aaa398ccc8c4e240f8", "reason": "not_your_turn" } ``` Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:20 (hora CDMX) · test `docs-checks-v3/tests/recetas/varios-firmantes-en-orden.receta.spec.ts` ## Siguiente - [Recuerda, reasigna o anula un documento que nadie firma](https://allsign.io/developers/docs/recetas/anula-recuerda-reasigna) — Destrabar un documento enviado: recordarle al firmante, pasarle su lugar a otra persona y, si ya no procede, anularlo dejando la razón. - [Recibe y verifica los webhooks de AllSign](https://allsign.io/developers/docs/recetas/recibe-y-verifica-webhooks) — Un endpoint que recibe los eventos de AllSign, comprueba que de verdad los firmó AllSign y sobrevive a sus propias caídas y a la rotación del secreto.