AnySign API
Invoicing and e-signatures over plain HTTPS + JSON. Everything the dashboard does, your code can do — it's the same API. Money is integer cents. Field coordinates are fractions of the page (x from the left, y from the top). Base URL: https://api.anysignhq.com
OpenAPI spec
Everything on this page comes from one OpenAPI 3.1 document, and it is served as-is at /openapi.yaml. Import it into Postman or Insomnia, or generate a client from it — you do not need the repository.
A test keeps the two honest: an endpoint cannot appear in the spec without appearing here, or here without existing in the spec.
Download the spec# grab it from this site
curl -O <this-site>/openapi.yaml
# generate a typed client from it
npx @openapitools/openapi-generator-cli generate \
-i openapi.yaml -g typescript-fetch -o ./anysign-client
# or skip codegen — maintained SDKs ship for
# TypeScript, Python, Ruby, PHP, Java and .NET
npm install @anysign/sdkAuthentication
Authenticate /v1 requests with a bearer API key. Live keys start with sk_live_; test keys with sk_test_ and never send real email. Create and revoke keys in Settings → API keys.
A key carries exactly the scopes you tick when you create it — envelopes:read, org:admin and so on. Tick nothing for a full-access key. Either way, keep them server-side, never in a browser.
curl https://api.anysignhq.com/v1/invoices \
-H "Authorization: Bearer sk_live_..."Errors & idempotency
Errors return conventional status codes with a JSON body: { error: { code, message } }. 4xx means the request needs fixing; 5xx means we do.
Every POST accepts an Idempotency-Key header — retry a timed-out create safely and you'll get the original result instead of a duplicate.
HTTP/1.1 422 Unprocessable Entity
{
"error": {
"code": "invalid_field",
"message": "line_items[0].unit_cents must be a positive integer"
}
}Versioning
Send Anysign-Version to pin the behaviour you built against. Leave it off and you get the current version.
Every response echoes the version it was served under, so you can assert on it. A version we do not know is refused with a 400 rather than quietly served under different rules than you expect.
Breaking changes arrive as a new dated version; the one you pinned keeps working.
curl https://api.anysignhq.com/v1/envelopes \
-H "Authorization: Bearer sk_live_..." \
-H "Anysign-Version: 2026-08-14"
# every response carries it back
Anysign-Version: 2026-08-14SDKs and Zapier
Every SDK is a thin, dependency-free wrapper over the same /v1 endpoints documented here — same resources, same idempotency, same errors. Nothing is available through an SDK that is not available over plain HTTPS.
Prefer no code? The Zapier app covers the events most workflows hang off: triggers for envelope completed, declined, approved and paid, recipient reassigned, invoice paid and new customer, and actions to create an invoice, create an envelope (optionally from a template) or tag one. It uses your API key and pins Anysign-Version, so a future breaking change cannot alter what a published Zap receives.
During the beta the Zapier app is shared privately — email [email protected] and we will add you.
npm install @anysign/sdk # TypeScript / JavaScript
pip install anysign # Python
gem install anysign # Ruby
composer require anysign/sdk # PHP
# Maven: dev.anysign:anysign # Java
dotnet add package AnySign # .NETGuide · Create and send an invoice
One call creates the invoice, computes totals server-side, assigns the number and emails the hosted view/pay link (send: true).
curl https://api.anysignhq.com/v1/invoices \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: inv-2026-07-0042" \
-d '{
"customer_id": "cus_9d2f1c",
"currency": "USD",
"due_date": "2026-07-27",
"tax_percent": 8.75,
"line_items": [
{ "description": "Brand identity — phase 2",
"quantity": 1, "unit_cents": 480000 },
{ "description": "Design system retainer",
"quantity": 2, "unit_cents": 110000 }
],
"send": true
}'const res = await fetch("https://api.anysignhq.com/v1/invoices", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANYSIGN_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "inv-2026-07-0042",
},
body: JSON.stringify({
customer_id: "cus_9d2f1c",
currency: "USD",
due_date: "2026-07-27",
tax_percent: 8.75,
line_items: [
{ description: "Brand identity — phase 2", quantity: 1, unit_cents: 480_000 },
{ description: "Design system retainer", quantity: 2, unit_cents: 110_000 },
],
send: true, // create + send in one call
}),
});
const invoice = await res.json();
console.log(invoice.number, invoice.hosted_url);Guide · Send an envelope for signature
Create the envelope with documents (base64), signers and fields in a single call — at create time, signer_id and document_id accept index strings ("0", "1", …). Then send.
# 1. create — documents, signers, fields in one call.
# signer_id / document_id take index strings at create.
curl https://api.anysignhq.com/v1/envelopes \
-H "Authorization: Bearer sk_live_..." \
-d '{
"title": "Consulting agreement",
"signing_order": "sequential",
"signers": [
{ "name": "Ada Chen", "email": "[email protected]", "order": 1 },
{ "name": "R. Okafor", "email": "[email protected]", "order": 2 }
],
"documents": [
{ "name": "agreement.pdf", "content_base64": "'$(base64 -i agreement.pdf)'" }
],
"fields": [
{ "document_id": "0", "signer_id": "0", "kind": "signature",
"page": 4, "x": 0.12, "y": 0.68, "w": 0.22, "h": 0.055 },
{ "document_id": "0", "signer_id": "0", "kind": "date",
"page": 4, "x": 0.12, "y": 0.75 },
{ "document_id": "0", "signer_id": "1", "kind": "signature",
"page": 4, "x": 0.58, "y": 0.68, "w": 0.22, "h": 0.055 }
]
}'
# 2. send — emails tokenized signing links
curl -X POST https://api.anysignhq.com/v1/envelopes/env_8f14e2/send \
-H "Authorization: Bearer sk_live_..."import { readFile } from "node:fs/promises";
const pdf = (await readFile("agreement.pdf")).toString("base64");
const create = await fetch("https://api.anysignhq.com/v1/envelopes", {
method: "POST",
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
body: JSON.stringify({
title: "Consulting agreement",
signing_order: "sequential",
signers: [
{ name: "Ada Chen", email: "[email protected]", order: 1 },
{ name: "R. Okafor", email: "[email protected]", order: 2 },
],
documents: [{ name: "agreement.pdf", content_base64: pdf }],
// index strings resolve to the records created above
fields: [
{ document_id: "0", signer_id: "0", kind: "signature",
page: 4, x: 0.12, y: 0.68, w: 0.22, h: 0.055 },
{ document_id: "0", signer_id: "1", kind: "signature",
page: 4, x: 0.58, y: 0.68, w: 0.22, h: 0.055 },
],
}),
});
const envelope = await create.json();
await fetch(`https://api.anysignhq.com/v1/envelopes/${envelope.id}/send`, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
});Guide · Embedded signing sessions
Keep signers inside your product: mint a short-lived signing URL for a specific signer and redirect (or iframe) them to it. Links live 15 minutes and work once.
curl https://api.anysignhq.com/v1/envelopes/env_8f14e2/signing_sessions \
-H "Authorization: Bearer sk_live_..." \
-d '{
"signer_id": "sgn_1a9b3c",
"return_url": "https://yourapp.com/deals/42?signed=1"
}'
# → { "url": "https://vault.anysignhq.com/sign/tok_…", "expires_at": "…" }
# valid 15 minutes, single use — redirect or iframe your signer thereconst res = await fetch(
`https://api.anysignhq.com/v1/envelopes/${envelopeId}/signing_sessions`,
{
method: "POST",
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
body: JSON.stringify({
signer_id: signerId,
return_url: "https://yourapp.com/deals/42?signed=1",
}),
},
);
const { url, expires_at } = await res.json();
// valid 15 minutes, single use
redirect(url);Guide · Verify webhook signatures
Deliveries are signed with HMAC-SHA256 over <timestamp>.<raw body> using your whsec_ secret. Verify in constant time and reject stale timestamps.
# Deliveries look like:
POST /hooks/anysign HTTP/1.1
Anysign-Signature: t=1760358000,v1=5f8a2c…
Content-Type: application/json
{ "type": "envelope.completed", "data": { "id": "env_8f14e2", … } }
# Recompute HMAC-SHA256 over "<t>.<raw body>" with your whsec_ secret,
# compare to v1 in constant time, and reject timestamps older than 5 min.import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyAnysignSignature(
payload: string, // raw request body
header: string, // Anysign-Signature: t=<unix>,v1=<hex>
secret: string, // whsec_…
toleranceSeconds = 300,
): boolean {
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("=") as [string, string]),
);
const t = Number(parts.t);
if (!parts.v1 || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) {
return false; // stale or malformed — reject replays
}
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${payload}`)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}Customers
The people and companies you bill and sign with. Customers are referenced by invoices via customer_id.
POST/v1/customersCreate customer
| Parameter | Type | Description |
|---|---|---|
| name* | string | Display name. |
| string | Where invoices and signing requests are sent. | |
| address | string | Billing address, shown on invoices. |
| tax_id | string | Tax identifier printed on invoices. |
| metadata | object | Arbitrary key–value data. |
↳ 201 · Customer
GET/v1/customersList customers
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Page size, default 25. |
| starting_after | string | Cursor — last customer id of the previous page. |
↳ 200 · { data: Customer[], has_more }
GET/v1/customers/{id}Get customer
No body parameters.
↳ 200 · Customer
Invoices
Money is always integer cents — no floats, ever. Create as draft, then send; sending assigns the invoice number and emails your customer a hosted view/pay link.
POST/v1/invoicesCreate invoice (draft, or send immediately with send=true)
| Parameter | Type | Description |
|---|---|---|
| customer_id* | string | Who to bill. |
| currency | string | ISO 4217, default USD. |
| line_items | LineItem[] | { description, quantity, unit_cents } each. |
| tax_percent | number | Applied after discount. |
| discount_percent | number | Applied to the subtotal. |
| issue_date / due_date | date | YYYY-MM-DD. |
| notes | string | Printed at the bottom of the invoice. |
| send | boolean | Send immediately after create. |
↳ 201 · Invoice
GET/v1/invoicesList invoices
| Parameter | Type | Description |
|---|---|---|
| status | enum | draft · sent · viewed · partially_paid · paid · overdue · void |
| limit / starting_after | — | Cursor pagination. |
↳ 200 · { data: Invoice[], has_more }
GET/v1/invoices/{id}Get invoice
No body parameters.
↳ 200 · Invoice
PATCH/v1/invoices/{id}Update draft invoice
No body parameters.
↳ 200 · Invoice
POST/v1/invoices/{id}/sendAssign number, email customer the hosted view/pay link
No body parameters.
↳ 200 · Invoice
POST/v1/invoices/{id}/voidVoid invoice
No body parameters.
↳ 200 · Invoice
GET/v1/invoices/{id}/pdfDownload rendered PDF
No body parameters.
↳ 200 · application/pdf
POST/v1/recurring_schedulesCreate recurring schedule — instantiates and sends its template invoice each interval
| Parameter | Type | Description |
|---|---|---|
| customer_id* | string | Who to bill. |
| template* | InvoiceInput | The invoice to instantiate each run. |
| interval_unit | enum | week · month (default) · year. |
| interval_count | integer | Every N units, default 1. |
| start_at | date | First run; RFC 3339 or YYYY-MM-DD, default now. |
↳ 201 · RecurringSchedule
GET/v1/recurring_schedulesList recurring schedules
No body parameters.
↳ 200 · { data }
GET/v1/recurring_schedules/{id}Get recurring schedule
No body parameters.
↳ 200 · RecurringSchedule
PATCH/v1/recurring_schedules/{id}Update schedule — pause/resume via active
No body parameters.
↳ 200 · RecurringSchedule
DELETE/v1/recurring_schedules/{id}Delete schedule
No body parameters.
↳ 204
Envelopes
An envelope is documents + signers + fields. Field coordinates are fractions of the page: x from the left, y from the TOP, w/h as fractions, page is 1-based. At create time, signer_id and document_id accept index strings ("0", "1", …) that resolve to the created records. Field kinds: signature · initials · date · text · checkbox · dropdown · radio · attachment. Give a field a name to reference it from another field's show_if_name (show it only when that field holds a value) or formula (arithmetic, e.g. "quantity * unit_price"). Formulas and read_only fields are filled by us, never by the signer.
POST/v1/envelopesCreate envelope — documents (base64), signers and fields in one call
| Parameter | Type | Description |
|---|---|---|
| title* | string | Subject of the signing request. |
| signers* | Signer[] | { name, email, phone?, role?, order, access_code?, delivery? } each. role is signer (default), cc, approver or viewer; delivery in_person is never emailed a link. |
| documents | Document[] | { name, content_base64 } — PDF or DOCX, or upload separately. |
| fields | Field[] | { document_id, signer_id, kind, name?, page, x, y, w, h, required, options?, read_only?, value?, show_if_name?, show_if_value?, formula? }. |
| tags | string[] | Free-form labels. These are the folders — an envelope can be in several at once. |
| allow_reassign | boolean | May a recipient hand their responsibility to someone else? Default true. |
| payment | Payment | { amount_cents, currency?, description? } — collect a card in Stripe Checkout before any signature is accepted. |
| remind_every_days | integer | Auto-remind pending signers every N days; 0 disables. |
| signing_order | enum | parallel (default) or sequential. |
| signature_level | enum | SES (default). AES, which verifies each signer via SMS code, is coming soon on Business and is rejected today. |
| language | enum | en (default) · de · fr · es — signer emails + signing page. |
| template_id | string | Envelope template to merge defaults from. |
| expires_in_days | integer | Default 30. |
↳ 201 · Envelope
GET/v1/envelopesList envelopes
| Parameter | Type | Description |
|---|---|---|
| status | enum | draft · sent · completed · declined · voided · expired. |
| q | string | Free-text over titles and signer names/emails. |
| tag | string | Only envelopes carrying this tag. |
| limit | integer | Default 25, max 100. |
| starting_after | string | Cursor — the last id from the previous page. |
↳ 200 · { data, has_more }
GET/v1/envelopes/{id}Get envelope with signers, documents, fields
No body parameters.
↳ 200 · Envelope
POST/v1/envelopes/{id}/documentsUpload a PDF to a draft envelope (multipart or JSON base64)
No body parameters.
↳ 201 · EnvelopeDocument
POST/v1/envelopes/{id}/sendSend — emails tokenized signing links per signing order
No body parameters.
↳ 200 · Envelope
POST/v1/envelopes/{id}/voidVoid envelope
No body parameters.
↳ 200 · Envelope
POST/v1/envelopes/{id}/remindRe-send signing links to pending signers
No body parameters.
↳ 204
POST/v1/envelopes/{id}/signing_sessionsCreate an embedded signing URL for a signer (15 min, single use)
| Parameter | Type | Description |
|---|---|---|
| signer_id* | string | Which signer the session is for. |
| return_url | string | Where to send the signer afterwards. |
↳ 201 · { url, expires_at }
POST/v1/envelopes/{id}/in_person_sessionsStart in-person signing on the host's own device (every plan, never emailed)
| Parameter | Type | Description |
|---|---|---|
| signer_id* | string | The signer standing in front of you. |
↳ 201 · { url, return_code, signer_name, expires_at }
POST/v1/envelopes/{id}/sender_sessionsEmbedded sender — a single-use URL to place fields and send from your own UI
No body parameters.
↳ 201 · { url, envelope_id, expires_at }
PUT/v1/envelopes/{id}/tagsReplace an envelope's tags
No body parameters.
↳ 200 · Envelope
GET/v1/envelope_tagsEvery tag in use, with counts
No body parameters.
↳ 200 · { data: TagCount[] }
GET/v1/field_kindsThe published field-type catalogue
No body parameters.
↳ 200 · { data: string[] }
GET/v1/envelopes/{id}/fields/{field_id}/attachmentDownload a file a signer uploaded into an attachment field
No body parameters.
↳ 200 · application/octet-stream
GET/v1/exports/envelopesZip of executed documents, certificates and manifests
| Parameter | Type | Description |
|---|---|---|
| status | enum | Narrow the archive to one status. |
| tag | string | Narrow the archive to one tag. |
| include_audit | boolean | Add audit-trail.json per envelope. |
↳ 200 · application/zip
GET/v1/envelope_tagsEvery tag in use, with envelope counts
No body parameters.
↳ 200 · { data: TagCount[] }
GET/v1/field_kindsThe published field-type catalogue
No body parameters.
↳ 200 · { data: string[] }
GET/v1/reports/envelopesEnvelope analytics — counts, completion rate, time to complete, per sender
| Parameter | Type | Description |
|---|---|---|
| from | date | Sent-at lower bound; defaults to 30 days ago. |
| to | date | Inclusive upper bound; defaults to now. |
| format | enum | csv returns the rows instead of the summary. |
↳ 200 · { sent, completed, declined, expired, voided, completion_rate, … } or text/csv
GET/v1/envelopes/{id}/audit_trailHash-chained audit trail
No body parameters.
↳ 200 · { data: AuditEvent[], chain_valid }
GET/v1/envelopes/{id}/documents/{doc_id}/downloadDownload executed (sealed), original, or certificate PDF
| Parameter | Type | Description |
|---|---|---|
| variant | enum | executed (default) · original · certificate |
↳ 200 · application/pdf
Templates
Reusable envelope field layouts or invoice defaults.
POST/v1/templatesCreate template
| Parameter | Type | Description |
|---|---|---|
| kind* | enum | envelope or invoice. |
| name* | string | Display name. |
| layout* | object | The reusable layout payload. |
↳ 201 · Template
GET/v1/templatesList templates (filter with ?kind=)
No body parameters.
↳ 200 · { data: Template[] }
GET/v1/templates/{id}Get template
No body parameters.
↳ 200 · Template
DELETE/v1/templates/{id}Delete template
No body parameters.
↳ 204
Bulk send
One envelope template plus a recipient list becomes one individual envelope per recipient — each recipient is the only signer on their own envelope and never sees the others. Business plan only; other workspaces get 402 plan_required. Maximum 500 recipients per batch. The whole list is validated before anything is sent: if any row is unusable the request returns 400 with a details array and nothing goes out. Fan-out is queued (one job per recipient) and each envelope consumes envelope quota exactly like a normal send.
POST/v1/bulk_sendsCreate and queue a bulk send
| Parameter | Type | Description |
|---|---|---|
| template_id* | string | An envelope template — its fields and signing settings are snapshotted. |
| title | string | Overrides the template. Use {{column}} to personalise per recipient. |
| message | string | Overrides the template; also supports {{column}}. |
| documents | object[] | { name, content_base64 } — the PDFs every recipient signs. |
| rows | object[] | { name, email, phone?, access_code?, merge? } per recipient. |
| csv | string | CSV alternative to rows. Needs "name" and "email" columns; any other column becomes a merge value. Handles BOM, CRLF and quoted fields. |
↳ 201 · BulkSend · 400 with per-row details · 402 plan_required
GET/v1/bulk_sendsList bulk sends (limit, starting_after)
No body parameters.
↳ 200 · { data, has_more }
GET/v1/bulk_sends/{id}Get a bulk send with per-recipient status
No body parameters.
↳ 200 · BulkSend
POST/v1/bulk_sends/{id}/retryRe-queue failed recipients — already-sent ones are untouched
No body parameters.
↳ 200 · BulkSend · 409 when nothing failed
Webhook endpoints
We POST events to your endpoints, signed with HMAC-SHA256 in the Anysign-Signature header as t=<unix>,v1=<hex>. The whsec_ secret is returned once, on create.
POST/v1/webhook_endpointsCreate endpoint (secret returned once)
| Parameter | Type | Description |
|---|---|---|
| url* | string | HTTPS endpoint to deliver to. |
| events | string[] | Event names to subscribe to; empty = all. |
↳ 201 · WebhookEndpoint (with secret)
GET/v1/webhook_endpointsList endpoints
No body parameters.
↳ 200 · { data }
DELETE/v1/webhook_endpoints/{id}Delete endpoint
No body parameters.
↳ 204
GET/v1/webhook_eventsThe published event catalogue
No body parameters.
↳ 200 · { data: string[] }
GET/v1/webhook_deliveriesRecent delivery attempts, with status codes and errors
| Parameter | Type | Description |
|---|---|---|
| endpoint_id | string | Only deliveries to this endpoint. |
| status | enum | pending · delivered · failed. |
| limit | integer | Default 50, max 200. |
↳ 200 · { data: WebhookDelivery[] }
GET/v1/webhook_deliveries/{id}One delivery, including the exact signed payload
No body parameters.
↳ 200 · WebhookDelivery
POST/v1/webhook_deliveries/{id}/replaySend it again — a new attempt; the original stays in the log
No body parameters.
↳ 202 · WebhookDelivery
Signing links
One public URL anyone can open to sign — waivers, NDAs, onboarding packets. Each visitor identifies themselves and gets their own private envelope, so signed copies never leak to the next person. The document travels with the link; the template supplies the field layout.
POST/v1/signing_linksCreate a reusable public signing link
| Parameter | Type | Description |
|---|---|---|
| template_id* | string | Envelope template the claimed envelope is built from. Its first signer slot is the visitor. |
| content_base64* | string | The PDF or DOCX every visitor signs. |
| document_name | string | Filename shown on the landing page. |
| slug | string | Custom public address; generated when omitted. |
| name | string | What you call it internally. |
| max_uses | integer | 0 = unlimited. |
| expires_at | string | RFC 3339 — the link stops accepting visitors after this. |
↳ 201 · SigningLink (with the public url)
GET/v1/signing_linksList signing links with their use counts
No body parameters.
↳ 200 · { data }
POST/v1/signing_links/{id}/closeStop accepting new signers — envelopes already produced are untouched
No body parameters.
↳ 200 · SigningLink
POST/v1/signing_links/{id}/openReopen a closed link
No body parameters.
↳ 200 · SigningLink
Vault
Store a document once, then hand it out over links that are allowlisted, expiring, revocable and view-logged. Holding the link proves nothing: the recipient also has to be an address you named, and that is re-checked on every request, so revoking or de-allowlisting bites on their next click. It runs inbound too: a document request mints one open link with a named slot per file you want back, closes each slot as it fills, and chases the recipient on a cadence until everything is in. That link is write-only — the person uploading can never read what has already been sent. Both recipients' own endpoints are a browser flow, not an integration surface, and are not part of this API. One call turns a stored document into a draft envelope.
POST/v1/vault/documentsStore a document
| Parameter | Type | Description |
|---|---|---|
| content_base64* | string | The file, up to 20 MB decoded. |
| name | string | Filename shown in the app and on download. |
| thumbnail_base64 | string | Optional page-one preview for the grid, up to 120 KB. PNG, JPEG or WebP only — sniffed from the bytes, so an SVG is refused. Our own uploaders draw it in the browser; omit it and the tile falls back to an icon. |
↳ 201 · VaultDocument (content_type is sniffed from the bytes, never your word for it)
GET/v1/vault/documentsList stored documents
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Page size, default 25. |
| starting_after | string | Cursor — last document id of the previous page. |
↳ 200 · { data: VaultDocument[], has_more }
GET/v1/vault/documents/{id}Get a stored document
No body parameters.
↳ 200 · VaultDocument
DELETE/v1/vault/documents/{id}Delete a document and revoke every link pointing at it
No body parameters.
↳ 204 · the bytes and the thumbnail are destroyed (the blob purge is queued and retried); the audit record and access log are kept
GET/v1/vault/documents/{id}/downloadDownload the stored bytes — always an attachment, always nosniff
No body parameters.
↳ 200 · application/octet-stream (PDF, PNG and JPEG keep their media type)
GET/v1/vault/documents/{id}/thumbnailThe page-one preview, inline and cacheable — 404 when the document has none
No body parameters.
↳ 200 · image/png | image/jpeg | image/webp, ETag on the document hash
POST/v1/vault/documents/{id}/sharesOpen an allowlisted share link
| Parameter | Type | Description |
|---|---|---|
| allow_emails | string[] | Addresses that may open it. |
| allow_domains | string[] | Domains that may open it; matched after the last @, exactly. |
| expires_in_days | integer | 0 = never, maximum 365. |
| name | string | What you call this link internally. |
| nda_text | string | A click-through NDA the recipient acknowledges before the first view. Recorded with their email, IP and browser — an audit line, not an executed agreement. |
| watermark | boolean | Stamp every page of the PDFs this link serves with the viewer's email and the time they opened it. Paid. Non-PDFs are served unchanged; a digitally signed PDF answers 409 rather than being served unmarked. |
↳ 201 · VaultShare with the url · 400 if the allowlist is empty · 402 unless your plan sells viewer watermarking
GET/v1/vault/documents/{id}/sharesList a document's share links
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Page size, default 25. |
| starting_after | string | Cursor — last share id of the previous page. |
↳ 200 · { data: VaultShare[], has_more } — without their urls
POST/v1/vault/documents/{id}/envelopeSend a stored document for signature — creates a draft envelope
| Parameter | Type | Description |
|---|---|---|
| signers | { name, email }[] | Who it is going to. |
| title | string | Defaults to the document's name. |
| message | string | Shown to the signers. |
↳ 201 · { envelope_id, url } — the builder, where the fields get placed · 409 for anything but PDF/Word
POST/v1/vault/sharesOpen one link over a set of documents
| Parameter | Type | Description |
|---|---|---|
| document_ids* | string[] | 1–20 documents, in the order the recipient sees them; the first is the primary. Repeats are collapsed. Only this route takes it — the document-scoped create answers 400. |
| allow_emails | string[] | Addresses that may open it. |
| allow_domains | string[] | Domains that may open it; matched after the last @, exactly. |
| expires_in_days | integer | 0 = never, maximum 365. |
| name | string | What you call this link internally. |
| nda_text | string | A click-through NDA the recipient acknowledges before the first view. Recorded with their email, IP and browser — an audit line, not an executed agreement. |
| watermark | boolean | See POST /v1/vault/documents/{id}/shares. |
↳ 201 · VaultShare with the url · 402 unless your plan sells sharing several documents on one link
GET/v1/vault/sharesEvery link your workspace has sent, newest first
| Parameter | Type | Description |
|---|---|---|
| limit | integer | 1–100, default 25. |
| starting_after | string | A share id from the previous page. |
↳ 200 · { data: VaultShare[], has_more } — each row carries document_name, never the url. Links whose document was deleted still list, revoked.
GET/v1/vault/shares/{id}One share link in full, including its NDA text
No body parameters.
↳ 200 · VaultShare — the list projection carries has_nda only. Every read carries documents[], the link's files with their names and sizes in order.
PATCH/v1/vault/shares/{id}Change a live link's watermark or expiry
| Parameter | Type | Description |
|---|---|---|
| watermark | boolean | Omit to leave it as it is. Turning it on is checked against your plan; turning it off never is. |
| expires_in_days | integer | A new deadline counted from now, 0..365, where 0 is never. Omit to leave the expiry alone — send only the fields you changed, because 0 is a value rather than an absent field. |
↳ 200 · VaultShare · 402 unless your plan sells viewer watermarking · 409 on a revoked or expired link
POST/v1/vault/shares/{id}/documentsAdd documents to a link that is already out — the URL does not change
| Parameter | Type | Description |
|---|---|---|
| document_ids | string[] | Appended after the link's current documents; ids already on it are ignored. The 20-document cap and the plan check apply to the resulting total, so growing a one-document link to two is checked exactly as creating a two-document link is. |
↳ 200 · VaultShare · 402 unless your plan sells multi-document links · 409 on a revoked or expired link
POST/v1/vault/shares/{id}/revokeRevoke a link, killing every grant and code already issued on it
No body parameters.
↳ 200 · VaultShare
POST/v1/vault/shares/{id}/revealShow a share's URL again
No body parameters.
↳ 200 · { url }
PUT/v1/vault/shares/{id}/allowlistReplace who may open a share — takes effect on their next request
| Parameter | Type | Description |
|---|---|---|
| allow_emails | string[] | The complete new list, not a patch. |
| allow_domains | string[] | The complete new list, not a patch. |
↳ 200 · VaultShare
PUT/v1/vault/shares/{id}/ndaSet or clear the click-through NDA a recipient accepts before the first view
| Parameter | Type | Description |
|---|---|---|
| nda_text | string | Up to 20000 characters. Empty removes the click-through; the acceptances already recorded are kept. |
↳ 200 · VaultShare · 402 unless your plan sells the click-through NDA
POST/v1/vault/requestsAsk someone for documents — one open, write-only link
| Parameter | Type | Description |
|---|---|---|
| name* | string | What you are asking for; the recipient sees it. |
| items* | { name, note }[] | 1–20 named slots, one file each. |
| recipient_email | string | Who to send it to and chase. Required if chase_every_days > 0. |
| recipient_name | string | How to address them. |
| message | string | A note shown on the upload page and in the email. |
| expires_in_days | integer | 0 = never, maximum 365. |
| chase_every_days | integer | 0 = never, maximum 30. |
| max_chases | integer | 1–12, default 6 — the reminder loop always ends. |
↳ 201 · VaultRequest with the url · 400 if there are no items or chasing has nobody to chase
GET/v1/vault/requestsList document requests with their received counts
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Page size, default 25. |
| starting_after | string | Cursor — last request id of the previous page. |
↳ 200 · { data: VaultRequest[], has_more } — without their urls
GET/v1/vault/requests/{id}Get a request and each item's status
No body parameters.
↳ 200 · VaultRequest
POST/v1/vault/requests/{id}/closeClose a request — the link stops accepting files and the chase stops
No body parameters.
↳ 200 · VaultRequest
POST/v1/vault/requests/{id}/revealShow a request's URL again
No body parameters.
↳ 200 · { url }
GET/v1/vault/shares/{id}/viewsA share's access log, newest first
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Page size, default 25. |
| starting_after | string | Cursor — last view id of the previous page. |
↳ 200 · { data: VaultShareView[], has_more } — opened · otp_sent · otp_failed · denied · verified · viewed · downloaded
GET/v1/vault/shared-with-meLive links other workspaces opened onto your address, by exact match or your domain
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Page size, default 25, max 100. |
↳ 200 · { data: VaultInboxShare[], has_more } · 403 for an API key, which is not a person, or an unconfirmed address
Compliance status
Which compliance features are live on the deployment you are talking to, and what an operator still has to switch on. Owner-only. Every feature degrades gracefully — nothing reported here ever blocks a signer.
GET/v1/compliance/statusDeployment compliance posture and operator warnings
No body parameters.
↳ 200 · { env, features: [{ key, label, status, message }], warnings, ready_for } · 403 if not an owner