API Reference
Complete reference for the zSign REST API. Send documents, manage signing sessions, and integrate webhooks.
Prefer a terminal workflow? Validate, send, track, and test webhooks with the zSign CLI.
Base URL
https://zsign.io/apiAuthentication
Bearer YOUR_API_KEYHealth & Status
API status and health check endpoints
Check API Status
/Verify the API is running.
Request
curl https://zsign.io/api
Response 200
API is running
{"message": "zSign API is running"}
Health Check
/healthGet detailed health status of the API.
Request
curl https://zsign.io/api/health
Response 200
Health status
{"status": "healthy"}
Documents
Upload, send, and manage documents
List Documents
/api/documentsRetrieve documents with optional filtering.
Query Parameters
| Name | Type | Description |
|---|---|---|
offset | integer= 0 | Pagination offset |
limit | integer= 20 | Items per page |
status | string | Filter by status |
recipient | string | Filter by recipient email |
Request
curl -X GET "https://zsign.io/api/documents?limit=20" \-H "Authorization: Bearer YOUR_API_KEY"
Response 200
List of documents
{"items": [{"id": "550e8400-e29b-41d4-a716-446655440000","name": "Contract.pdf","status": "pending","created_at": "2024-01-15T10:30:00Z"}],"total": 1,"offset": 0,"limit": 20}
Upload and Send Document
/api/documents/sendUpload a PDF and send it for signing in one request. PDFs must include field annotations using {type:party:name} syntax (e.g., {signature:signer}, {text:signer:name}). The party must exactly match a recipient's "role" — matching is case-sensitive, so {signature:signer} pairs with "role": "signer", not "Signer". Add * after type for required fields: {type*:party:name}. Supported types: signature, text, date, initials, radio (radio is four parts and visible-text only — see notes below).
Request Body
Content-Type: multipart/form-data
Fields
| Name | Type | Description |
|---|---|---|
filerequired | file | PDF file to upload |
recipientsrequired | string | JSON array of recipients |
name | string | Document name |
send_invite | string ("true"/"false")= true | Omitting it is unchanged behavior. Set false when you deliver the signing link yourself: suppresses zSign's invite email AND reminders for this envelope. signing_urls in the response is unchanged, and completion emails are still sent. Sent as a string (this endpoint is multipart); an unrecognized value 422s. |
send_completion_email | string ("true"/"false")= true | Independent of send_invite. Omitting it is unchanged behavior. Set false when you deliver the completed document to your signers yourself: suppresses zSign's "fully signed" email to every signer for this envelope. Does not affect the notification the envelope owner receives. Sent as a string (this endpoint is multipart); an unrecognized value 422s. |
[{"email": "john@example.com","name": "John Doe","role": "signer"}]
Request
curl -X POST https://zsign.io/api/documents/send \-H "Authorization: Bearer YOUR_API_KEY" \-F "file=@contract.pdf" \-F 'recipients=[{"email":"john@example.com","name":"John Doe","role":"signer"}]'
Response 201
Document sent successfully
{"document_id": "550e8400-e29b-41d4-a716-446655440000","session_id": "660e8400-e29b-41d4-a716-446655440000","signing_urls": [{"recipient_email": "john@example.com","signing_url": "https://zsign.io/sign/eyJ..."}]}
Get Submitted Field Values
/api/v1/documents/{document_id}/fieldsEvery field on a document, with its submitted value once the signer has answered it — the way to read back which radio option a signer chose, or what they typed into a text field. Accepts either the original document id or the completed document id (the one the document.completed webhook carries). Unanswered optional fields report "value": null, not an omitted key. Field values can carry personal data, so this route is API-key/owner scoped only.
Path Parameters
| Name | Type | Description |
|---|---|---|
document_idrequired | string | Either the original document id (from POST /api/v1/documents/send) or the completed document id |
Request
curl -X GET "https://zsign.io/api/v1/documents/{document_id}/fields" \-H "Authorization: Bearer YOUR_API_KEY"
Response 200
Every field defined on the document, with its submitted value
{"document_id": "550e8400-e29b-41d4-a716-446655440000","session_id": "660e8400-e29b-41d4-a716-446655440000","fields": [{"id": "client:term_sheet_option","type": "radio","party": "client","name": "term_sheet_option","group": "term_sheet_option","required": true,"page": 3,"value": "Option 2","submitted_at": "2026-08-19T14:32:00+00:00"},{"id": "client:full_name","type": "text","party": "client","name": "full_name","required": true,"page": 1,"value": null,"submitted_at": null},{"id": "client:signature","type": "signature","party": "client","name": "signature","required": true,"page": 1,"status": "pending","signed_at": null}]}
Signing Sessions
Create and manage signing sessions
Create Session
/api/sessionsCreate a new signing session.
Request Body
Content-Type: application/json
Fields
| Name | Type | Description |
|---|---|---|
document_idrequired | UUID | Document to sign |
document_typerequired | string | template or one_off |
recipientsrequired | array | List of recipients |
{"document_id": "550e8400-e29b-41d4-a716-446655440000","document_type": "one_off","recipients": [{"name": "John Doe","email": "john@example.com","role": "signer","signing_order": 1}]}
Request
curl -X POST https://zsign.io/api/sessions \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"document_id": "550e8400-e29b-41d4-a716-446655440000","document_type": "one_off","recipients": [{"name": "John Doe","email": "john@example.com","role": "signer","signing_order": 1}]}'
Response 201
Session created
{"session_id": "550e8400-e29b-41d4-a716-446655440000","status": "pending","expires_at": "2024-02-14T10:30:00Z","recipients": [{"recipient_id": "770e8400-e29b-41d4-a716-446655440000","signing_url": "https://zsign.io/sign/eyJ..."}]}
Get Session Status
/api/sessions/{session_id}/statusGet lightweight session status.
Path Parameters
| Name | Type | Description |
|---|---|---|
session_idrequired | UUID | Session identifier |
Request
curl -X GET "https://zsign.io/api/sessions/{session_id}/status" \-H "Authorization: Bearer YOUR_API_KEY"
Response 200
Session status
{"session_id": "550e8400-e29b-41d4-a716-446655440000","status": "in_progress","progress": {"completed": 1,"total": 2,"percentage": 50}}
Void Envelope
/api/sessions/{session_id}/voidTerminate a live envelope. Signing links stop working immediately and pending recipients are emailed. The send credit is refunded only if no recipient has viewed, signed, or declined the envelope (declining does not require a prior view, so it can block the refund on its own) and no access has otherwise been recorded against it. Idempotent: voiding an already-voided envelope returns 200, not an error. The API-key equivalent is POST /api/v1/documents/{document_id}/void, which voids the newest session for a document. PATCH /api/sessions/{session_id}/cancel is a deprecated alias for this endpoint.
Path Parameters
| Name | Type | Description |
|---|---|---|
session_idrequired | UUID | Session identifier |
Request Body
Content-Type: application/json
Fields
| Name | Type | Description |
|---|---|---|
reason | string | Free text explaining the void, max 1000 characters |
{"reason": "Contract terms changed"}
Request
curl -X POST "https://zsign.io/api/sessions/{session_id}/void" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"reason": "Contract terms changed"}'
Response 200
Envelope voided
{"session_id": "550e8400-e29b-41d4-a716-446655440000","document_id": "660e8400-e29b-41d4-a716-446655440000","status": "voided","voided_at": "2024-01-15T10:30:00Z","reason": "Contract terms changed","recipients_to_notify": 1,"credit_refunded": true,"credit_refund_skipped": null}
Webhook Event: document.voided
Webhooks
Configure webhook endpoints for real-time notifications. The document.completed payload carries a fields array — every submitted field value, in the same shape as GET /api/v1/documents/{document_id}/fields — additive on top of the existing payload keys.
Create Webhook
/api/webhooksCreate or replace webhook configuration.
Request Body
Content-Type: application/json
Fields
| Name | Type | Description |
|---|---|---|
urlrequired | string | Webhook endpoint URL (HTTPS) |
eventsrequired | array | List of events to subscribe to |
{"url": "https://example.com/webhook","events": ["document.signed","document.completed"]}
Request
curl -X POST https://zsign.io/api/webhooks \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"url": "https://example.com/webhook","events": ["document.signed", "document.completed"]}'
Response 201
Webhook created
{"id": "550e8400-e29b-41d4-a716-446655440000","url": "https://example.com/webhook","events": ["document.signed","document.completed"],"enabled": true,"secret": "whsec_..."}
White-Label Branding
Brand the signer-facing experience, subscribe to White-label ($49/month, 100 envelopes included), manage a custom sender domain, and configure embed origins
Get Branding Settings
/api/brandingRetrieve the current account's branding configuration.
Request
curl -H "Authorization: Bearer YOUR_API_KEY" https://zsign.io/api/branding
Response 200
Branding settings
{"id": "550e8400-e29b-41d4-a716-446655440000","user_id": "660e8400-e29b-41d4-a716-446655440000","company_name": "Acme Corp","logo_url": "https://storage.googleapis.com/zsign-public/branding/.../logo.png","primary_color": "#2563eb","custom_domain": null,"custom_domain_verified": false,"custom_domain_verified_at": null,"custom_domain_target": "zsign-frontend.onrender.com","custom_domain_a_record": "216.24.57.1","badge_removed": false,"embed_origins": ["https://app.acme.com"],"created_at": "2026-01-01T00:00:00Z","updated_at": "2026-01-01T00:00:00Z"}
Update Branding Settings
/api/brandingUpdate company name, logo URL, and accent color shown on signing pages, emails, and the Certificate of Completion.
Request Body
Content-Type: application/json
Fields
| Name | Type | Description |
|---|---|---|
company_name | string | Displayed on signing pages, emails, and the certificate |
logo_url | string | Public logo URL (or use POST /api/branding/logo to upload one) |
primary_color | string | Hex color, e.g. #2563eb, drives the signing page accent |
{"company_name": "Acme Corp","primary_color": "#ff6600"}
Request
curl -X PUT https://zsign.io/api/branding \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"company_name": "Acme Corp", "primary_color": "#ff6600"}'
Response 200
Updated branding settings
{"company_name": "Acme Corp","primary_color": "#ff6600"}
Upload Brand Logo
/api/branding/logoUpload a PNG, JPEG, or WebP logo (max 1MB, magic-byte validated -- no SVG). Stores it in public GCS and saves the URL onto the account's branding.
Request Body
Content-Type: multipart/form-data
Fields
| Name | Type | Description |
|---|---|---|
filerequired | file | PNG, JPEG, or WebP image, max 1MB |
// multipart/form-data with a "file" field
Request
curl -X POST https://zsign.io/api/branding/logo \-H "Authorization: Bearer YOUR_API_KEY" \-F "file=@logo.png"
Response 200
Logo uploaded
{"logo_url": "https://storage.googleapis.com/zsign-public/branding/.../logo.png"}
Start White-Label Checkout
/api/branding/white-label/checkoutStart a $49/month White-label subscription via Stripe Checkout (100 envelopes included each month). Returns 409 if the account already has an active subscription.
Request
curl -X POST https://zsign.io/api/branding/white-label/checkout \-H "Authorization: Bearer YOUR_API_KEY"
Response 200
Stripe Checkout session URL
{"checkout_url": "https://checkout.stripe.com/c/pay/cs_test_..."}
Open Billing Portal
/api/branding/white-label/portalOpen the Stripe Billing Portal to manage or cancel the White-label subscription. Returns 400 if no subscription is on file.
Request
curl -X POST https://zsign.io/api/branding/white-label/portal \-H "Authorization: Bearer YOUR_API_KEY"
Response 200
Stripe Billing Portal session URL
{"portal_url": "https://billing.stripe.com/p/session/..."}
Set Sender Domain
/api/branding/sender-domainRegister a custom domain for outbound invite and completion emails via Resend. Requires an active White-label subscription (403 if not subscribed). Returns 409 if the domain is already claimed by another account.
Request Body
Content-Type: application/json
Fields
| Name | Type | Description |
|---|---|---|
domainrequired | string | Domain to send from, e.g. mail.yourcompany.com |
{"domain": "mail.yourcompany.com"}
Request
curl -X PUT https://zsign.io/api/branding/sender-domain \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"domain": "mail.yourcompany.com"}'
Response 200
Sender domain registered with Resend, not yet verified
{"sender_domain": "mail.yourcompany.com","sender_domain_verified": false,"records": [{"record": "TXT","name": "mail.yourcompany.com","value": "resend-verify=..."}]}
Verify Sender Domain
/api/branding/sender-domain/verifyAsk Resend to re-check DNS for the account's configured sender domain and report status. Not gated by subscription status, so an account whose subscription has lapsed can still confirm its DNS records.
Request
curl -X POST https://zsign.io/api/branding/sender-domain/verify \-H "Authorization: Bearer YOUR_API_KEY"
Response 200
Current verification status and DNS records
{"sender_domain": "mail.yourcompany.com","sender_domain_verified": true,"records": [{"record": "TXT","name": "mail.yourcompany.com","value": "resend-verify=...","status": "verified"}]}
Remove Sender Domain
/api/branding/sender-domainRemove the configured sender domain and deregister it from Resend. Not gated by subscription status, so an account whose subscription has lapsed can still clean up its DNS.
Request
curl -X DELETE https://zsign.io/api/branding/sender-domain \-H "Authorization: Bearer YOUR_API_KEY"
Response 200
Sender domain removed
{"success": true}
Set Embed Origins
/api/branding/embed-originsConfigure the exact origins allowed to embed this account's signing pages in an iframe (default deny -- an empty list blocks all framing). Exact match only, https required except http://localhost[:port], no paths or wildcards, max 10.
Request Body
Content-Type: application/json
Fields
| Name | Type | Description |
|---|---|---|
originsrequired | array | List of allowed origins, e.g. ["https://app.yourcompany.com"] |
{"origins": ["https://app.yourcompany.com"]}
Request
curl -X PUT https://zsign.io/api/branding/embed-origins \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"origins": ["https://app.yourcompany.com"]}'
Response 200
Updated branding settings including the normalized embed_origins list
{"embed_origins": ["https://app.yourcompany.com"]}