API Reference

Complete reference for the zSign REST API. Send documents, manage signing sessions, and integrate webhooks.

Base URL

https://api.zsign.io/api

Authentication

Bearer YOUR_API_KEY

Health & Status

API status and health check endpoints

GET

Check API Status

/

Verify the API is running.

Request

curl https://api.zsign.io/api

Response 200

API is running

{
"message": "zSign API is running"
}
GET

Health Check

/health

Get detailed health status of the API.

Request

curl https://api.zsign.io/api/health

Response 200

Health status

{
"status": "healthy"
}

Documents

Upload, send, and manage documents

GET

List Documents

/api/documents
Auth required

Retrieve documents with optional filtering.

Query Parameters

NameTypeDescription
offsetinteger= 0Pagination offset
limitinteger= 20Items per page
statusstringFilter by status
recipientstringFilter by recipient email

Request

curl -X GET "https://api.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
}
POST

Upload and Send Document

/api/documents/send
Auth required

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

Request Body

Content-Type: multipart/form-data

Fields

NameTypeDescription
filerequiredfilePDF file to upload
recipientsrequiredstringJSON array of recipients
namestringDocument name
Example
[
{
"email": "john@example.com",
"name": "John Doe",
"role": "signer"
}
]

Request

curl -X POST https://api.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://app.zsign.io/sign/eyJ..."
}
]
}
### PDF Field Annotation Requirements **Sample LLM prompt to format PDF documents with correct annotation syntax:** ``` Create a fillable PDF document with embedded form field annotations. The form fields must follow these specifications: ## Field Naming Syntax: {type:party:name} Format: `{type:party:name}` or `{type*:party:name}` for required fields **Field Types:** - `signature` - Signature field - `text` - Text input field - `date` - Date field - `initials` - Initials field **Components:** 1. **Type**: text, date, signature, or initials 2. **Party**: The role/party who fills it (e.g., signer, borrower, employee, manager, landlord, tenant). This must exactly match the `role` you send for that recipient. Matching is **case-sensitive**: `{signature:signer}` is assigned to a recipient with `"role": "signer"`, and will NOT match `"Signer"`. A party that matches no recipient's role leaves the field assigned to nobody — the document still sends, but the signer sees nothing to fill in. 3. **Name**: Field identifier (e.g., name, email, address, company_name) **Required Fields:** Add `*` after the type to make a field required: - `{text*:signer:name}` - Required text field - `{signature*:signer}` - Required signature **Examples:** - `{text:signer:full_name}` - `{text*:signer:email}` - `{date:signer:birthdate}` - `{signature:signer}` - `{initials:signer}` - `{text*:borrower:company_name}` - `{signature*:landlord}` ## Critical Technical Requirements The field name MUST be set directly on each widget annotation's `/T` attribute, NOT only in a parent AcroForm field object. Many PDF signing services read the `/T` value directly from page annotations. **Each annotation must have:** - `/Type`: `/Annot` - `/Subtype`: `/Widget` - `/FT`: `/Tx` (for text fields) - `/T`: The field name in `{type:party:name}` format (THIS IS CRITICAL) - `/F`: `4` (print flag) - `/Rect`: `[left, bottom, right, top]` coordinates ``` **Sample PDF:** [Download simple_contract_1.pdf](https://storage.googleapis.com/zsign-public/simple_contract_1.pdf)

Signing Sessions

Create and manage signing sessions

POST

Create Session

/api/sessions
Auth required

Create a new signing session.

Request Body

Content-Type: application/json

Fields

NameTypeDescription
document_idrequiredUUIDDocument to sign
document_typerequiredstringtemplate or one_off
recipientsrequiredarrayList of recipients
Example
{
"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://api.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://app.zsign.io/sign/eyJ..."
}
]
}
GET

Get Session Status

/api/sessions/{session_id}/status
Auth required

Get lightweight session status.

Path Parameters

NameTypeDescription
session_idrequiredUUIDSession identifier

Request

curl -X GET "https://api.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
}
}

Webhooks

Configure webhook endpoints for real-time notifications

POST

Create Webhook

/api/webhooks
Auth required

Create or replace webhook configuration.

Request Body

Content-Type: application/json

Fields

NameTypeDescription
urlrequiredstringWebhook endpoint URL (HTTPS)
eventsrequiredarrayList of events to subscribe to
Example
{
"url": "https://example.com/webhook",
"events": [
"document.signed",
"document.completed"
]
}

Request

curl -X POST https://api.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

Get Branding Settings

/api/branding
Auth required

Retrieve the current account's branding configuration.

Request

curl -H "Authorization: Bearer YOUR_API_KEY" https://api.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"
}
PUT

Update Branding Settings

/api/branding
Auth required

Update company name, logo URL, and accent color shown on signing pages, emails, and the Certificate of Completion.

Request Body

Content-Type: application/json

Fields

NameTypeDescription
company_namestringDisplayed on signing pages, emails, and the certificate
logo_urlstringPublic logo URL (or use POST /api/branding/logo to upload one)
primary_colorstringHex color, e.g. #2563eb, drives the signing page accent
Example
{
"company_name": "Acme Corp",
"primary_color": "#ff6600"
}

Request

curl -X PUT https://api.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"
}
POST

Start White-Label Checkout

/api/branding/white-label/checkout
Auth required

Start 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://api.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_..."
}
POST

Open Billing Portal

/api/branding/white-label/portal
Auth required

Open 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://api.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/..."
}
PUT

Set Sender Domain

/api/branding/sender-domain
Auth required

Register 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

NameTypeDescription
domainrequiredstringDomain to send from, e.g. mail.yourcompany.com
Example
{
"domain": "mail.yourcompany.com"
}

Request

curl -X PUT https://api.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=..."
}
]
}
Unlike the two endpoints below, this one requires an active subscription (`active`, `trialing`, or `past_due`). Switching to a different domain removes the previous Resend registration.
POST

Verify Sender Domain

/api/branding/sender-domain/verify
Auth required

Ask 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://api.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"
}
]
}
DELETE

Remove Sender Domain

/api/branding/sender-domain
Auth required

Remove 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://api.zsign.io/api/branding/sender-domain \
-H "Authorization: Bearer YOUR_API_KEY"

Response 200

Sender domain removed

{
"success": true
}
PUT

Set Embed Origins

/api/branding/embed-origins
Auth required

Configure 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

NameTypeDescription
originsrequiredarrayList of allowed origins, e.g. ["https://app.yourcompany.com"]
Example
{
"origins": [
"https://app.yourcompany.com"
]
}

Request

curl -X PUT https://api.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"
]
}