Skip to content

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.

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.

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[].order is the step the recipient signs in; recipients with the same number sign at the same time. Without it, the role’s order from 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. The envelope.created event records routingOverridden: true.
  • CC: extra CC recipients get the completed-document email and never sign. They appear in recipients with kind: "cc" and the role _cc.
  • Reminders: who has a turn is reminded every intervalHours (default: the deployment’s REMINDER_INTERVAL_HOURS, 72), at most three times; enabled: false sends none. Reminders apply to email delivery.
  • Signer authentication is per recipient: verification overrides the role’s method. Email codes (email_otp) are available today; passkey and id_check are 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.

  • 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).

┌──────────── declined (a signer declined)
│
sent ─────┼──────────── voided (POST /v1/envelopes/{id}/void)
│
├──────────── expired (expiresAt passed)
│
└──────────── completed ──► seal: pending ──► sealed
status 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.

{
"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.

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 notifyOnSignature is true (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_viewed and viewer.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.

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.

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.

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).