Signing / E-Signature Audit-archive envelopes, PAdES-LT cryptographic signatures, downloadable audit certificates.
What This Is
A SignEnvelope is an audit-archive Profile holding up to twenty PDFs sent to one or more recipients for electronic signature. Every envelope is parented to a Workspace. The platform's internal PAdES-LT signing engine produces a long-term-validation cryptographic signature on every completed document; an envelope-level audit certificate captures the chain of evidence (consent acceptance, OTP authentication where required, per-recipient sign events, and document hashes). The audit chain is hash-linked and verifiable, and a downloadable audit certificate is available once an envelope reaches a terminal state (completed, declined, voided, expired, or failed).
Each envelope flows through a small lifecycle state machine — Draft, Sent, InProgress, then one of Completed / Declined / Voided / Expired / Failed — and emits a global activity-stream event on every transition so comments and notification subscribers can react.
Permission model: Workspace membership covers read endpoints, /send, document downloads (original and signed PDFs) and the audit-certificate download; workspace admin is required only for /void and /retry. Signer-surface endpoints are authenticated by the path token only.
Endpoint Summary
Sender / Admin Surface — Workspace-Parented Envelopes
| Method | Endpoint | Description |
|---|---|---|
| POST | /current/workspace/{workspace_id}/sign_envelopes/create/ | Create a draft envelope |
| GET | /current/workspace/{workspace_id}/sign_envelopes/list/ | List envelopes owned by the workspace (offset-paginated) |
| GET | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/details/ | Get an envelope (with documents/recipients/fields sub-collections) |
| POST | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/update/ | Update mutable fields on a draft envelope (POST or PATCH; POST recommended) |
| POST | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/send/ | Send a draft envelope (Draft → Sent; routes to first recipient slot) |
| POST | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/void/ | Void a non-terminal envelope (cascade to Voided; reason required) |
| POST | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/retry/ | Manually re-drive a stuck envelope through the self-healing recovery routine (admin; idempotent + no-op-success; no body; permanent failures cascade to Failed) |
| GET | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/documents/{document_id}/download/ | Stream the source PDF bytes directly (application/pdf; Bearer-authed) |
| GET | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/documents/{document_id}/preview/ | Stream the source PDF for inline preview (application/pdf; Bearer-authed) |
| GET | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/documents/{document_id}/signed/download/ | Stream the signed PDF bytes directly (application/pdf; Bearer-authed; 404 code 146422 until the document completes) |
| GET | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/audit/download/ | Stream the audit-certificate bytes directly (JSON evidence record; Bearer-authed; 404 code 128301 until the envelope reaches a terminal state) |
| GET | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/audit/pdf/download/ | Stream the rendered Certificate-of-Completion PDF directly (application/pdf; Bearer-authed; 404 code 121249 until the envelope is terminal and the PDF has rendered) |
| POST | /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/my_sign_link/ | Mint an action-capable signer link for the calling workspace member when they are themselves a currently-actionable pending signer (else a structured blocked / terminal / reauth response) |
Signer Surface (Public — Path Token Auth)
The recipient receives a notification with a signing link of the form /sign_envelopes/signer/{token}/view. The {token} is a short-lived JWT bound to a (sign_envelope_id, recipient_id) pair; no Fastio session is required to call these endpoints.
| Method | Endpoint | Description |
|---|---|---|
| GET | /current/sign_envelopes/signer/{token}/view/ | Landing — returns the envelope, recipient state, document list with per-document source download URLs, the recipient's fields, and the in-force consent disclosure |
| GET | /current/sign_envelopes/signer/{token}/authenticate/ | Issue an OTP (recipients with auth_method=email_otp or sms_otp) |
| POST | /current/sign_envelopes/signer/{token}/authenticate/ | Verify an OTP and elevate the session token |
| POST | /current/sign_envelopes/signer/{token}/sign/ | Submit consent + field values; queues the async PAdES signing job |
| GET | /current/sign_envelopes/signer/{token}/status/ | Poll the signing pipeline; returns adaptive next_poll_seconds |
| POST | /current/sign_envelopes/signer/{token}/decline/ | Decline to sign (envelope cascades to Declined) |
| GET | /current/sign_envelopes/signer/{token}/completed/ | Once the envelope is terminal — recipient state, per-document completion, audit-certificate availability |
| GET | /current/sign_envelopes/signer/{token}/documents/{document_id}/download/ | Stream the source PDF for one document (original pre-signature file — for the signing UI's field overlay). Signer token is the sole credential |
| GET | /current/sign_envelopes/signer/{token}/documents/{document_id}/signed/download/ | Stream the signed PDF for one document. Signer token is the sole credential. 1609 (Not Found) until the signed PDF exists; 1680 (Access Denied) / 401 until the envelope is fully completed |
| GET | /current/sign_envelopes/signer/{token}/audit/download/ | Stream the envelope's audit certificate (JSON). Signer token is the sole credential. 1609 (Not Found) until the certificate exists; 1680 (Access Denied) / 401 while the envelope is still active |
| GET | /current/sign_envelopes/signer/{token}/audit/pdf/download/ | Stream the rendered Certificate-of-Completion PDF. Signer token is the sole credential. 1680 (Access Denied) / 401 while the envelope is still active; 1609 (Not Found) once terminal but the PDF has not yet rendered |
Provider Webhook Surface (Public — HMAC Auth)
Inbound webhook receiver for provider events. The connection is identified by an opaque short id in the URL and is authenticated via the provider's own HMAC signature header (verified per-provider, never via a Fastio session token).
| Method | Endpoint | Description |
|---|---|---|
| POST | /current/webhooks/sign_envelopes/{provider}/{connection_short_id}/ | Receive a provider webhook event (signature-verified, replay-deduped, enqueued for the async normalizer) |
{provider} is the slug of the connected provider implementation; the platform ships a Documenso connector for inbound provider integration.
Sign Template Surface — Workspace-Parented Templates
Reusable signing templates that capture recipient role slots, document slots, field placements, and envelope policy. A template is instantiated to produce a draft envelope with concrete bindings applied. Signing is enabled on every plan. Template ids use the sa OpaqueId family (30-character self-describing string).
| Method | Endpoint | Description |
|---|---|---|
| POST | /current/workspace/{workspace_id}/sign_templates/create/ | Create a signing template (PERM_MEMBER) |
| GET | /current/workspace/{workspace_id}/sign_templates/list/ | List non-deleted templates in the workspace (PERM_VIEW; offset-paginated) |
| GET | /current/workspace/{workspace_id}/sign_templates/{template_id}/details/ | Fetch a template with full snapshot (PERM_VIEW) |
| POST | /current/workspace/{workspace_id}/sign_templates/{template_id}/update/ | In-place update under optimistic CAS — expected_version required (PERM_MEMBER) |
| POST | /current/workspace/{workspace_id}/sign_templates/{template_id}/delete/ | Soft-delete a template — tombstoned, not purged; advisory referrer scan returned (PERM_ADMIN) |
| POST | /current/workspace/{workspace_id}/sign_templates/{template_id}/instantiate/ | Apply a template: bind role slots to concrete people, resolve document slots, create a draft envelope (PERM_MEMBER) |
Availability
Signing is enabled on every plan; capabilities.signing on the org resource confirms availability. Should an org's plan ever not grant signing, the signing endpoints reject calls with a feature-disabled error.
Concepts
SignEnvelope Profile
A SignEnvelope is a Profile — the same hierarchy primitive used by Workspaces and Shares. Each envelope carries:
- A lifecycle
envelope_status(see Lifecycle below). - A
provideridentifier and an opaqueprovider_envelope_idcorrelating the envelope to its upstream signing connector (internal PAdES engine or an external provider). - A
policyobject configured at create time (auth_method, reminder cadence, retention hints). - Lifecycle timestamps (
sent_at,completed_at,voided_at,expires_at). - A
revision_numberthat increments on each mutating write. - An optional
audit_certificate_node_idpopulated once the envelope reaches any terminal state (completed,declined,voided,expired, orfailed). - Four boolean lifecycle flags surfaced from the Profile base:
archived,closed,locked,suspended, plus alegal_holdflag. - A read-only
_trust_classfield set tosystem_trusted— downstream consumers (audit pipelines) read this to classify envelope output.
Documents, recipients, and fields are stored as sub-collections of the envelope and are returned inline on the detail / create response when present.
Lifecycle
draft -> sent
sent -> in_progress | declined | voided | expired
in_progress -> completed | declined | voided | expired | failed
(terminal) -> nothing
completed, declined, expired, voided, and failed are terminal. Sending a draft transitions draft -> sent and activates the first recipient's slot (sequential routing) or every recipient at once (parallel routing). A single decline cascades the envelope to declined. A void is sender-initiated and cascades the envelope to voided (a reason string is required; non-refundable per industry convention). Expiry runs from the expires_at policy and cascades to expired. failed is reserved for signing-pipeline errors that exhaust retries. A past-deadline envelope that is not yet complete is rejected for signing and declining, and is automatically transitioned to expired in the background; an envelope whose required recipients have all signed is allowed to finish (it is not expired).
Setting envelope_status directly via the update endpoint is not supported; lifecycle transitions only happen through dedicated endpoints (/send, /void) and through the signer-surface actions.
Documents, Recipients, Fields
A draft envelope is created with up to twenty source documents, at least one recipient, and zero or more fields. Documents are referenced by their source_node_id (a node in storage) and optionally a source_version_id (the document resource returns source_version_id: null when no specific version was pinned at create time); the create flow copies the file bytes into the envelope's own private storage instance so the envelope is a self-contained archive. Each document carries a display_order, a source_sha256, a signed_pdf_node_id (populated when signing completes), a completed_sha256, and a signed_at timestamp.
The authoritative “the signed PDF exists and is downloadable” signal for a document is a non-empty signed_pdf_node_id, surfaced as the boolean signed_document_available. The envelope resource rolls this up across all documents: signing_complete is true only once every document has a signed artifact (it is derived from the signed artifacts, so a terminal but unsigned envelope — declined, voided, expired, or failed — is NOT signing_complete), documents_progress reports { total, completed, in_progress, failed } counts (a document is completed when its signed artifact exists), and audit_certificate_available is true once the terminal-envelope audit certificate has been written (the certificate is generated on any terminal outcome — completed, declined, voided, expired, or failed). signing_failed is true when at least one document's signing attempt has errored without yet producing a signed artifact — the failure may be transient (it will retry) or permanent — and is independent of signing_complete; gate downloads on signed_document_available / signing_complete, not on document_copyback_status.
Recipients carry a role (one of signer, cc, viewer, approver, certified_recipient), a routing_order (1-based for sequential routing; identical numbers run in parallel), an auth_method (none, email_otp, sms_otp), and per-recipient lifecycle timestamps. Recipient status flows pending → sent → viewed → authenticated → signing_in_progress → signed, with declined / expired / voided / failed as terminal short-cuts.
Fields are placed on a (document_id, page) using normalized 0..1 coordinates (x_norm / y_norm for the top-left corner, w_norm / h_norm for the bounding box). The supported field type values are signature, initial, date, text, and checkbox. A field belongs to exactly one recipient and is rendered only when that recipient signs.
A field can optionally carry a validation object that constrains the value a signer may submit. It is echoed back as validation_json on the field resource (the /details/ and signer /view/ responses) so the signing UI can enforce the same rules client-side. Supported keys (all optional):
- Text-style fields (
text,title,company,full_name,email,radio,dropdown):min_length(integer ≥ 0),max_length(integer ≥ 0; must be ≥min_lengthwhen both are present), andpattern(a regular-expression source — no delimiters, up to 512 characters — enforced as a full-string anchored match). - Date fields (
date):date_minanddate_max, each aYYYY-MM-DDcalendar date (date_minmust be ≤date_maxwhen both are present).
Any other key, or a validation object on a signature / initial / checkbox field, is rejected at create/update time with 1605 (Invalid Input). The object is also size-capped. At /sign/ time the platform enforces these rules against the submitted value before recording the signature; a violation is rejected with 1605 (Invalid Input) and lists the offending fields. Date fields are always validated as real calendar dates at sign time even without a validation object.
Trust Class
Every envelope-resource response carries a read-only _trust_class field set to system_trusted. Handler outputs that pass through a SignEnvelope are platform-derived and downstream consumers (audit pipelines, AI agents) can rely on the value without independent verification. _trust_class is display metadata; it is never accepted as an input.
Audit Chain & Certificate
Every state change on an envelope appends a hash-chained audit row. Rows record the envelope's event_seq, an event_type (see Activity Events below), the actor's identifier, and a content_hash that links to the immediately preceding row's hash. The chain is signed by a per-workspace HMAC audit key.
When an envelope reaches a terminal state — completed, declined, voided, expired, or failed — the platform builds a structured audit certificate (JSON evidence record with a self-anchoring HMAC) and copies it into the envelope's storage instance. The audit_certificate_node_id field on the envelope resource carries the node reference. The dedicated /audit/download/ endpoint streams the certificate bytes directly (Content-Disposition: attachment, no token round-trip). Until the certificate exists, the owner endpoint returns 404 with code 128301 and the signer endpoint returns 1609 (Not Found) — so a voided envelope's certificate IS downloadable; do not poll “until completed”.
The certificate is a single JSON object with these top-level keys: schema_version ("1.0"), generated_at, envelope (id, status, lifecycle timestamps, revision_number, first-view charge state, and parent{parent_id,parent_type}), recipients[] (id, signer identity, email, display name, role, routing order, status, auth method, per-stage timestamps, decline reason), documents[] (document_id, display_order, source_node_id, source_sha256, signed_pdf_node_id, completed_sha256, signed_at, copyback_status), consent_texts[] (version, body_hash), audit_chain[] (hash-linked rows: sequence, event_id, event_type, actor ids, occurred_at/recorded_at, prior_event_hash/event_hash, ip, user_agent, document_hash, consent_text_version, provider ids, and canonical_json), audit_chain_count, and attestation{algorithm:"HMAC-SHA256", master_version, attestation_hmac_sha256} anchoring the whole body. All timestamps are Y-m-d H:i:s UTC; all hashes are 64-character lowercase hex. The JSON is the machine-verifiable authoritative record; a human-readable Certificate-of-Completion PDF rendition of the same evidence is also produced and downloadable (see Certificate-of-Completion PDF below).
Internal PAdES-LT Signing
The default signing path uses the platform's internal PAdES-LT engine — a long-term-validation cryptographic signature embedded directly in each completed PDF. Signed PDFs include an embedded validation certificate chain so the signature remains verifiable offline without contacting the signing service. The signing pipeline runs asynchronously after /sign; the signer client polls /status/ for completion.
Recipient Authentication (OTP)
When a recipient's auth_method is email_otp or sms_otp, the recipient must complete an OTP gate before submitting field values. The flow is:
- The recipient's client calls
GET /sign_envelopes/signer/{token}/authenticate/— the platform issues a fresh 6-digit code and sends it to the recipient. The code is short-lived and code-issuance is throttled per(envelope, recipient). - The recipient submits the code via
POST /sign_envelopes/signer/{token}/authenticate/. On match, the platform records the authenticated timestamp, re-mints the session token with the elevatedauth_level, and returns the new token. On miss, the response includesattempts_remainingand verification is throttled per(envelope, recipient). - The recipient's subsequent
/sign/call carries the elevated token; the platform refuses to accept signatures from a token whoseauth_leveldoes not satisfy the recipient's configuredauth_method.
Recipients with auth_method=none skip the authenticate step entirely.
A recipient who already authenticated but no longer holds the elevated token — for example a returning signer who reopened the email link in a fresh browser session — may re-invoke GET /sign_envelopes/signer/{token}/authenticate/ to receive a new code and re-elevate. The prior authentication is preserved (re-issuing a code does not reset recipient state); only signed, declined, and otherwise-terminal recipients are refused.
Consent Acceptance
Before submitting field values, a recipient must accept the in-force ESIGN/UETA consent disclosure surfaced on the /view response. The acceptance is recorded in the audit chain along with the hash of the consent body the recipient was shown. The /sign/ request carries consent_body_hash and consent_text_version to prove the acceptance; a mismatch is rejected with 1605 (Invalid Input).
Document Byte Access
The source document download and preview endpoints stream the PDF bytes directly (200 application/pdf, Bearer-authed) — the bytes live in the envelope's own storage instance and are fetched server-side, so a document's source_node_id must NOT be read through the generic Storage /storage/{node_id}/read/ endpoint (that resolves nodes in the workspace instance, not the envelope's). The signed-PDF (signed/download) and audit-certificate (audit/download) endpoints stream their bytes the same way (signed PDF as application/pdf; the audit certificate as its JSON evidence record).
First-View Billing
Sending an envelope reserves credits at send time; a separate first-view charge fires the first time any recipient opens the /view landing page. Subsequent views by the same or different recipients do not re-charge — the billing meter is once-per-envelope. The user-visible first_view_charged_at timestamp is returned on the envelope resource so callers can see when the meter fired. Voiding an envelope does not refund the credits (industry convention: credits consumed at send).
Request / Response Shapes
SignEnvelope Resource
{
"result": true,
"sign_envelope": {
"id": "{envelope_id}",
"_trust_class": "system_trusted",
"name": "Master Services Agreement",
"envelope_status": "draft",
"is_terminal": false,
"signing_complete": false,
"signing_failed": false,
"documents_progress": { "total": 1, "completed": 0, "in_progress": 1, "failed": 0 },
"audit_certificate_available": false,
"audit_certificate_pdf_available": false,
"provider": "internal_pades",
"provider_envelope_id": "{provider_envelope_id}",
"revision_number": 1,
"origin_step_occurrence_id": null,
"sent_at": null,
"completed_at": null,
"voided_at": null,
"voided_reason": null,
"expires_at": "2026-06-15 14:30:00 UTC",
"first_view_charged_at": null,
"audit_certificate_node_id": null,
"audit_certificate_pdf_node_id": null,
"policy": {
"auth_method": "email_otp",
"reminder_cadence_hours": 24,
"retention_days": 2555
},
"archived": false,
"closed": false,
"locked": false,
"suspended": false,
"legal_hold": false,
"parent_type": "workspace",
"parent_workspace": {
"id": "12345678901234567890",
"name": "Sales Contracts",
"parent_org_id": "98765432109876543210"
},
"parent_org": "98765432109876543210",
"created_at": "2026-05-23 17:42:18 UTC",
"updated_at": "2026-05-23 17:42:18 UTC",
"deleted_at": null,
"retention_expires_at": null,
"documents": [
{
"document_id": "{document_id}",
"source_node_id": "2ltsu-q4mja-cuv7p-gc5yd-lxnsj-wee4",
"source_version_id": "3l5np-obens-xscsb-y5wws-gwcd6-ni6t",
"source_sha256": "{hex_sha256}",
"display_order": 0,
"signed_pdf_node_id": null,
"completed_sha256": null,
"completed_byte_count": null,
"signed_at": null,
"document_copyback_status": "not_required",
"signed_document_available": false,
"created_at": "2026-05-23 17:42:18 UTC",
"updated_at": "2026-05-23 17:42:18 UTC"
}
],
"recipients": [
{
"recipient_id": "{recipient_id}",
"signer_identity_id": "{signer_identity_id}",
"email": "signer@example.com",
"display_name": "Alex Signer",
"role": "signer",
"routing_order": 1,
"status": "pending",
"auth_method": "email_otp",
"sent_at": null,
"viewed_at": null,
"authenticated_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null,
"created_at": "2026-05-23 17:42:18 UTC",
"updated_at": "2026-05-23 17:42:18 UTC"
}
],
"fields": [
{
"field_id": "{field_id}",
"recipient_id": "{recipient_id}",
"document_id": "{document_id}",
"page": 1,
"x_norm": 0.5,
"y_norm": 0.5,
"w_norm": 0.2,
"h_norm": 0.05,
"type": "signature",
"required": true,
"value_json": null,
"validation_json": null,
"created_at": "2026-05-23 17:42:18 UTC",
"updated_at": "2026-05-23 17:42:18 UTC"
}
]
}
}
Create an Envelope
curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/create/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Master Services Agreement",
"expires_at": "2026-06-15 14:30:00 UTC",
"policy_json": {
"auth_method": "email_otp",
"reminder_cadence_hours": 24
},
"documents": [
{
"source_node_id": "2ltsu-q4mja-cuv7p-gc5yd-lxnsj-wee4",
"source_version_id": "3l5np-obens-xscsb-y5wws-gwcd6-ni6t",
"display_order": 0
}
],
"recipients": [
{
"email": "signer@example.com",
"display_name": "Alex Signer",
"phone_e164": "+15555550123",
"role": "signer",
"routing_order": 1,
"auth_method": "email_otp"
}
],
"fields": [
{
"recipient_email": "signer@example.com",
"document_index": 0,
"page": 1,
"x_norm": 0.5,
"y_norm": 0.5,
"w_norm": 0.2,
"h_norm": 0.05,
"type": "signature",
"required": true,
"value_json": null
},
{
"recipient_email": "signer@example.com",
"document_index": 0,
"page": 1,
"x_norm": 0.5,
"y_norm": 0.6,
"w_norm": 0.3,
"h_norm": 0.04,
"type": "text",
"required": true,
"validation": { "min_length": 2, "max_length": 64, "pattern": "[A-Za-z .'-]+" }
}
]
}'
Body fields:
name(optional) — human-facing envelope display name / title (UI label, max 255 chars). Returned on the envelope resource asname; omitted/empty leaves itnullon the resource.expires_at(optional) — UTC timestamp at which the envelope auto-expires; passnullto use the policy default.policy_json(optional) — envelope policy bag. The platform readsauth_method(one ofemail_otp/sms_otp/none), reminder cadence, and retention; unknown keys are accepted forward-compatibly but not interpreted. Values must be JSON strings or integers — decimal/floating-point numbers are rejected with1605 (Invalid Input)/ HTTP 406; express any numeric value as an integer or a string.documents(required, 1..20) — list of source documents to copy into the envelope. Each entry needssource_node_id,source_version_id, and adisplay_order. The platform copies the file bytes into the envelope's storage at create time; later mutation of the source node does not affect the envelope.recipients(required, >= 1) — list of recipients. Each carriesemail,display_name, optionalphone_e164(required whenauth_method=sms_otp),role,routing_order(1-based), andauth_method. Multiple recipients with the samerouting_orderrun in parallel.fields(optional) — list of field placements.recipient_emailanddocument_indexcross-reference the lists above; coordinates are normalized to0..1. Each field may carry an optionalvalidationobject (see “Documents, Recipients, Fields” above for the supported keys); it is echoed back asvalidation_jsonand enforced at sign time.
A successful create returns 200 OK with the envelope in envelope_status: "draft" as a FLAT SignEnvelope object — the documents / recipients / fields sub-collections are NOT inlined on the create response (only /details/ inlines them), and provider / provider_envelope_id are null (the signing provider is allocated at send time). Fetch /details/ after create to read the generated document_ids and recipient ids. The envelope sits in draft until /send/ is invoked.
Update an Envelope
curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/update/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Master Services Agreement (rev B)",
"documents": [
{ "id": "{existing_document_id}", "display_order": 0 },
{ "source_node_id": "{new_pdf_node_id}", "display_order": 1 }
],
"recipients": [ ... ],
"fields": [ ... ]
}'
Update accepts POST or PATCH (POST recommended — PATCH is accepted by the endpoint but some intermediaries block it). Only draft envelopes are editable (a non-draft envelope is rejected with 1680 (Access Denied) / HTTP 401). Each successful update increments revision_number and re-appends the draft audit row. Field semantics: name (optional) — renames the envelope when a non-blank string is sent; absent / null / blank leaves the current name unchanged (the name cannot be cleared via update). expires_at and policy_json are declarative — every update writes them, so OMITTING one CLEARS it (resets to null); resend the current values to keep them. recipients is required and always a full replacement (>= 1). documents and fields are optional — omit to keep the current set. When you omit fields, the existing placements are preserved and re-mapped onto the recipients you send (matched by email; the endpoint regenerates recipient ids, so placements are re-pointed for you), and any placement whose signer or document was removed is dropped; supply a non-empty fields to replace all placements, or an explicit empty fields: [] to clear them. documents is optional:
- Omit it to leave the document set unchanged.
- Supply it for a full declarative replacement. Each entry KEEPS an existing document (carries its
id— thedocument_idfrom the details response;display_ordermay change) or ADDS a new one (carries asource_node_id, copied into the envelope at update time). Existing documents absent from the list are REMOVED along with their field placements. 1..20 documents must remain. document_indexinfieldsindexes into thedocumentsarray AS SENT (0-based), so resendfieldswith indices aligned to the new document order whenever you change the set.
Send an Envelope
curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/send/" \
-H "Authorization: Bearer {jwt_token}"
/send/ runs a billing preflight, transitions draft -> sent, activates the first routing-order slot (sequential) or every recipient (parallel), appends an audit row, and dispatches the notification pipeline. The response carries the post-send envelope shape. An already-sent envelope returns success without re-emitting events (idempotent retry semantics).
/send/ takes no body — it sends the envelope's already-saved documents, recipients, and fields. Before sending, the envelope must be complete: at least one document, at least one signing recipient (a signer, approver, or certified_recipient — a draft with only cc/viewer recipients cannot be sent), at least one field, and every signer-role recipient must have at least one field to complete (cc/viewer roles do not need a field). These checks run only at send time, not on create/update, so an in-progress draft may be saved incomplete. If the envelope is not ready, /send/ returns an input-validation error whose message names the specific gap — e.g. “Recipient {email} is a signer but has no fields to complete” — so you can fix exactly that recipient (place a field, change their role, or remove them) and retry.
Void an Envelope
curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/void/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{"reason": "Counterparty no longer required"}'
reason is required and capped at 1024 bytes. Void is destructive — voiding an already-terminal envelope (or any other disallowed transition) is rejected with 1680 (Access Denied) / HTTP 401; non-terminal envelopes cascade to voided with all pending recipients short-circuited. Credits are not refunded.
Get My Sign Link
curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/my_sign_link/" \
-H "Authorization: Bearer {jwt_token}"
Mints an action-capable signer link for the calling workspace member when that member is themselves a currently-actionable pending signer on the envelope — the only supported way for a workspace member who is also a recipient to obtain a sign link. The caller's recipient row is resolved through the platform's fail-closed, verified-email, org-scoped identity resolution, so a member can never obtain a link bound to a recipient that is not provably theirs.
The endpoint is POST (not GET) and takes no body: minting a signer token and consuming a throttle slot are state-changing, and declaring POST makes the endpoint enforce write scope — a read-only-scoped API token cannot acquire an action-capable sign link. Requires workspace membership on the envelope and the org's signing capability; both are checked before any token is minted, and the request is rate-limited per (member, envelope).
The response always returns 200 with result: true and a decision-shaped body; the decision is conveyed by which fields are populated:
- Actionable — the caller may sign now.
sign_urlis a usable signing link;recipient_status,auth_method, androuting_orderdescribe the caller's recipient row;is_terminalisfalse. For anemail_otp/sms_otprecipient the link still requires the OTP step first. - Blocked — the caller is a signer but their sequential routing slot has not been activated yet (
recipient_statusis stillpending). No link is minted (sign_urlisnull).blocked_signerslists the downstream signers (same org) whose turn unblocks once the caller signs, each as{"display_name": ..., "routing_order": ...}. - Re-auth required — the caller's row is alive but a fresh email link cannot advance it (an OTP recipient who already authenticated, or a recipient whose signing is already in progress); they must continue in their existing signer session. No link is minted;
reauth_requiredistrue. - Terminal — the envelope is in a terminal state; nothing to sign.
sign_url,recipient_status,auth_method, androuting_orderarenull;is_terminalistrue.
{
"result": true,
"sign_url": null,
"recipient_status": "pending",
"auth_method": "email_otp",
"routing_order": 2,
"blocked_signers": [
{"display_name": "Jordan Counterparty", "routing_order": 3}
],
"is_terminal": false
}
If the caller is not a signer on the envelope, the request is rejected with 1680 (Access Denied) / HTTP 401 (no recipient detail is leaked). Exceeding the per-(member, envelope) request throttle also returns 1680 (Access Denied) / HTTP 401.
Signer View
curl -X GET "https://api.fast.io/current/sign_envelopes/signer/{signer_token}/view/"
Returns the envelope state, the recipient's own state, the list of documents (each carrying an absolute, cookie-free download_url — served from the downloadai content host so the signer's app cookies are never attached — the client fetches directly to stream the source PDF), the fields belonging to this recipient, and the in-force consent disclosure with body and body hash. The first call additionally fires the first-view billing meter (best-effort; the view is served even on a billing miss).
{
"result": true,
"envelope": {
"id": "{envelope_id}",
"envelope_status": "sent",
"is_terminal": false,
"expires_at": "2026-06-15 14:30:00 UTC",
"sender_name": "Sales Contracts",
"document_count": 1,
"recipient_count": 1,
"policy": {
"otp_required": true,
"auth_method": "email_otp"
}
},
"recipient": {
"id": "{recipient_id}",
"email": "signer@example.com",
"display_name": "Alex Signer",
"role": "signer",
"routing_order": 1,
"status": "viewed",
"auth_level": "none",
"sent_at": "2026-05-23 17:42:18 UTC",
"viewed_at": "2026-05-23 18:01:04 UTC",
"authenticated_at": null,
"signed_at": null,
"declined_at": null
},
"documents": [
{
"document_id": "{document_id}",
"display_name": "Master Services Agreement.pdf",
"byte_count": 184302,
"display_order": 0,
"signed_at": null,
"download_url": "https://downloadai.fast.io/api/current/sign_envelopes/signer/{token}/documents/{document_id}/download/"
}
],
"fields": [
{
"field_id": "{field_id}",
"document_id": "{document_id}",
"page": 1,
"x_norm": 0.5,
"y_norm": 0.5,
"w_norm": 0.2,
"h_norm": 0.05,
"type": "signature",
"required": true,
"value_json": null,
"validation_json": null
}
],
"consent": {
"version": "1.0.0",
"body": "...",
"body_hash": "{hex_sha256}"
}
}
Signer Authenticate (OTP)
# Issue a code
curl -X GET "https://api.fast.io/current/sign_envelopes/signer/{signer_token}/authenticate/"
# Verify a code; on success returns a new elevated token
curl -X POST "https://api.fast.io/current/sign_envelopes/signer/{signer_token}/authenticate/" \
-H "Content-Type: application/json" \
-d '{"code": "123456"}'
The success response carries {"authenticated": true, "new_token": "{signer_token}"} — the client persists the new token and uses it on the subsequent /sign/ call. The failure response carries {"authenticated": false, "attempts_remaining": 2} (a small integer counting down the remaining verify attempts in the current (envelope, recipient) window; the ceiling is not published).
Signer Sign
curl -X POST "https://api.fast.io/current/sign_envelopes/signer/{signer_token}/sign/" \
-H "Content-Type: application/json" \
-d '{
"consent_body_hash": "{hex_sha256}",
"consent_text_version": "1.0.0",
"fields": [
{"field_id": "{field_id}", "value_json": "{\"kind\": \"signature\", \"data_url\": \"data:image/png;base64,{base64_png}\"}"}
]
}'
Returns 202 ACCEPTED with {"status": "signing", "poll_url": "...", "status_token": "{signer_token}", "is_last_signer": false}. The platform queues an asynchronous PAdES-LT signing job and the client polls /status/ until completion. Each issued signer token is single-use for the consume-style actions (/sign, /decline, /authenticate POST) — once consumed the token can no longer drive those actions, and the response surface re-mints a fresh polling-scoped token for the post-sign status flow.
Before the signature is recorded, every submitted value is validated against its field's validation rules (and every date field is validated as a real YYYY-MM-DD calendar date). A value that violates a rule — too short / too long, failing the pattern, outside the date_min / date_max window, or an unparseable date — is rejected with 1605 (Invalid Input), and the error lists each offending field. Because this check runs before the single-use token is consumed, a correctable mistake leaves the link usable for a retry.
Signer Status
curl -X GET "https://api.fast.io/current/sign_envelopes/signer/{signer_token}/status/"
Returns envelope_status, is_terminal, the polling signer's own recipient status, signing_complete, signing_failed, a documents_progress rollup ({ total, completed, in_progress, failed }), and next_poll_seconds. signing_complete is true only once every signed artifact actually exists (a non-empty signed_pdf_node_id on each document) — it is derived from the signed artifacts, never from document_copyback_status, so a terminal but unsigned envelope is NOT signing_complete. next_poll_seconds is 0 only when the envelope is in a terminal state or signing_complete is genuinely true; otherwise it is an adaptive hint (2s for the first 10s after sign, 5s for the next 60s, 15s thereafter). Keep polling while next_poll_seconds is greater than 0, and use a terminal envelope_status as the stop signal: signing_failed indicates a document failed signing (which may be transient — it will retry — or permanent) and does NOT by itself mean polling should stop. The polling-scoped token is multi-call; the original landing token also works until it has been consumed by a one-shot action.
Signer Decline
curl -X POST "https://api.fast.io/current/sign_envelopes/signer/{signer_token}/decline/" \
-H "Content-Type: application/json" \
-d '{"reason": "I do not have authority to sign this document"}'
A single decline cascades the envelope to declined. The decline reason is captured in the audit chain. reason is capped at 1024 bytes.
Document Downloads
The source download and preview endpoints stream the PDF bytes directly (200 application/pdf, Bearer-authed); fetch with the Authorization header and render the resulting blob (the URL cannot be a bare <iframe src> — the navigation carries no auth header). The signed-PDF endpoint streams the signed artifact the same way (200 application/pdf, Bearer-authed, 404 until the document completes):
# Source PDF (the file uploaded at create time)
curl -X GET "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/documents/{document_id}/download/" \
-H "Authorization: Bearer {jwt_token}"
# Source PDF preview (browser-renderable)
curl -X GET "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/documents/{document_id}/preview/" \
-H "Authorization: Bearer {jwt_token}"
# Signed PDF (404 until the document completes)
curl -X GET "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/documents/{document_id}/signed/download/" \
-H "Authorization: Bearer {jwt_token}"
The owner signed-PDF endpoint returns 404 with code 146422 (“Signed PDF is not yet available for this document.”) until the document's signed_at timestamp is populated. (The signer-surface twin uses the generic 1609 instead.) Do NOT read a document's source_node_id through the generic /storage/{node_id}/read/ endpoint — that node lives in the envelope's own storage instance, not the workspace tree, so it returns not-found; always fetch bytes through the envelope endpoints above.
Audit Certificate Download
curl -X GET "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/audit/download/" \
-H "Authorization: Bearer {jwt_token}"
Streams the envelope's audit-certificate bytes directly (JSON evidence record; Bearer-authed). The endpoint returns 404 with code 128301 until the envelope reaches a terminal state (completed, declined, voided, expired, or failed) and the certificate has been rendered and copied back into the envelope's storage instance. The JSON is the machine-verifiable authoritative record; a human-readable Certificate-of-Completion PDF rendition of the same evidence is also produced and downloadable.
Certificate-of-Completion PDF
Alongside the JSON audit certificate, the platform renders a human-readable Certificate-of-Completion PDF — a legally-meaningful summary of the envelope's evidence (recipients, per-stage timestamps, document hashes, consent acceptance) — and copies it into the envelope's own storage instance. It is a first-class download surface on both the owner and signer sides.
# Owner (Bearer-authed)
curl -X GET "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/audit/pdf/download/" \
-H "Authorization: Bearer {jwt_token}"
# Signer (path-token authed — no Fastio session)
curl -X GET "https://api.fast.io/current/sign_envelopes/signer/{signer_token}/audit/pdf/download/"
Both endpoints stream the PDF bytes directly (200, application/pdf, Content-Disposition: attachment); there is no read-token round-trip, and the bytes are fetched server-side from the envelope's storage instance (a audit_certificate_pdf_node_id must NOT be passed to /storage/{node_id}/read/). Availability:
- The certificate PDF exists only once the envelope reaches a terminal state (
completed,declined,voided,expired, orfailed) AND the completion flow has stampedaudit_certificate_pdf_node_idonto the envelope row. - Owner endpoint — returns
404(code121249) while the PDF node is not yet set. - Signer endpoint — returns
1680 (Access Denied)/ HTTP 401 while the envelope is still active (not terminal), and1609 (Not Found)/ HTTP 404 once terminal but the PDF node has not yet been written.1650 (Authentication Invalid)/ HTTP 401 on a bad or expired signer token.
The envelope resource surfaces the PDF's availability through audit_certificate_pdf_available (boolean — true once the rendered PDF exists) and audit_certificate_pdf_node_id (the storage node reference, null until rendered) — mirroring the existing audit_certificate_available / audit_certificate_node_id pair for the JSON certificate. The signer /completed/ response also returns an audit_certificate_pdf block — {"available": true, "download_url": "..."} — alongside the JSON audit_certificate block (both are null until their artifact exists).
Plan Availability
Signing is enabled on every plan; capabilities.signing on the org resource confirms availability. Should an org's plan ever not grant signing, new envelopes cannot start or advance; any in-flight envelopes drain to a terminal state.
Sign Templates
What Sign Templates Are
A sign template (SignTemplate) is a workspace-scoped reusable signing configuration identified by a 30-character sa-prefixed OpaqueId. It captures four things: recipient role slots (who must sign, in what role, with what authentication), document slots (which PDFs are needed and whether they are pinned to specific storage nodes or supplied at instantiation time), field placements (where the signature boxes and other fields sit on each document page in normalized 0…1 coordinates), and envelope policy (default message, reminder cadence, etc.).
Instantiating a template binds each slot to a concrete value and produces a draft SignEnvelope that then follows the normal envelope lifecycle. Templates are soft-deleted (tombstoned) when removed.
Snapshot Structure
The snapshot object has four required top-level keys:
recipient_slots— array. Each slot:slot_key(stable unique string per slot, e.g."signer_1"— referenced byfields[].recipient_slot_keyand by therecipient_bindingsmap at instantiate time),role(signer/cc/viewer/approver/certified_recipient),routing_order(0= parallel; positive = sequential),auth_method(noneoremail_otponly — other methods rejected), optionallabel, optionaldefault_email, optionaldefault_display_name. Sorted byslot_keyin the canonical snapshot. Cap: 100.document_slots— array. Each slot:slot_index(0-based integer, referenced byfields[].document_slot_index), optionallabel, optionalsource_node_id(OpaqueId of the storage node to pin), optionalsource_version_id(required whensource_node_idis present; absent when unpinned). A nullsource_node_idmeans the slot is unresolved and must be supplied at instantiation viadocuments. Cap: 20.fields— array. Each field:recipient_slot_key(matches a recipient slot'sslot_key),document_slot_index,page(1-indexed),x_norm/y_norm/w_norm/h_norm(0…1),type(one of signature / initial / date / text / checkbox / radio / dropdown / attachment / title / company / full_name / email / approve / decline),required(boolean),validation(object or null — key isvalidation, NOTvalidation_json). Cap: 2000. Snapshot size cap: 2 MB.policy— object (serializes as{}when empty, never[]). Keydefault_messagestores the signer message — it is mapped topolicy_json.signer_messageat instantiation time and round-trips asdefault_messageon all template reads.
Resource Shape
List returns identity fields only (id, name, description, version, author_user_id, created_at, updated_at, deleted_at) — snapshot omitted for size. Create, details, update, and instantiate return the full resource including snapshot. Timestamps use 'Y-m-d H:i:s UTC' format.
Endpoints
Create — POST /current/workspace/{workspace_id}/sign_templates/create/
Body: name (required, max 255 chars), optional description (max 1024 chars), snapshot (required, full object). Returns {"sign_template": {...}} with full snapshot. Error codes: 156113 (invalid input), 150581 (pinned doc not found), 146058 (duplicate).
List — GET /current/workspace/{workspace_id}/sign_templates/list/
Query params: offset (default 0), limit (default 50, max 200). Returns {"sign_templates": [...], "total": int, "offset": int, "limit": int}. Each item is identity-only (no snapshot). Error codes: 148805 / 186446 (invalid params).
Details — GET /current/workspace/{workspace_id}/sign_templates/{template_id}/details/
Returns full resource with snapshot. Error codes: 138322 (invalid template id in URI), 129325 (not found).
Update — POST /current/workspace/{workspace_id}/sign_templates/{template_id}/update/
Optimistic CAS. Body: expected_version (required, integer ≥ 1 — the version you fetched), optional name (absent = preserved), optional description (absent = preserved), optional snapshot (always a full replacement when present). Returns full resource. Error codes: 135997 (invalid id in URI), 174911 (expected_version missing/invalid), 147321 (version conflict — HTTP 409; reload and retry), 156706 (invalid input), 194205 (template not found or pinned doc not found).
Delete — POST /current/workspace/{workspace_id}/sign_templates/{template_id}/delete/
Soft-delete only (tombstoned, not purged). Returns {"sign_template_id": "sa...", "deleted": true, "referrer_count": int, "referrers": [...]}. Advisory only — delete is never blocked by referrers. Error codes: 199127 (invalid id in URI), 112441 (not found).
Instantiate — POST /current/workspace/{workspace_id}/sign_templates/{template_id}/instantiate/
Binds slots to concrete values and creates a draft SignEnvelope. Body: recipient_bindings (required — a map keyed by slot_key string; each value carries email, optional display_name, optional auth_method; a list/array is rejected with recipient_bindings_invalid), optional documents (list to supply or override document slots not pinned in the template; each entry: document_slot_index, source_node_id, optional source_version_id), optional name.
Returns {"sign_envelope": {...}, "geometry_mismatch": bool, "geometry_details": {}}. A geometry_mismatch: true is a soft advisory — the draft is returned alongside the flag and is usable, but field placements may not align visually.
Key error codes: 133174 (invalid id in URI), 101088 (template not found), 127674 (recipient slot unbound), 191903 (document slot unresolved), 104351 (duplicate email), 151857 (auth downgrade rejected), 149201 (auth method not in allowlist), 114536 (doc not found in storage), 188340 (snapshot corrupt).
Webhooks
Provider Webhook Receiver
The platform ships a Documenso connector for inbound provider webhooks. The receiver path is:
POST /current/webhooks/sign_envelopes/documenso/{connection_short_id}/
The {connection_short_id} identifies the configured provider connection (opaque short id). The provider signs each request with its own HMAC scheme; the verifier is per-provider and runs before the body is JSON-parsed. After verification the platform normalizes the event into an internal shape, deduplicates against an idempotency key derived from (connection_id, provider_event_id), and enqueues the work for asynchronous processing.
Receiver semantics:
200 OKon accept (including idempotent replay).202 ACCEPTEDwhen the event has been enqueued for processing.401on signature mismatch.404on unknown provider, unknown connection, or disabled connection (the provider sees “unregistered URL” and stops retrying — this is the intended teardown semantics for a revoked connection).405on non-POST.
The raw request body bytes are what the provider signs; do not JSON-parse before verifying. Duplicate deliveries (same (connection_id, provider_event_id) pair) 200-ACK without enqueuing.
Activity Events
Every state transition emits a platform activity-stream event that is observable via the standard Events surface (/current/events/search/). Each transition also publishes to the realtime activity pipeline for live UI updates — see Realtime Activity below.
| Event Type | Emitted When |
|---|---|
sign_envelope_drafted | A draft envelope is created |
sign_envelope_sent | A draft envelope transitions to sent |
sign_envelope_voided | An envelope transitions to voided |
sign_envelope_viewed | A recipient opens the /view landing for the first time (first-view billing fires alongside) |
sign_envelope_recipient_signed | A recipient finishes the /sign flow and the platform commits the recipient transition to signed |
sign_envelope_recipient_declined | A recipient declines via /decline |
sign_envelope_document_signed | The async PAdES pipeline completes a single document's signature embedding and copyback |
sign_envelope_completed | The last document completes and the envelope transitions to completed (audit certificate rendered) |
sign_envelope_expired | An envelope's signing deadline (expires_at) passed while not all required recipients had signed; the envelope is no longer signable and was transitioned to the terminal expired state (outstanding recipients are marked expired and an audit certificate is generated) |
sign_envelope_failed | An envelope could not finish its signing pipeline after every required recipient had signed (a permanent document error, or recovery did not finalize within the bounded window) and was transitioned to the terminal failed state (an audit certificate is generated) |
Audit-chain rows are written for every transition above plus per-recipient viewed, authenticated, consent_accepted, and an internal first_view_billed row. The audit-chain rows are visible inside the audit certificate (JSON) and are not exposed through a separate event-log endpoint at this version.
Realtime Activity
The transitions above also publish to the realtime activity pipeline so a sender can watch an envelope live instead of polling. The generic activity/websocket model (the activity vs enriched event frames, the activity-field nudge, and the GET /current/activity/poll/{profile_id}/ long-poll fallback) is documented in the Events reference. Signing specifics:
- Channels: activity publishes to the envelope's own channel
SIGN_ENVELOPE-{envelope_id}(subscribe to watch a single envelope), AND is dual-published to the parent workspace channelWS-{workspace_id}under thesign_envelopesactivity field, carrying the envelope id as the activity object (subscribe here for a workspace-level dashboard that live-refreshes the affected row from one subscription, mirroring how shares publish toshares). One websocket connection, channel-scoped. - Fields:
details(envelope status changed: drafted / sent / voided / completed / expired),recipients(a recipient viewed / signed / declined, or the roster settled on send / complete / void),documents(a document's signed artifact landed). - Field mapping: drafted →
details; sent / voided / completed →details+recipients; viewed / recipient-signed / recipient-declined →recipients; document-signed →documents+recipients; expired →details. - Reaction: treat any field nudge as “envelope changed → refetch the details endpoint” (it inlines documents / recipients / fields). Signer-side actions (viewed / signed / declined) are performed by an unauthenticated recipient, so
calling_user_idis empty on those frames — attribute via the refetched recipient rows.
Error Responses
Common error codes for the signing category (see the overview reference for the full code table):
Reading the error tables: the four-digit 16xx/17xx values below are HTTP-status classes, not error.code. The error.code a client actually receives is assigned per endpoint, so use the HTTP status as the gate and a documented error.code — five or six digits, plus the 9661-9669 family — only as a refinement. A 16xx value identifies the status class — useful for telling which kind of failure occurred — but comparing one against error.code will never match. Five- and six-digit codes (and the 9661-9669 family) are real error.code values. If you widen a check from a specific code to a status, widen what you assert with it — a status covers failures the narrower code did not, so a message written for that one code becomes a confident falsehood on the rest.
| Error Code | HTTP Status | Cause |
|---|---|---|
1605 (Invalid Input) | 406 | Field validation failure — bad coordinates, missing required field at sign time, malformed payload, consent body hash mismatch |
1609 (Not Found) | 404 | Envelope / document / recipient row not found, or signed PDF / audit certificate requested before the envelope reaches that stage |
1650 (Authentication Invalid) | 401 | Missing / invalid bearer token, signer-surface session-token verification failure, signer token already consumed by a one-shot action, OTP attempts exhausted |
1658 (Not Acceptable) | 406 | Duplicate envelope row rejected |
1671 (Rate Limited) | 429 | Standard rate limit; OTP send/verify throttle is reported through the same error |
1680 (Access Denied) | 401 | Caller lacks the required workspace permission for the action, or a lifecycle transition is not allowed from the current state (editing a non-draft envelope, voiding a terminal envelope, or any other disallowed transition) |
1696 (Credit Limit Exceeded) | 402 | Org has insufficient signing credits for a /send call |
1698 (Unprocessable Entity) | 422 | Schema-level rejection on update / payload structurally invalid |
The signing service surfaces a richer internal taxonomy that maps onto the codes above. The case callers see most often in practice is the idempotent retry: a second /send/ call against an already-sent envelope returns success without re-emitting events or re-charging credits.
Standard rate limits apply. See the overview for the global rate-limit header schema (x-ve-limit-avail, x-ve-limit-max, x-ve-limit-expires) and error code 1671.
Identifier Formats
Signing API responses carry four identifier kinds. Every identifier is returned as a JSON string — never a number.
| Identifier kind | Shape | Fields |
|---|---|---|
| Profile id | 19-digit numeric string (e.g. 4011234567890123456) | envelope_id, workspace_id, org_id, every *_user_id field |
| OpaqueId | 34-character hyphenated base32 string (e.g. 2ltsu-q4mja-cuv7p-gc5yd-lxnsj-wee4) | document_id, recipient_id, signer_identity_id, field_id, source_node_id, signed_pdf_node_id, audit_certificate_node_id, provider_envelope_id, connection_short_id, origin_step_occurrence_id |
| Sign template id | 30-character base32 string with the sa family prefix (e.g. sa…) | template_id, sign_template_id on instantiate output |
| Signer token | Compact JWT carried in the URL path | {token} placeholder above — never persist a signer token longer than the envelope's lifetime, and treat it as a single-use bearer for the consume-style actions (/sign, /decline, /authenticate POST) |
OpaqueIds are also accepted in an un-hyphenated 29-character form — the router strips hyphens before validation, so a 34-char 2ltsu-q4mja-cuv7p-gc5yd-lxnsj-wee4 and a 29-char 2ltsuq4mjacuv7pgc5ydlxnsjwee4 route identically.
Notes
- All timestamps in the API are
'Y-m-d H:i:s UTC'(e.g.2026-05-23 17:42:18 UTC). - Every envelope is parented to a Workspace. Access is gated by workspace membership.
- A draft envelope is mutable via the
/update/endpoint (POST or PATCH); a Sent / InProgress / terminal envelope is immutable on the sender surface (the only transitions are through/voidor the signer-surface actions). - The hard cap on documents per envelope is twenty. Exceeding it is rejected at create time with
1605 (Invalid Input). /send/reserves credits and the first-view billing meter fires the first time any recipient opens the/viewlanding. Voiding does not refund.- Audit certificates render asynchronously after the envelope reaches ANY terminal state (completed, declined, voided, expired, or failed) — not just completion; the owner
/audit/download/endpoint returns404code128301until the certificate is in place. Callers polling for completion can drive on theaudit_certificate_node_idfield on the envelope resource — it goes non-null when the certificate is ready. - Signer tokens are short-lived JWTs bound to a single
(envelope_id, recipient_id). Re-issuing a token is the OTP-elevation path; outside that, a token issued for one recipient cannot be used to act for any other recipient. - A single decline cascades the envelope to
declined. Pending recipients in later routing slots never get notified once the envelope cascades. - The platform's internal PAdES-LT engine is the default signing path. A pluggable provider system is in place for future integrations; only the Documenso webhook receiver path is wired in this release.
- Per-endpoint rate-limit numbers are not published. Throttle-aware clients read the standard
x-ve-limit-avail/x-ve-limit-max/x-ve-limit-expiresresponse headers and back off on a1671response.