v1

API Reference

·https://docura.cloud/api/v1

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

bash
curl https://docura.cloud/api/v1/templates \
  -H "Authorization: Bearer sk_live_your_api_key"
API keys carry full access to your organisation's data. Never expose them in client-side code or public repositories. Rotate a key immediately if compromised via Settings → API Keys.

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

bash
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].id

Step 2 — Send for signing

bash
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

bash
# 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

text
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

POST/v1/templates

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

ParameterTypeRequiredDescription
fileFileYesThe PDF document. Must be a valid, non-encrypted PDF.
namestringYesTemplate name. 1–200 characters.
descriptionstringNoOptional description. Max 1,000 characters.
signerRolesJSONYesJSON array of signer role objects: [{name, color?}]. At least one role is required.
fieldsJSONNoJSON array of pre-placed field definitions. See Field schema reference. If omitted, fields can be placed in the dashboard builder.
metadataJSONNoFlat 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

bash
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)

bash
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"
Use __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

json
{
  "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)

typescript
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)

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

Docura does not perform HTML-to-PDF conversion itself. Use a PDF generation library in your stack, then upload the result via POST /v1/templates.

Node.js — Puppeteer (headless Chrome, pixel-perfect)

typescript
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)

python
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

LibraryRuntimeNotes
PuppeteerNode.jsChrome-based. Pixel-perfect rendering. Heavy (~200 MB).
PlaywrightNode.js / PythonSame quality as Puppeteer. Better API.
WeasyPrintPythonPure-Python CSS renderer. No browser needed. 10-100× faster.
wkhtmltopdfAny (CLI)Fast binary. Good for simple layouts. Limited CSS support.
PDFKitNode.jsProgrammatic PDF generation (no HTML). Full layout control.
GotenbergDocker sidecarREST API wrapper around Chrome. Great for production.

GET List templates

GET/v1/templates

Returns a paginated list of active templates for your organisation.

Query parameters

ParameterTypeRequiredDescription
pageintegerNoPage number. Defaults to 1.
limitintegerNoResults per page. 1–100. Defaults to 20.

Response

