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.ioAuthentication
Bearer YOUR_API_KEYDocuments
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": [{"document_id": "550e8400-e29b-41d4-a716-446655440000","name": "Contract.pdf","status": "pending","created_at": "2024-01-15T10:30:00Z"}],"total": 1,"offset": 0,"limit": 20}
List Envelopes (API key)
/api/v1/documentsList envelopes in the caller's organization. Same filter semantics as the dashboard JWT list. Includes unsent drafts (status draft or ready_to_send, session_id null) in the default All mix. limit max 50. Each item includes thin recipients[] (recipient_id, email, name, status, can_remind) for Remind. Sender email and the rich session payload stay on GET /api/v1/documents/{document_id}.
Query Parameters
| Name | Type | Description |
|---|---|---|
offset | integer= 0 | Pagination offset |
limit | integer= 20 | Items per page (max 50) |
status | string | Session status, cancelled (voided+declined), or draft / ready_to_send |
q | string | Substring search on name, filename, recipient, or id |
date_from | string | ISO-8601 inclusive created_at lower bound |
date_to | string | ISO-8601 inclusive created_at upper bound |
Request
curl -X GET "https://zsign.io/api/v1/documents?q=Contract&limit=20" \-H "Authorization: Bearer zs_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Response 200
Org-scoped envelope page
{"items": [{"document_id": "550e8400-e29b-41d4-a716-446655440000","session_id": "660e8400-e29b-41d4-a716-446655440000","name": "Contract.pdf","status": "pending","created_at": "2024-01-15T10:30:00Z","recipient_count": 1,"completed_recipient_count": 0,"recipients": [{"recipient_id": "770e8400-e29b-41d4-a716-446655440000","email": "signer@example.com","name": "Alex Signer","status": "pending","can_remind": true}]}],"total": 1,"offset": 0,"limit": 20}
Upload and Send Document
/api/v1/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}. The sender's account default also applies: "Require new fields by default" (Settings → Sending) is on by default and makes every unstarred tag a required field; senders who turn it off get optional fields unless they star them. * marks required either way. 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. |
sequential | string ("true"/"false")= true | Sequential send-body flag. Default true. true = one at a time (only the current signer can use their invite; later recipients wait). false = everyone at once. 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/v1/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}]}
Create Draft Envelope
/api/v1/documents/draftsUpload one or more PDFs as an unsent Draft (no credit debit, no invites), or stamp a saved template. Provide exactly one of `file`/`files` or `template_id`. `file` is the single-document form; `files` is repeatable. When both are sent the order is `file` first, then `files`. `document_names` is an optional JSON array of display names aligned to that order. `role_mapping` (template only) is a JSON object `{source_role: destination_role}`; unmapped roles stay literal. `fields` cannot be combined with `template_id`. The same `file` + `files` + `document_names` shape is accepted on POST /api/v1/documents/send. Response `documents[]` is `{id, name, filename, page_start, page_count, position}` plus `revision`.
Request Body
Content-Type: multipart/form-data
Fields
| Name | Type | Description |
|---|---|---|
file | file | First PDF. Combined with `files` when both are sent. |
files | file (repeatable) | Additional PDFs. Each counts toward `MAX_ENVELOPE_DOCUMENTS` (default 10). |
document_names | string (JSON array) | Display names aligned to `file` first, then `files`. Length must match the file count. |
recipients | string (JSON array) | Stored for later send / Approve. |
fields | string (JSON array) | Placed fields. Each may include `document_id` (a `documents[].id`); `position.page_number` is then relative to that document. |
revision | integer | Optimistic concurrency token returned on every draft response. Omit to skip the check. |
template_id | uuid | Saved template to stamp. Mutually exclusive with `file`/`files`. |
role_mapping | string (JSON object) | Template only. `{source_role: destination_role}`; unmapped roles stay literal. |
name | string | Display name for the new draft. Defaults to the template name when stamping. |
{"document_names": ["NDA","MSA"]}
Request
curl -X POST "https://zsign.io/api/v1/documents/drafts" \-H "Authorization: Bearer YOUR_API_KEY" \-F "files=@nda.pdf" \-F "files=@msa.pdf" \-F 'document_names=["NDA","MSA"]'
Response 201
Unsent draft with envelope documents and revision
{"document_id": "550e8400-e29b-41d4-a716-446655440000","status": "draft","revision": 1,"documents": [{"id": "660e8400-e29b-41d4-a716-446655440000","name": "NDA","filename": "nda.pdf","page_start": 1,"page_count": 1,"position": 0},{"id": "770e8400-e29b-41d4-a716-446655440000","name": "MSA","filename": "msa.pdf","page_start": 2,"page_count": 3,"position": 1}]}
Save Draft as Template
/api/v1/documents/drafts/{document_id}/save-as-templateAPI-key twin of POST /api/documents/drafts/{id}/save-as-template. Copies the draft's current bytes and parsed_fields into a new reusable template. Free. The draft itself is unchanged. There is no blank-template flow.
Path Parameters
| Name | Type | Description |
|---|---|---|
document_idrequired | uuid | Unsent Draft id from POST /api/v1/documents/drafts |
Request Body
Content-Type: application/json
Fields
| Name | Type | Description |
|---|---|---|
namerequired | string | Template display name |
{"name": "Std NDA"}
Request
curl -X POST "https://zsign.io/api/v1/documents/drafts/{document_id}/save-as-template" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"name":"Std NDA"}'
Response 201
Template summary (id, name, parties, updated_at)
{"id": "22222222-2222-2222-2222-222222222222","name": "Std NDA","filename": "contract.pdf","parties": ["signer"],"field_count": 1,"document_count": 1}
List Templates
/api/v1/documents/templatesAPI-key twin of GET /api/documents/templates. Every template the caller's org owns, with role names (`parties`), updated_at, and public-link status. Same rows as the dashboard list — not a second template model.
Request
curl -X GET "https://zsign.io/api/v1/documents/templates" \-H "Authorization: Bearer YOUR_API_KEY"
Response 200
Template list
{"templates": [{"id": "22222222-2222-2222-2222-222222222222","name": "Std NDA","parties": ["signer"],"field_count": 1,"document_count": 1}],"total": 1}
Add Document to Draft
/api/v1/documents/drafts/{document_id}/documentsAppend one uploaded PDF, or every document of a saved template, to a Draft envelope. Provide exactly one of `file` or `template_id`. Inserting a template adds all of its documents; each counts toward `MAX_ENVELOPE_DOCUMENTS` (default 10). `role_mapping` (template only) is a JSON object `{source_role: destination_role}`; unmapped roles stay literal.
Path Parameters
| Name | Type | Description |
|---|---|---|
document_idrequired | UUID | Draft envelope id from POST /api/v1/documents/drafts |
Request Body
Content-Type: multipart/form-data
Fields
| Name | Type | Description |
|---|---|---|
file | file | PDF to append. Mutually exclusive with template_id. |
template_id | UUID | Saved template whose documents are all inserted. Mutually exclusive with file. |
name | string | Display name for a single uploaded file (not allowed with a multi-document template). |
role_mapping | string (JSON object) | Template only. Maps template roles onto this envelope, e.g. {"customer":"client"}. |
revision | integer | Optimistic concurrency token from the last draft response. |
{"template_id": "880e8400-e29b-41d4-a716-446655440000","role_mapping": {"customer": "client"}}
Request
curl -X POST "https://zsign.io/api/v1/documents/drafts/{document_id}/documents" \-H "Authorization: Bearer YOUR_API_KEY" \-F "file=@msa.pdf" \-F "name=MSA"
Response 201
Updated draft with the new document(s) appended
{"document_id": "550e8400-e29b-41d4-a716-446655440000","revision": 2,"documents": [{"id": "660e8400-e29b-41d4-a716-446655440000","name": "a","filename": "a.pdf","page_start": 1,"page_count": 1,"position": 0},{"id": "770e8400-e29b-41d4-a716-446655440000","name": "T","filename": "t.pdf","page_start": 2,"page_count": 1,"position": 1}]}
Reorder Draft Documents
/api/v1/documents/drafts/{document_id}/documents/orderSet the order of documents in a Draft. `order` must list every `documents[].id` exactly once. Field pages are remapped to the new ranges. `revision` is the optimistic concurrency token.
Path Parameters
| Name | Type | Description |
|---|---|---|
document_idrequired | UUID | Draft envelope id |
Request Body
Content-Type: application/json
Fields
| Name | Type | Description |
|---|---|---|
orderrequired | array of UUID | Every document id in the wanted order |
revision | integer | Optimistic concurrency token from the last draft response |
{"order": ["770e8400-e29b-41d4-a716-446655440000","660e8400-e29b-41d4-a716-446655440000"],"revision": 2}
Request
curl -X PUT "https://zsign.io/api/v1/documents/drafts/{document_id}/documents/order" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"order":["770e8400-e29b-41d4-a716-446655440000"],"revision":2}'
Response 200
Updated draft with remapped page ranges
{"document_id": "550e8400-e29b-41d4-a716-446655440000","revision": 3}
Rename Draft Document
/api/v1/documents/drafts/{document_id}/documents/{part_id}Change the display name of one document inside a Draft. Path `{part_id}` is `documents[].id`. Display metadata only — no bytes rebuilt, no field moved. `revision` is the optimistic concurrency token.
Path Parameters
| Name | Type | Description |
|---|---|---|
document_idrequired | UUID | Draft envelope id |
part_idrequired | UUID | documents[].id of the document to rename |
Request Body
Content-Type: application/json
Fields
| Name | Type | Description |
|---|---|---|
namerequired | string | New display name (1–255 characters) |
revision | integer | Optimistic concurrency token |
{"name": "Second","revision": 2}
Request
curl -X PATCH "https://zsign.io/api/v1/documents/drafts/{document_id}/documents/{part_id}" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"name":"Second"}'
Response 200
Updated draft
{"document_id": "550e8400-e29b-41d4-a716-446655440000","revision": 3}
Remove Draft Document
/api/v1/documents/drafts/{document_id}/documents/{part_id}Remove one document from a Draft. Path `{part_id}` is `documents[].id`. An envelope must keep at least one document (`last_document`). `?revision=` is the optimistic concurrency token.
Path Parameters
| Name | Type | Description |
|---|---|---|
document_idrequired | UUID | Draft envelope id |
part_idrequired | UUID | documents[].id of the document to remove |
Query Parameters
| Name | Type | Description |
|---|---|---|
revision | integer | Optimistic concurrency token from the last draft response |
Request
curl -X DELETE "https://zsign.io/api/v1/documents/drafts/{document_id}/documents/{part_id}?revision=2" \-H "Authorization: Bearer YOUR_API_KEY"
Response 200
Updated draft after the document was removed
{"document_id": "550e8400-e29b-41d4-a716-446655440000","revision": 3}
Download Signed Document
/api/v1/documents/{completed_document_id}/downloadDownload the sealed, signed PDF. `{completed_document_id}` is the id from get-envelope-status / document.completed; the original document id from send / list also works and resolves to that document's most recent completed session (a session id does not). `?document=` is one `documents[].id` to download just that document (its pages plus the certificate pages — an extract; the certificate hash identifies the full envelope). `?format=zip` returns every document as its own PDF in one archive. Do not combine `document` and `format`.
Path Parameters
| Name | Type | Description |
|---|---|---|
completed_document_idrequired | UUID | Completed document id from GET /api/v1/documents/{document_id}, or that original document id itself |
Query Parameters
| Name | Type | Description |
|---|---|---|
document | UUID | documents[].id — download only that document plus the certificate pages |
format | string | `zip` — every document as its own PDF in one archive |
Request
curl -X GET "https://zsign.io/api/v1/documents/{completed_document_id}/download?document={part_id}" \-H "Authorization: Bearer YOUR_API_KEY" \-o signed.pdf
Response 200
Sealed PDF (or ZIP when format=zip)
(binary)
Get Signing URLs
/api/v1/documents/{document_id}/signing-urlsRecipient signing URLs for an already-sent envelope. Same `{recipient_email, recipient_name, signing_url, token}` objects POST /api/v1/documents/send returns as `signing_urls`. These are the ordinary agent-link values — valid iframe `signingUrl`s for `@zsign/embed`. Optional `recipient_email` keeps one recipient. Drafts and documents with no session 404.
Path Parameters
| Name | Type | Description |
|---|---|---|
document_idrequired | uuid | Document identifier from POST /api/v1/documents/send or send-from-template |
Query Parameters
| Name | Type | Description |
|---|---|---|
recipient_email | string | If set, only that recipient's signing URL is returned |
Request
curl -X GET "https://zsign.io/api/v1/documents/{document_id}/signing-urls" \-H "Authorization: Bearer YOUR_API_KEY"
Response 200
Send-shape signing URLs
{"document_id": "550e8400-e29b-41d4-a716-446655440000","session_id": "660e8400-e29b-41d4-a716-446655440000","signing_urls": [{"recipient_email": "alice@example.com","recipient_name": "Alice","signing_url": "https://app.example/sign/TOKEN","token": "TOKEN"}]}
Remind Recipient
/api/v1/documents/{document_id}/recipients/{recipient_id}/remindSend one signing reminder to a still-unsigned recipient. Same policy as the dashboard Remind button and the cron sweep: cap, spacing, sequential can_sign_now. REMINDERS_ENABLED does not apply. Envelopes sent with send_invite=false are refused unless override_send_invite is true. MCP remind_envelope is a thin wrapper over this route (envelopes:send).
Path Parameters
| Name | Type | Description |
|---|---|---|
document_idrequired | UUID | Document identifier from POST /api/v1/documents/send |
recipient_idrequired | UUID | Recipient identifier from GET /api/v1/documents/{document_id} (session.recipients[].recipient_id) |
Request Body
Content-Type: application/json
Fields
| Name | Type | Description |
|---|---|---|
override_send_invite | boolean= false | Required only when the envelope was sent with send_invite=false. The integrator that owns delivery owns reminders too unless this is true. |
{"override_send_invite": false}
Request
curl -X POST "https://zsign.io/api/v1/documents/{document_id}/recipients/{recipient_id}/remind" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{}'
Response 200
Reminder sent
{"session_id": "660e8400-e29b-41d4-a716-446655440000","recipient_id": "770e8400-e29b-41d4-a716-446655440000","email": "john@example.com","reminder_count": 1,"reminder_last_at": "2026-09-06T12:00:00Z","sent": true}
Signing Sessions
Manage signing sessions
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.
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. GET /api/webhooks is the current singleton HTTP endpoint (200 + config, or 200 + null when none). Extra destinations are listed at GET /api/webhooks/subscriptions (200 + []). 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.
Get Webhook
/api/webhooksRead the org's current singleton HTTP webhook configuration. Empty is 200 with a null body, not 404. Settings and API-key callers share this route.
Request
curl https://zsign.io/api/webhooks \-H "Authorization: Bearer YOUR_API_KEY"
Response 200
Current singleton config, or null if none is configured
{"id": "550e8400-e29b-41d4-a716-446655440000","url": "https://example.com/webhook","events": ["document.signed","document.completed"],"enabled": true}
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. Parent SDKs: npm install @zsign/embed (createSigningEmbed) or @zsign/react (ZSignEmbed).
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"]}