API Reference
·https://docura.cloud/api/v1Getting started
Docura API Reference
The Docura REST API lets you create templates, send documents for signing, track status, download signed PDFs, and receive real-time webhook notifications — all programmatically.
Templates can be created entirely via API by uploading a PDF with field definitions. If your workflow generates documents from HTML or a template engine, convert to PDF first (see Create from HTML) and then POST to the API.
The API follows standard REST conventions: JSON request and response bodies, HTTP verbs for actions, and predictable resource URLs. All endpoints return consistent error shapes.
Authentication
All API requests must include a secret API key in the Authorization header. Keys are prefixed sk_live_ and scoped to your organisation.
curl https://docura.cloud/api/v1/templates \ -H "Authorization: Bearer sk_live_your_api_key"
Keys are created in the dashboard. You can have up to 10 active keys per organisation. Each key can be named and revoked independently.
Quickstart
Send your first document for signing in 3 API calls: create a template, then send it to a signer.
Step 1 — Create a template from PDF
curl -X POST https://docura.cloud/api/v1/templates \
-H "Authorization: Bearer sk_live_..." \
-F "name=Non-Disclosure Agreement" \
-F "signerRoles=[{\"name\":\"Client\",\"color\":\"#6b4eff\"}]" \
-F "file=@nda.pdf"
# Response → copy the template id and signerRoles[0].idStep 2 — Send for signing
curl -X POST https://docura.cloud/api/v1/templates/tmpl_01HZ.../submissions \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"signers": [
{
"roleId": "role_01HZ...",
"email": "jane@acme.com",
"name": "Jane Smith"
}
],
"message": "Please review and sign the attached NDA.",
"expiresInDays": 7
}'Step 3 — Poll for completion or use a webhook
# Check status curl https://docura.cloud/api/v1/submissions/sub_01HZ... \ -H "Authorization: Bearer sk_live_..." # When status === "COMPLETED" → download the signed PDF curl https://docura.cloud/api/v1/submissions/sub_01HZ.../download \ -H "Authorization: Bearer sk_live_..." \ --output signed-document.pdf
Base URL & versioning
Base URL: https://docura.cloud/api/v1 Version: v1 (current, stable) Format: JSON — Content-Type: application/json (unless uploading a file)
The API is versioned by path prefix. Breaking changes will be introduced in a new version (/v2) with a 6-month deprecation window for v1.
Templates
A template is a PDF document with pre-defined field positions and signer roles. Templates can be created via the API (PDF upload) or visually in the dashboard builder, and then sent to signers via the submissions endpoint.
POST Create a template
Creates a new template by uploading a PDF file along with signer role definitions and optional pre-placed field positions. Accepts multipart/form-data.
Form parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
file | File | Yes | The PDF document. Must be a valid, non-encrypted PDF. |
name | string | Yes | Template name. 1–200 characters. |
description | string | No | Optional description. Max 1,000 characters. |
signerRoles | JSON | Yes | JSON array of signer role objects: [{name, color?}]. At least one role is required. |
fields | JSON | No | JSON array of pre-placed field definitions. See Field schema reference. If omitted, fields can be placed in the dashboard builder. |
metadata | JSON | No | Flat JSON object of key-value pairs for correlation with your system. Values can be string, number, or boolean. Example: {"customerId":"cust_123","planId":"enterprise"}. |
Example — PDF with two signer roles, no pre-defined fields
curl -X POST https://docura.cloud/api/v1/templates \
-H "Authorization: Bearer sk_live_..." \
-F "name=Service Agreement" \
-F "description=Standard MSA for new clients" \
-F 'signerRoles=[{"name":"Client","color":"#6b4eff"},{"name":"Service Provider","color":"#f59e0b"}]' \
-F "file=@service-agreement.pdf"Example — PDF with pre-placed signature and date fields (coordinates as 0–1 percentages)
curl -X POST https://docura.cloud/api/v1/templates \
-H "Authorization: Bearer sk_live_..." \
-F "name=NDA" \
-F 'signerRoles=[{"name":"Client","color":"#6b4eff"}]' \
-F 'fields=[
{
"id": "field_1",
"type": "signature",
"signerRoleId": "__ROLE_0__",
"page": 0,
"x": 0.1, "y": 0.82,
"width": 0.35, "height": 0.06,
"required": true,
"label": "Client Signature"
},
{
"id": "field_2",
"type": "date",
"signerRoleId": "__ROLE_0__",
"page": 0,
"x": 0.6, "y": 0.82,
"width": 0.2, "height": 0.04,
"required": true,
"label": "Date Signed",
"dateFormat": "MMM D, YYYY"
}
]' \
-F "file=@nda.pdf"__ROLE_0__, __ROLE_1__, … as placeholder signerRoleId values in the fields array. They are resolved to the actual generated role IDs in order. Alternatively, first create the template without fields, grab the role IDs from the response, then update fields in the dashboard builder.Response — 201 Created
{
"id": "tmpl_01HZ5X9BKPQR3V2M",
"name": "Service Agreement",
"description": "Standard MSA for new clients",
"pageCount": 3,
"signerRoles": [
{ "id": "role_01HZ...", "name": "Client", "order": 0, "color": "#6b4eff" },
{ "id": "role_02HZ...", "name": "Service Provider", "order": 1, "color": "#f59e0b" }
],
"createdAt": "2026-06-28T10:00:00Z",
"updatedAt": "2026-06-28T10:00:00Z"
}Node.js example (form-data)
import fs from "fs";
import FormData from "form-data";
import fetch from "node-fetch";
const form = new FormData();
form.append("name", "Non-Disclosure Agreement");
form.append(
"signerRoles",
JSON.stringify([
{ name: "Disclosing Party", color: "#6b4eff" },
{ name: "Receiving Party", color: "#f59e0b" },
])
);
form.append("file", fs.createReadStream("./nda.pdf"), {
filename: "nda.pdf",
contentType: "application/pdf",
});
const res = await fetch("https://docura.cloud/api/v1/templates", {
method: "POST",
headers: {
Authorization: "Bearer sk_live_...",
...form.getHeaders(),
},
body: form,
});
const template = await res.json();
console.log("Template created:", template.id);
console.log("Role IDs:", template.signerRoles.map((r) => r.id));Python example (requests)
import requests, json
with open("nda.pdf", "rb") as f:
response = requests.post(
"https://docura.cloud/api/v1/templates",
headers={"Authorization": "Bearer sk_live_..."},
data={
"name": "Non-Disclosure Agreement",
"signerRoles": json.dumps([
{"name": "Disclosing Party", "color": "#6b4eff"},
{"name": "Receiving Party", "color": "#f59e0b"},
]),
},
files={"file": ("nda.pdf", f, "application/pdf")},
)
template = response.json()
print("Template ID:", template["id"])
print("Role IDs:", [r["id"] for r in template["signerRoles"]])Create from HTML
Docura accepts any valid PDF — including ones generated server-side from HTML. This lets you build fully programmatic signing workflows without touching the dashboard: render your document from a template engine, convert to PDF, POST to Docura.
POST /v1/templates.Node.js — Puppeteer (headless Chrome, pixel-perfect)
import puppeteer from "puppeteer";
import FormData from "form-data";
import fetch from "node-fetch";
// 1. Render your HTML to a PDF buffer
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(`
<html>
<head>
<style>
body { font-family: Georgia, serif; padding: 60px; color: #201515; }
h1 { font-size: 28px; margin-bottom: 8px; }
.sig { border-top: 1px solid #c5c0b1; margin-top: 80px; padding-top: 6px; width: 220px; }
</style>
</head>
<body>
<h1>Non-Disclosure Agreement</h1>
<p>This Agreement is entered into as of the date signed below between...</p>
<p style="margin-top:40px">
Both parties agree to the terms herein.
</p>
<div class="sig">Disclosing Party signature</div>
</body>
</html>
`, { waitUntil: "networkidle0" });
const pdfBuffer = await page.pdf({ format: "Letter", printBackground: true });
await browser.close();
// 2. Upload to Docura
const form = new FormData();
form.append("name", "NDA — Generated " + new Date().toISOString().split("T")[0]);
form.append(
"signerRoles",
JSON.stringify([{ name: "Disclosing Party", color: "#6b4eff" }])
);
form.append("file", pdfBuffer, {
filename: "nda.pdf",
contentType: "application/pdf",
});
const res = await fetch("https://docura.cloud/api/v1/templates", {
method: "POST",
headers: { Authorization: "Bearer sk_live_...", ...form.getHeaders() },
body: form,
});
const template = await res.json();
console.log("Template ready:", template.id);Python — WeasyPrint (CSS-based, no headless browser)
import io, json, requests
from weasyprint import HTML
html_source = """
<html>
<head>
<style>
body { font-family: Georgia, serif; margin: 60px; color: #201515; }
h1 { font-size: 28px; }
.sig { border-top: 1px solid #c5c0b1; margin-top: 80px; width: 220px; }
</style>
</head>
<body>
<h1>Service Agreement</h1>
<p>This agreement is entered into between the parties below...</p>
<div class="sig">Client signature</div>
</body>
</html>
"""
# 1. Convert HTML → PDF
pdf_bytes = HTML(string=html_source).write_pdf()
# 2. Upload to Docura
response = requests.post(
"https://docura.cloud/api/v1/templates",
headers={"Authorization": "Bearer sk_live_..."},
data={
"name": "Service Agreement",
"signerRoles": json.dumps([{"name": "Client", "color": "#6b4eff"}]),
},
files={"file": ("agreement.pdf", io.BytesIO(pdf_bytes), "application/pdf")},
)
template = response.json()
print("Template ID:", template["id"])Recommended HTML-to-PDF libraries
| Library | Runtime | Notes |
|---|---|---|
Puppeteer | Node.js | Chrome-based. Pixel-perfect rendering. Heavy (~200 MB). |
Playwright | Node.js / Python | Same quality as Puppeteer. Better API. |
WeasyPrint | Python | Pure-Python CSS renderer. No browser needed. 10-100× faster. |
wkhtmltopdf | Any (CLI) | Fast binary. Good for simple layouts. Limited CSS support. |
PDFKit | Node.js | Programmatic PDF generation (no HTML). Full layout control. |
Gotenberg | Docker sidecar | REST API wrapper around Chrome. Great for production. |
GET List templates
Returns a paginated list of active templates for your organisation.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number. Defaults to 1. |
limit | integer | No | Results per page. 1–100. Defaults to 20. |
Response
{
"data": [
{
"id": "tmpl_01HZ5X9BKPQR3V2M",
"name": "Non-Disclosure Agreement",
"description": "Standard 2-page NDA for contractors",
"pageCount": 2,
"signerRoles": [
{ "id": "role_01HZ...", "name": "Client", "order": 0, "color": "#6b4eff" },
{ "id": "role_02HZ...", "name": "Vendor", "order": 1, "color": "#f59e0b" }
],
"createdAt": "2026-01-15T09:00:00Z",
"updatedAt": "2026-03-20T14:30:00Z"
}
],
"total": 12,
"page": 1,
"limit": 20
}GET Get a template
curl https://docura.cloud/api/v1/templates/tmpl_01HZ... \ -H "Authorization: Bearer sk_live_..."
{
"id": "tmpl_01HZ5X9BKPQR3V2M",
"name": "Non-Disclosure Agreement",
"description": "Standard 2-page NDA for contractors",
"pageCount": 2,
"signerRoles": [
{ "id": "role_01HZ...", "name": "Client", "order": 0, "color": "#6b4eff" }
],
"createdAt": "2026-01-15T09:00:00Z",
"updatedAt": "2026-03-20T14:30:00Z"
}PUT Update a template
Update a template's name or description. All fields are optional.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | Template name. 1–200 characters. |
description | string | No | Optional description. Max 1,000 characters. |
curl -X PUT https://docura.cloud/api/v1/templates/tmpl_01HZ... \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "name": "NDA v2 — Updated" }'DELETE Delete a template
Soft-deletes a template. It will no longer appear in lists or accept new submissions.
{ "success": true }Submissions
A submission is an instance of a template sent to one or more signers. Creating a submission immediately emails all signers (or the first signer if sequential) with a unique signing link.
POST Create a submission
Sends the template to all specified signers. Each signer must correspond to a role on the template. Use GET /v1/templates/:id to retrieve role IDs.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
signers | array | Yes | Array of signer objects. At least one required. |
signers[].roleId | string | Yes | The signer role ID from the template. |
signers[].email | string | Yes | Signer's email address. Invitation sent here. |
signers[].name | string | No | Signer's display name. |
signingOrder | enum | No | "PARALLEL" (default) or "SEQUENTIAL". Sequential sends one at a time in role order. |
message | string | No | Custom message included in the signing invitation email. Max 1,000 characters. |
expiresInDays | integer | No | Days until the signing link expires. 1–365. |
metadata | object | No | Flat key-value object for correlating this submission with records in your system (e.g. your order ID, customer ID, deal stage). Returned on GET submission and included in webhook payloads. Values: string, number, or boolean. |
curl -X POST https://docura.cloud/api/v1/templates/tmpl_01HZ.../submissions \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"signers": [
{
"roleId": "role_01HZ5X9BKPQR3V2M",
"email": "jane@acme.com",
"name": "Jane Smith"
}
],
"signingOrder": "PARALLEL",
"message": "Hi Jane, please sign the attached NDA before our kickoff call.",
"expiresInDays": 7,
"metadata": {
"customerId": "cust_abc123",
"dealId": "deal_789",
"source": "crm-integration"
}
}'Response — 201 Created
{
"id": "sub_01HZ8P4NKQR2V3M",
"status": "PENDING",
"signingOrder": "PARALLEL",
"metadata": {
"customerId": "cust_abc123",
"dealId": "deal_789",
"source": "crm-integration"
},
"signers": [
{
"id": "sgn_01HZ...",
"email": "jane@acme.com",
"name": "Jane Smith",
"roleName": "Client",
"status": "INVITED",
"signingUrl": "https://docura.cloud/sign/tok_eyJh..."
}
],
"createdAt": "2026-06-28T10:30:00Z"
}signingUrl if you want to embed signing in your own UI using the embed SDK or an iframe. URLs expire when the submission does.GET Get a submission
Returns full submission details including per-signer status. Poll this to track progress.
{
"id": "sub_01HZ8P4NKQR2V3M",
"status": "COMPLETED",
"signingOrder": "PARALLEL",
"template": { "id": "tmpl_01HZ...", "name": "Non-Disclosure Agreement" },
"message": "Hi Jane, please sign the attached NDA...",
"expiresAt": "2026-07-05T10:30:00Z",
"completedAt": "2026-06-28T11:15:00Z",
"createdAt": "2026-06-28T10:30:00Z",
"signers": [
{
"id": "sgn_01HZ...",
"email": "jane@acme.com",
"name": "Jane Smith",
"roleName": "Client",
"status": "SIGNED",
"signedAt": "2026-06-28T11:15:00Z",
"order": 0
}
]
}GET Download signed PDF
Returns the finalised, signed PDF as a binary stream. The submission must have status === "COMPLETED".
curl https://docura.cloud/api/v1/submissions/sub_01HZ.../download \ -H "Authorization: Bearer sk_live_..." \ --output signed-nda-jane-smith.pdf
Content-Type: application/pdf. A download audit event is recorded on each call. The PDF includes an appended audit trail page.GET Audit trail
Returns the full chronological event log for the submission. Use this to prove signing sequence, timestamps, and IP addresses in legal proceedings.
{
"submissionId": "sub_01HZ8P4NKQR2V3M",
"events": [
{
"id": "evt_01HZ...",
"eventType": "SUBMISSION_CREATED",
"signerId": null,
"ipAddress": "203.0.113.1",
"createdAt": "2026-06-28T10:30:00Z"
},
{
"id": "evt_02HZ...",
"eventType": "INVITATION_VIEWED",
"signerId": "sgn_01HZ...",
"ipAddress": "198.51.100.42",
"createdAt": "2026-06-28T11:10:00Z"
},
{
"id": "evt_03HZ...",
"eventType": "DOCUMENT_SIGNED",
"signerId": "sgn_01HZ...",
"ipAddress": "198.51.100.42",
"createdAt": "2026-06-28T11:15:00Z"
},
{
"id": "evt_04HZ...",
"eventType": "DOCUMENT_COMPLETED",
"signerId": null,
"ipAddress": null,
"createdAt": "2026-06-28T11:15:01Z"
}
]
}DELETE Void a submission
Voids an in-progress submission. Signing links are immediately invalidated. Cannot void a submission with status === "COMPLETED" or "VOIDED".
{ "success": true }Webhooks
Webhooks deliver real-time HTTP POST notifications to your server when submission or signer events occur. This is more reliable than polling — configure a webhook once and receive events as they happen.
POST Create a webhook
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS URL that will receive POST requests. Must be publicly reachable. |
events | string[] | Yes | Array of event types to subscribe to. At least one required. |
secret | string | No | Signing secret for payload verification. Min 8 characters. Auto-generated if omitted. |
curl -X POST https://docura.cloud/api/v1/webhooks \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/docura",
"events": [
"submission.completed",
"signer.signed",
"submission.expired"
]
}'{
"id": "wh_01HZ...",
"url": "https://your-app.com/webhooks/docura",
"events": ["submission.completed", "signer.signed", "submission.expired"],
"secret": "whs_auto_generated_secure_value",
"isActive": true,
"createdAt": "2026-06-28T09:00:00Z"
}secret immediately — it is only returned on creation. Store it securely (environment variable, secrets manager). You need it to verify payload signatures.GET List webhooks
{
"data": [
{
"id": "wh_01HZ...",
"url": "https://your-app.com/webhooks/docura",
"events": ["submission.completed", "signer.signed"],
"isActive": true,
"createdAt": "2026-06-01T09:00:00Z"
}
]
}PUT Update a webhook
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | No | New destination URL. |
events | string[] | No | Replace the subscribed event types. Min 1. |
isActive | boolean | No | Enable or disable the webhook without deleting it. |
DELETE Delete a webhook
{ "success": true }Event types
Subscribe to any combination of the following events:
| Event | When it fires |
|---|---|
submission.created | A new submission is created |
submission.completed | All signers have signed |
submission.expired | The signing deadline passed without completion |
submission.declined | Any signer declined to sign |
submission.voided | The submission was voided via API or dashboard |
signer.viewed | A signer opened their unique signing link |
signer.signed | A signer completed all required fields |
signer.declined | A signer declined to sign |
Verifying payloads
Every webhook request includes a X-Docura-Signature header — an HMAC-SHA256 of the raw request body signed with your webhook secret. Always verify it before processing the payload.
import crypto from "crypto";
export function verifyWebhook(
rawBody: string, // raw request body (string, not parsed JSON)
signature: string, // X-Docura-Signature header value
secret: string // your webhook secret
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody, "utf8")
.digest("hex");
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(signature, "hex")
);
}
// Express.js example
app.post("/webhooks/docura", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-docura-signature"] as string;
const valid = verifyWebhook(req.body.toString(), sig, process.env.WEBHOOK_SECRET!);
if (!valid) return res.status(401).send("Invalid signature");
const event = JSON.parse(req.body);
if (event.event === "submission.completed") {
// Download and store the signed PDF
}
res.sendStatus(200);
});import hmac, hashlib, json
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret"
@app.route("/webhooks/docura", methods=["POST"])
def handle_webhook():
sig = request.headers.get("X-Docura-Signature", "")
expected = hmac.new(
WEBHOOK_SECRET.encode(),
request.get_data(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
event = request.get_json()
if event["event"] == "submission.completed":
print("Completed:", event["data"]["submissionId"])
return "", 200// Example webhook payload
{
"id": "evt_abc123nanoid",
"event": "submission.completed",
"createdAt": "2026-06-28T11:15:01Z",
"data": {
"submission": {
"id": "sub_01HZ8P4NKQR2V3M",
"status": "COMPLETED",
"templateId": "tmpl_01HZ...",
"completedAt": "2026-06-28T11:15:00Z",
"metadata": {
"customerId": "cust_abc123",
"dealId": "deal_789"
}
},
"signers": [
{
"email": "jane@acme.com",
"name": "Jane Smith",
"status": "SIGNED",
"signedAt": "2026-06-28T11:15:00Z"
}
]
}
}MCP Server
Overview
Docura ships a native Model Context Protocol (MCP) server — a standalone process that exposes your account to AI assistants like Claude Code and Cursor. Once connected, you can manage documents entirely through natural language without opening the dashboard.
Published on npm as docura-mcp. No build step required — npx downloads it automatically on first run. It authenticates with a standard Docura API key and requires no changes to your main app.
Setup
The Docura MCP server is published on npm as docura-mcp. No build step required — add it to your AI client config and npx downloads it automatically on first run.
Step 1 — Get an API key
Generate a key at Settings → API Keys. Keys start with sk_live_.
Step 2 — Register with Claude Code
Add to .claude/settings.local.json in your project root:
{
"mcpServers": {
"docura": {
"command": "npx",
"args": ["-y", "docura-mcp"],
"env": {
"DOCURA_API_KEY": "sk_live_your_key_here",
"DOCURA_BASE_URL": "https://docura.cloud"
}
}
}
}Step 2 (alternative) — Register with Cursor
Go to Cursor Settings → MCP and add the same JSON block above.
docura server will appear in the MCP tools panel.Available tools
| Tool | Description |
|---|---|
list_templates | List all PDF templates in your organization |
get_template | Get a template's details, fields, and signer roles |
list_submissions | List submissions, optionally filtered by status |
get_submission | Get status and signer progress for a submission |
create_submission | Send a template to one or more signers |
void_submission | Cancel a pending submission |
resend_invitation | Resend the signing email to a specific signer |
get_audit_trail | Full cryptographic event log with timestamps and IPs |
get_download_url | Get the URL to download a completed signed PDF |
list_webhooks | List all registered webhook endpoints |
test_webhook | Send a test event to verify a webhook is reachable |
Example prompts
Once connected, your AI assistant can handle document operations conversationally:
"List all my templates" → calls list_templates "Send the NDA template to alice@company.com for signing" → calls get_template, then create_submission "What's the status of submission sub_abc123?" → calls get_submission "Resend the invite to the second signer on the Smith contract" → calls get_submission, then resend_invitation "Get the audit trail for the Johnson agreement" → calls get_audit_trail "Void all pending submissions that expired last week" → calls list_submissions (status=PENDING), then void_submission for each
Reference
Field schema
When creating a template via API, the optional fields array defines interactive form fields placed on the PDF pages. All coordinate values are fractional percentages (0–1) of the page dimensions, making them resolution-independent.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique field identifier within the template. Any string — e.g. "field_1". |
type | enum | Yes | Field type. One of: "text", "signature", "initials", "date", "checkbox", "image", "select", "multiselect". |
signerRoleId | string | Yes | Role ID from the signerRoles array, or a placeholder: "__ROLE_0__", "__ROLE_1__", etc. |
page | integer | Yes | Zero-indexed page number (0 = first page). |
x | number | Yes | Left edge position as a fraction of page width. 0 = left, 1 = right. |
y | number | Yes | Top edge position as a fraction of page height. 0 = top, 1 = bottom. |
width | number | Yes | Field width as a fraction of page width. |
height | number | Yes | Field height as a fraction of page height. |
required | boolean | No | Whether the signer must complete this field. Defaults to true. |
label | string | No | Display label shown above the field in the signing UI. |
placeholder | string | No | Placeholder text for "text" fields. |
options | string[] | No | Options for "select" and "multiselect" field types. |
dateFormat | string | No | Date display format for "date" fields. Default: "MMM D, YYYY". |
fontSize | integer | No | Font size in points for text rendered into the final PDF. Default: 11. |
// Field schema examples
// Text field
{
"id": "field_name",
"type": "text",
"signerRoleId": "__ROLE_0__",
"page": 0,
"x": 0.1, "y": 0.12,
"width": 0.4, "height": 0.04,
"required": true,
"label": "Full Name",
"placeholder": "Your legal name",
"fontSize": 11
}
// Signature field
{
"id": "field_sig",
"type": "signature",
"signerRoleId": "__ROLE_0__",
"page": 1,
"x": 0.1, "y": 0.82,
"width": 0.35, "height": 0.07,
"required": true,
"label": "Signature"
}
// Date field
{
"id": "field_date",
"type": "date",
"signerRoleId": "__ROLE_0__",
"page": 1,
"x": 0.6, "y": 0.82,
"width": 0.25, "height": 0.04,
"required": true,
"label": "Date",
"dateFormat": "MMMM D, YYYY"
}
// Checkbox
{
"id": "field_agree",
"type": "checkbox",
"signerRoleId": "__ROLE_0__",
"page": 0,
"x": 0.08, "y": 0.55,
"width": 0.03, "height": 0.025,
"required": true,
"label": "I agree to the terms"
}
// Dropdown select
{
"id": "field_state",
"type": "select",
"signerRoleId": "__ROLE_0__",
"page": 0,
"x": 0.1, "y": 0.35,
"width": 0.3, "height": 0.04,
"required": false,
"label": "State",
"options": ["California", "New York", "Texas", "Other"]
}Status enums
Submission status
| Parameter | Type | Required | Description |
|---|---|---|---|
DRAFT | string | No | Created but not yet sent to signers. |
PENDING | string | No | Sent. Waiting for all signers to complete. |
COMPLETED | string | No | All signers have signed. PDF available for download. |
DECLINED | string | No | One or more signers declined to sign. |
EXPIRED | string | No | Signing deadline passed before completion. |
VOIDED | string | No | Cancelled via API or dashboard. |
Signer status
| Parameter | Type | Required | Description |
|---|---|---|---|
PENDING | string | No | Invitation not yet sent (sequential mode — waiting for prior signer). |
INVITED | string | No | Invitation email sent. Awaiting action. |
VIEWED | string | No | Signer opened the signing link. |
SIGNED | string | No | Signer completed all required fields. |
DECLINED | string | No | Signer declined to sign. |
Error codes
All errors return a consistent JSON shape:
{ "error": "Cannot void a completed submission." }| HTTP | Cause |
|---|---|
400 | Invalid request body or missing required parameters. See the error message for details. |
401 | Missing or invalid API key. Ensure Authorization: Bearer sk_live_... is present. |
403 | Plan restriction — API access requires Starter plan or above. |
404 | Resource not found or belongs to a different organisation. |
415 | Unsupported Media Type — endpoint expects multipart/form-data (template upload) or application/json. |
422 | Business logic error — e.g. voiding a completed submission, invalid PDF. |
429 | Rate limit exceeded. Wait until X-RateLimit-Reset before retrying. |
500 | Unexpected server error. Retrying with exponential backoff is safe. |
Rate limits
Requests are rate-limited per organisation. Response headers indicate your current usage:
X-RateLimit-Limit: 300 X-RateLimit-Remaining: 297 X-RateLimit-Reset: 1750000000 # Unix timestamp (seconds)
| Plan | Requests / min | Requests / day |
|---|---|---|
| Free | – | No API access |
| Starter | 300 | 5,000 |
| Professional | 600 | 20,000 |
| Enterprise | Custom | Custom |
X-RateLimit-Reset (Unix timestamp) to know exactly when the window resets. Implement exponential backoff with jitter for robust retry logic.