Skip to content
On this page

Developers

Markdown

Envelopes API

Upload files, build a draft envelope, add recipients and fields, preview it, send it, and correct it after sending.

An envelope holds one or more documents and the people who act on them. Its status is draft, sent, completed, voided, declined or expired. All paths below start with /api/v1. To start from a template instead, see the API quick start. Templates themselves can be created, changed and deleted through /templates; see the API reference.

Upload a file

POST /files (ability files:write) takes a multipart file (a PDF; an Office file: doc, docx, odt, rtf, txt, xls, xlsx, ods, ppt, pptx, odp; or an image: jpg, jpeg, png, gif, webp, heic, heif) or a public HTTPS url to a PDF. Office files and images are converted to PDF; each image becomes one Letter page. Optional: password for a protected PDF, use_text_tags (default true) to turn text tags into fields, and import_form_fields: false to use a fillable PDF as a plain document. Otherwise a fillable PDF's own form fields are found and set up; see Fillable PDF forms.

bash
curl -X POST https://esignstack.app/api/v1/files \
  -H "Authorization: Bearer $ESIGNSTACK_KEY" \
  -H "Idempotency-Key: order-456-file" \
  -F file=@agreement.pdf

The file's status is processing, then ready or failed. Check it with GET /files/{file} or wait for the file.ready or file.failed event.

Create a draft

POST /envelopes creates a draft from a ready file:

json
{
  "file": "file_01J…",
  "name": "Acme service agreement",
  "external_id": "order-456",
  "email_subject": "Please sign the Acme agreement",
  "expires_in_days": 30
}

Add folder (an envelope folder ID, fdr_…) to put the draft in a folder. A file that isn't ready yet returns 409 resource_conflict. Change draft settings with PATCH /envelopes/{envelope} and delete a draft with DELETE /envelopes/{envelope}; on an envelope that's already sent, both return 409 resource_conflict. send_emails: false means eSignStack sends no emails for the envelope; your app brings people to sign with signing sessions.

Documents and pages

PUT /envelopes/{envelope}/documents replaces the draft's documents. Each document is built from pages of uploaded files (file) or of documents already in the envelope (document), with optional rotation (0, 90, 180 or 270):

json
{
  "documents": [
    { "name": "Agreement", "pages": [{ "file": "file_01J…", "page": 1 }, { "file": "file_01J…", "page": 2 }] },
    { "name": "Pricing", "pages": [{ "file": "file_01J…", "page": 1 }], "visible_to": ["rcp_01J…"] }
  ]
}

Fields move with their page. Fields on pages you drop are removed. GET /envelopes/{envelope}/pages lists every page in the order fields use.

visible_to limits who sees a document. It's a list of recipient IDs: null means everyone and [] means no one. A recipient's signing copy contains only the documents they can see, and you can't give a recipient a field on a document they can't see. You can also set a recipient's documents (a list of doc_… IDs) from the recipient endpoints.

Recipients

Add, change and remove draft recipients with POST /envelopes/{envelope}/recipients, PATCH …/recipients/{recipient} and DELETE …/recipients/{recipient}.

type Shown in the app as What they do
signer Signer Completes their fields and signs
approver Approver Reviews the documents and clicks Approve or Reject
viewer Needs to acknowledge Reviews the documents and confirms they've read them
cc Gets a copy Gets the completed documents; takes no action

An approval or acknowledgment is recorded in the envelope history and on the certificate. Nothing is added to the PDF. Only signers can have fields.

  • routing_order (1 to 99): recipients with the same number act at the same time; higher numbers wait for lower ones.
  • role: a label such as Client.
  • auth_method: none, access_code (with auth_access_code), sms (with auth_phone) or email_otp. Any method other than none needs the Platform plan.
  • receive_completed_pdf: whether the recipient is emailed the completed documents.
  • allow_delegation: see Reassigning.
  • embedded: see Signing in your app.

Signing in your app

Set embedded: true on a recipient who signs inside your app, for example someone sitting with you, while others on the same envelope sign from their email. It works like DocuSign's clientUserId. eSignStack sends that recipient no invitation, reminder, next-signer or "document updated" email, and the app shows no resend or remind button for them. You open their signing page with a signing session, hosted or embedded.

Everything else stays the same. recipient.activated fires when it's their turn, so in a signing order you know when to open their session. Other webhooks fire as usual. Their completed copy and void or decline notices follow your notification settings, and email or SMS verification codes still go out, since the signer asks for them.

  • An embedded recipient still needs an email address, like any signer.
  • It defaults to false. Set it when you add the recipient, on a draft, or with a correction before that recipient acts. Leaving it out of an update keeps the current value.
  • cc and in_person_signer recipients can't be embedded (422).
  • In POST /templates/{template}/envelopes, set embedded on an entry in recipients.

Fields

POST /envelopes/{envelope}/fields adds a field; PATCH and DELETE on …/fields/{field} change or remove one.

json
{
  "type": "signature",
  "recipient": "rcp_01J…",
  "document": "doc_01J…",
  "page": 1,
  "x": 72, "y": 640, "width": 180, "height": 40,
  "required": true
}

Positions are in PDF points from the top-left corner of the page. page is counted within document; a negative number counts from the end. type is one of signature, initials, date_signed, name, email, company, title, text, number, date, checkbox, checkbox_group, radio_group, dropdown, attachment or repeat_group. Every field needs a recipient, unless you set prefill: true and fill it yourself with value before sending. Other options: label, options (for choice fields), validation and tab_order. See Fields for what each type does.

Fillable PDF forms