json
{
  "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

GET/v1/templates/:id
bash
curl https://docura.cloud/api/v1/templates/tmpl_01HZ... \
  -H "Authorization: Bearer sk_live_..."
json
{
  "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

PUT/v1/templates/:id

Update a template's name or description. All fields are optional.

ParameterTypeRequiredDescription
namestringNoTemplate name. 1–200 characters.
descriptionstringNoOptional description. Max 1,000 characters.
bash
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

DELETE/v1/templates/:id

Soft-deletes a template. It will no longer appear in lists or accept new submissions.

json
{ "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

POST/v1/templates/:id/submissions

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

ParameterTypeRequiredDescription
signersarrayYesArray of signer objects. At least one required.
signers[].roleIdstringYesThe signer role ID from the template.
signers[].emailstringYesSigner's email address. Invitation sent here.
signers[].namestringNoSigner's display name.
signingOrderenumNo"PARALLEL" (default) or "SEQUENTIAL". Sequential sends one at a time in role order.
messagestringNoCustom message included in the signing invitation email. Max 1,000 characters.
expiresInDaysintegerNoDays until the signing link expires. 1–365.
metadataobjectNoFlat 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.
bash
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

json
{
  "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"
}
Store the 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

GET/v1/submissions/:id

Returns full submission details including per-signer status. Poll this to track progress.

json
{
  "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

GET/v1/submissions/:id/download

Returns the finalised, signed PDF as a binary stream. The submission must have status === "COMPLETED".

bash
curl https://docura.cloud/api/v1/submissions/sub_01HZ.../download \
  -H "Authorization: Bearer sk_live_..." \
  --output signed-nda-jane-smith.pdf
Response Content-Type: application/pdf. A download audit event is recorded on each call. The PDF includes an appended audit trail page.

GET Audit trail

GET/v1/submissions/:id/audit

Returns the full chronological event log for the submission. Use this to prove signing sequence, timestamps, and IP addresses in legal proceedings.

json
{
  "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

DELETE/v1/submissions/:id

Voids an in-progress submission. Signing links are immediately invalidated. Cannot void a submission with status === "COMPLETED" or "VOIDED".

json
{ "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

POST/v1/webhooks
ParameterTypeRequiredDescription
urlstringYesHTTPS URL that will receive POST requests. Must be publicly reachable.
eventsstring[]YesArray of event types to subscribe to. At least one required.
secretstringNoSigning secret for payload verification. Min 8 characters. Auto-generated if omitted.
bash
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"
    ]
  }'
json
{
  "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"
}
Copy the 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

GET/v1/webhooks
json
{
  "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

PUT/v1/webhooks/:id
ParameterTypeRequiredDescription
urlstringNoNew destination URL.
eventsstring[]NoReplace the subscribed event types. Min 1.
isActivebooleanNoEnable or disable the webhook without deleting it.

DELETE Delete a webhook

DELETE/v1/webhooks/:id
json
{ "success": true }

Event types

Subscribe to any combination of the following events:

EventWhen it fires
submission.createdA new submission is created
submission.completedAll signers have signed
submission.expiredThe signing deadline passed without completion
submission.declinedAny signer declined to sign
submission.voidedThe submission was voided via API or dashboard
signer.viewedA signer opened their unique signing link
signer.signedA signer completed all required fields
signer.declinedA 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.

typescript
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);
});
python
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
json
// 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.

MCP access requires Starter plan or above — the same plan that unlocks REST API access.

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:

json
{
  "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.

Restart your AI client after saving the config. The docura server will appear in the MCP tools panel.

Available tools

ToolDescription
list_templatesList all PDF templates in your organization
get_templateGet a template's details, fields, and signer roles
list_submissionsList submissions, optionally filtered by status
get_submissionGet status and signer progress for a submission
create_submissionSend a template to one or more signers
void_submissionCancel a pending submission
resend_invitationResend the signing email to a specific signer
get_audit_trailFull cryptographic event log with timestamps and IPs
get_download_urlGet the URL to download a completed signed PDF
list_webhooksList all registered webhook endpoints
test_webhookSend a test event to verify a webhook is reachable

Example prompts

Once connected, your AI assistant can handle document operations conversationally:

text
"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.

ParameterTypeRequiredDescription
idstringYesUnique field identifier within the template. Any string — e.g. "field_1".
typeenumYesField type. One of: "text", "signature", "initials", "date", "checkbox", "image", "select", "multiselect".
signerRoleIdstringYesRole ID from the signerRoles array, or a placeholder: "__ROLE_0__", "__ROLE_1__", etc.
pageintegerYesZero-indexed page number (0 = first page).
xnumberYesLeft edge position as a fraction of page width. 0 = left, 1 = right.
ynumberYesTop edge position as a fraction of page height. 0 = top, 1 = bottom.
widthnumberYesField width as a fraction of page width.
heightnumberYesField height as a fraction of page height.
requiredbooleanNoWhether the signer must complete this field. Defaults to true.
labelstringNoDisplay label shown above the field in the signing UI.
placeholderstringNoPlaceholder text for "text" fields.
optionsstring[]NoOptions for "select" and "multiselect" field types.
dateFormatstringNoDate display format for "date" fields. Default: "MMM D, YYYY".
fontSizeintegerNoFont size in points for text rendered into the final PDF. Default: 11.
json
// 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

ParameterTypeRequiredDescription
DRAFTstringNoCreated but not yet sent to signers.
PENDINGstringNoSent. Waiting for all signers to complete.
COMPLETEDstringNoAll signers have signed. PDF available for download.
DECLINEDstringNoOne or more signers declined to sign.
EXPIREDstringNoSigning deadline passed before completion.
VOIDEDstringNoCancelled via API or dashboard.

Signer status

ParameterTypeRequiredDescription
PENDINGstringNoInvitation not yet sent (sequential mode — waiting for prior signer).
INVITEDstringNoInvitation email sent. Awaiting action.
VIEWEDstringNoSigner opened the signing link.
SIGNEDstringNoSigner completed all required fields.
DECLINEDstringNoSigner declined to sign.

Error codes

All errors return a consistent JSON shape:

json
{ "error": "Cannot void a completed submission." }
HTTPCause
400Invalid request body or missing required parameters. See the error message for details.
401Missing or invalid API key. Ensure Authorization: Bearer sk_live_... is present.
403Plan restriction — API access requires Starter plan or above.
404Resource not found or belongs to a different organisation.
415Unsupported Media Type — endpoint expects multipart/form-data (template upload) or application/json.
422Business logic error — e.g. voiding a completed submission, invalid PDF.
429Rate limit exceeded. Wait until X-RateLimit-Reset before retrying.
500Unexpected server error. Retrying with exponential backoff is safe.

Rate limits

Requests are rate-limited per organisation. Response headers indicate your current usage:

text
X-RateLimit-Limit:     300
X-RateLimit-Remaining: 297
X-RateLimit-Reset:     1750000000   # Unix timestamp (seconds)
PlanRequests / minRequests / day
FreeNo API access
Starter3005,000
Professional60020,000
EnterpriseCustomCustom
When you hit a 429, check X-RateLimit-Reset (Unix timestamp) to know exactly when the window resets. Implement exponential backoff with jitter for robust retry logic.