Envelopes & recipients
An envelope is one contract on its way to being signed: a template version rendered with your variables, the
recipients who act on it, and the evidence of everything that happens. Create one with POST /v1/envelopes.
Creating an envelope
Section titled “Creating an envelope”| Field | Type | Notes |
|---|---|---|
template |
tpl_… |
Required |
version |
number or "latest" |
Default latest; resolved and pinned at creation. Must be published |
variables |
object | Validated against the version’s JSON Schema |
recipients |
array | One per role (role, name, email; optional externalUserId, verification, fields prefill by field id, order to change the signing order) |
subject, message |
string | Used in the signing emails and shown on the signing page |
delivery |
email · embedded · none |
Default email |
expiresAt |
RFC 3339 | Must be in the future; the envelope expires then, and signing and viewer links stop working |
cc |
array | Extra CC recipients (name, email) beyond the template’s CC roles; up to 50 |
viewers |
array | People who follow the envelope without signing (name, email, optional notifyOnSignature); limited per envelope by plan |
viewerDocumentAccess |
boolean | Let viewers open the document on their status page. Default false (status only); Team plan and up |
reminders |
object | { "enabled": false } turns reminders off; { "enabled": true, "intervalHours": 24 } changes the interval (1 to 720) |
externalId |
string | Your own id, up to 200 characters |
dedupeOn |
"external_id" |
Return the existing envelope with this externalId instead of creating another |
metadata |
object of strings | Up to 500 characters per value; returned on the envelope and in webhooks |
Every recipient needs a role of the version; each non-CC role needs exactly one recipient. Errors are reported
together, with pointers such as /recipients/1/role or /recipients/0/fields/start_date.
Responses: 201 with the envelope and a Location header; 200 with the existing envelope and
x-deduplicated: true when dedupeOn matched; 200 with a dry-run summary for ?dry_run=true; 402 when a
Free workspace has used its included envelopes; 403 plan-upgrade-required when the viewers exceed the plan’s
allowance or viewerDocumentAccess is not on the plan (Plans & limits). Dry runs check the
plan limits too.
Every field added after the first release is optional: a request without them behaves exactly as before.
Send-time options
Section titled “Send-time options”The template defines who signs in which order. For one envelope you can change that without touching the template:
{ "template": "tpl_9fK2mQ7xLp", "variables": { "client": { "name": "Jane Doe" } }, "recipients": [ { "role": "client", "name": "Jane Doe", "email": "jane@acme.dev", "order": 2 }, { "role": "company", "name": "Sam Lee", "email": "sam@bonafide.dev", "order": 1 }, { "role": "witness", "name": "Kim Park", "email": "kim@acme.dev", "order": 1 } ], "cc": [{ "name": "Legal", "email": "legal@acme.dev" }], "viewers": [{ "name": "Alex Chen", "email": "alex@acme.dev", "notifyOnSignature": true }], "reminders": { "enabled": true, "intervalHours": 48 }, "expiresAt": "2026-11-30T23:59:59Z"}- Order:
recipients[].orderis the step the recipient signs in; recipients with the same number sign at the same time. Without it, the role’sorderfrom the template applies. Orders are positive integers; CC roles can’t be ordered; every role still needs its recipient (so the template’s fields keep their signer); at least one recipient must sign. Theenvelope.createdevent recordsroutingOverridden: true. - CC: extra CC recipients get the completed-document email and never sign. They appear in
recipientswithkind: "cc"and the role_cc. - Reminders: who has a turn is reminded every
intervalHours(default: the deployment’sREMINDER_INTERVAL_HOURS, 72), at most three times;enabled: falsesends none. Reminders apply toemaildelivery. - Signer authentication is per recipient:
verificationoverrides the role’s method. Email codes (email_otp) are available today;passkeyandid_checkare rejected as not available yet.
The dashboard’s send screen offers the same options: move steps earlier or later, tick Sign at the same time, add CCs and viewers, and set an expiry date and a reminder schedule.
Delivery
Section titled “Delivery”email: each recipient is emailed a link to the hosted signing page when it is their turn. Recipients who have not signed get a reminder every few days (up to three), and everyone is emailed when the envelope completes.embedded: no email to signers. Mint a signing session when the signer is in your product. Viewers are still emailed their status link.none: no email at all (viewers included); use it when you deliver links yourself (hosted sessions).
POST /v1/envelopes/{id}/recipients/{recipient}/resend emails a fresh link to a recipient whose turn it is (at most
once a minute).
Lifecycle
Section titled “Lifecycle” ┌──────────── declined (a signer declined) │sent ─────┼──────────── voided (POST /v1/envelopes/{id}/void) │ ├──────────── expired (expiresAt passed) │ └──────────── completed ──► seal: pending ──► sealedstatus |
Meaning |
|---|---|
rendering |
Being prepared |
sent |
In progress: recipients are signing in routing order |
completed |
Every recipient finished. Sealing starts |
declined |
A recipient declined; the envelope ends |
voided |
You voided it ({ "reason": "…" }); open signing sessions stop working |
expired |
expiresAt passed before completion |
seal tracks the sealed PDF: not_started, pending (completed, being sealed),
sealed, or failed. Sealing never blocks completion: the envelope completes first and is sealed right after.
Recipients and routing
Section titled “Recipients and routing”{ "id": "rcp_5Jw0pLs9Xe", "role": "client", "name": "Jane Doe", "email": "jane@acme.dev", "order": 1, "status": "viewed", "verification": "email_otp", "completedAt": null}Recipient status is pending (not their turn yet), sent, viewed, completed or declined, and kind is
the role’s signer, approver, form_filler or cc. Routing is by order (the role’s, or the
send-time override): all recipients of the lowest unfinished order act in parallel, and the
next group starts when they are done. CC recipients never block routing.
Viewers
Section titled “Viewers”A viewer follows an envelope without signing: a manager, a paralegal, the account owner on the customer’s side. Viewers are not recipients. They have no role and no turn, never block or complete anything, and never appear on the certificate’s list of signers.
- When the envelope is sent (or when you add them later with
POST /v1/envelopes/{id}/viewers), each viewer gets a branded email with a link to a read-only status page: the envelope title, the sender, every step with its recipients’ names, roles, status and timestamps, live as people sign. Recipients’ email addresses are not shown. - Viewers are emailed again when the envelope is completed, declined or voided, and after every signature if
notifyOnSignatureistrue(default off). - The document is only on the page when you set
viewerDocumentAccess: true(Team plan and up): the original while in progress, the sealed PDF once sealed. Otherwise viewers see the status only. DELETE /v1/envelopes/{id}/viewers/{viewer}revokes a viewer: every link they were sent stops working at once.- Links are personal capability URLs. Only a SHA-256 of each link token is stored. A link works until the envelope expires (at most 90 days), for 30 days after the envelope finished, and never once the viewer is revoked.
- Adding, opening (first view of the page and of the document) and revoking are recorded in the audit trail as
viewer.added,viewer.viewed,viewer.document_viewedandviewer.revoked, and so on the certificate.
The single-envelope responses (create, get, void) list them:
{ "viewers": [ { "id": "vwr_2Hc8sQ1nVt", "name": "Alex Chen", "email": "alex@acme.dev", "notifyOnSignature": true, "status": "active", "invitedAt": "2026-10-13T13:58:04.512Z", "firstViewedAt": "2026-10-13T14:05:41.090Z", "lastViewedAt": "2026-10-13T15:12:09.337Z", "revokedAt": null, "createdAt": "2026-10-13T13:58:04.512Z" } ], "viewerDocumentAccess": false}The number of viewers per envelope depends on the plan (Free 1, Pay-as-you-go 2, Team 5, Business 20, Platform unlimited); CC recipients don’t count. Revoked viewers free their place.
Documents
Section titled “Documents”GET /v1/envelopes/{id}/document?version=… downloads a PDF, with its SHA-256 in x-document-sha256:
version |
File |
|---|---|
original (default) |
The rendered document as sent for signature |
sealed |
The final PDF: values flattened, certificate of completion appended, document timestamp. After envelope.sealed |
certificate |
The certificate of completion on its own |
The envelope’s documentHash is the SHA-256 of the original and sealedDocumentHash the one of the sealed file;
those are what verification looks up.
Audit trail
Section titled “Audit trail”Every action is an event in a per-envelope hash chain: hash = SHA-256(prevHash ‖ canonical JSON of the event), the first prevHash being 64 zeros. GET /v1/envelopes/{id}/events returns the events and checks the
chain:
{ "data": [ { "id": "evt_…", "envelope": "env_8Gm2kQ4rT1", "seq": 1, "type": "envelope.created", "createdAt": "2026-10-13T13:58:04.512Z", "recipient": null, "actor": { "kind": "api_key", "id": "…", "name": null, "email": null }, "ip": "203.0.113.7", "userAgent": null, "data": { "template": "tpl_…", "templateVersion": 3, "documentHash": "9f2c…" }, "hash": "4b1d…", "prevHash": "0000000000000000000000000000000000000000000000000000000000000000" } ], "chain": { "valid": true, "brokenAt": null }}Besides the webhook event types, the trail has audit-only entries such as
session.created, recipient.verified, recipient.reminded and the viewer events (actor kind
viewer for a viewer opening their page).
For live status in a UI, GET /v1/envelopes/{id}/events/stream is a server-sent event stream: each frame’s event
name is the event type and its id the seq; reconnect with Last-Event-ID to resume.
Listing
Section titled “Listing”GET /v1/envelopes filters by status, seal, template, and q (free text over recipient names and emails,
externalId and the envelope id). Like every list it is cursor-paginated:
{ "data": [ … ], "hasMore": true, "nextCursor": "…" }Pass nextCursor back as cursor; limit is 1 to 100 (default 20).