An envelope made from a fillable PDF gets the form's own fields. Fields with a value are kept as filled; empty ones go to the first signer once you add one. To choose which fields recipients fill, list them and assign the ones you need. The rest stay as they are in the PDF.

GET /envelopes/{envelope}/form_fields (ability envelopes:read) lists one entry per form field: name, label, type, page and box, value, required, read_only, and status (assigned, kept or unassigned) with its recipient. A field with several boxes, such as a radio button per choice, lists each in widgets. The response also has an acceptance_digest.

PUT /envelopes/{envelope}/form_fields (ability envelopes:write) replaces the choice:

json
{
  "acceptance_digest": "9f2c…",
  "fields": [
    { "name": "FullName", "recipient": "rcp_01J…", "required": true },
    { "name": "Email", "recipient": "rcp_01J…", "label": "Signer email" }
  ]
}
  • Every form field you leave out keeps its current value, and nobody fills it.
  • Read-only form fields always stay as they are.
  • Signature and initials fields can't stay empty. One you leave out keeps its recipient; if it has none, the request is refused.
  • If the form changed since you listed it, the request is refused with acceptance_digest as the param. List it again.

When the list has requires_acceptance: true, the upload was digitally signed or was a converted XFA form, and notices explain what changed. Sending needs the report accepted, and the PUT request accepts it for your key.

Templates work the same way with GET and PUT /templates/{template}/form_fields (abilities templates:read and templates:write), using role instead of recipient.

Preview

POST /envelopes/{envelope}/preview (ability envelopes:write) renders the draft as it is now, watermarked "DRAFT — not for signature". It returns a url that works for 15 minutes. If the draft changes, request a new preview.

To show the preview inside your app, send parent_origin. It must exactly match one of the origins in Settings → Embedding. The response adds preview_session; put preview_session.url in an iframe. Open it within 10 minutes; the read-only session then lasts 15 minutes. The page posts esignstack:preview.ready, esignstack:preview.stale (the draft changed), esignstack:session.expired, esignstack:error and esignstack:resize to your page. It has no signing or editing actions.

Send

POST /envelopes/{envelope}/send (ability envelopes:send) sends the draft. A live send counts toward your plan's API envelope limit.

After sending:

Endpoint Does
POST …/remind Emails a reminder. Optional recipient for one person.
POST …/extend Adds days (1 to 365) to the expiry. notify_pending_signers is optional.
POST …/void Voids the envelope. reason is optional (up to 1000 characters; a generic one is used without it). It goes in the audit log and in the "voided" email to everyone who was invited.
POST …/duplicate Copies the envelope into a new draft.
POST …/move Moves the envelope to folder, or out of its folder with null. Works for any status.
GET …/artifacts Links to the completed PDFs, valid for 5 minutes (ability artifacts:read).
GET …/audit_trail The audit trail as data: the same entries as the certificate (event, actor, ip_address, occurred), in any status.
POST …/archive, POST …/restore Archives or restores an envelope (archived). Void an envelope out for signature first; a legal hold blocks archiving (409).
POST …/legal_hold Places a legal hold (legal_hold). Only an owner or admin can release it, in the app.
POST /envelope_downloads One ZIP with the signed PDFs of up to 50 completed envelopes, behind a 10-minute link (ability artifacts:read).

Archived envelopes are left out of GET /envelopes; GET /envelopes?filter[archived]=true lists only them. They can still be retrieved, with their audit trail. A deleted draft is archived too, and an archived draft is purged after the workspace's deleted-draft retention period. Filter lists with filter[status], filter[external_id], filter[template], filter[created][gte]/filter[created][lte] and filter[metadata][key]=value. | POST …/retry-finalization | Retries a completion that failed (envelope.finalization_failed). |

Corrections

POST /envelopes/{envelope}/corrections (ability envelopes:send) changes an envelope that's out for signature:

  • recipients: change the name, email or authentication of recipients who haven't acted, by id. A new email, phone or authentication method stops their old link, and they get a new one if it's their turn. A name change keeps the link.
  • add_recipients: add recipients to routing stages that haven't started.
  • remove_recipients: remove recipients who haven't acted.
  • documents: a replacement document list, in the same shape as the documents endpoint.
  • fields: a complete replacement field list.
  • note and expected_revision, which rejects the change if the envelope changed since you read it.

A correction can turn embedded on or off for a recipient who hasn't acted. Either way their current link stops working: turned on, they sign only through signing sessions; turned off, eSignStack emails them a new link.

Send documents and fields in separate requests: correct documents first, then fields using the new doc_… IDs. Documents and fields can change only until any recipient has acted. After that the API returns 409 correction_requires_void; void the envelope and send a new one. Content changes keep signing links and signing sessions working: they open the new version, and an action from a page that still shows the old one is refused, so the page reloads with the new version. Everyone consents again, and everyone whose turn it is gets an email with their existing link. Earlier versions stay in the envelope history.

Reassigning

Set allow_delegation: true on a signer, approver or recipient who needs to acknowledge to let them pass their invitation to someone else before acting. It defaults to false, and leaving it out of an update keeps the current setting. Recipients who get a copy can't delegate.

Template roles have the same setting. Envelopes made from a template inherit it, and an explicit allow_delegation on a recipient in POST /templates/{template}/envelopes overrides it.

The recipient reassigns from the signing page (Reassign to someone else). Their link stops working, the new person gets an invitation, and the change is recorded in the envelope history. The new person still has to pass any required authentication.

Test mode

Test keys use every endpoint on this page, including upload, create, send, correct and duplicate, on test data that's kept separate from live data. Test envelopes don't count toward any limit, every page is marked as a test, and emails go only to verified members of the workspace. Sending with SMS verification returns 422. See Sandbox.