API docsManage API keys ›Send documents for signature programmatically: upload a PDF, place signature fields, and send an envelope to one or more recipients.
Authenticate every request with an API key as a bearer token:
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxCreate a key under Security → API keys. The full key is shown once — copy it immediately. Revoke a key any time; revoked keys return 401 instantly. Keys belong to your organisation, not a single user — keep them secret.
Base URL:
https://siggy.com.au/api/be/v1multipart/form-data with one file field (PDF, max 25 MB).
curl -X POST $BASE/documents \
-H "Authorization: Bearer $SIGGY_API_KEY" \
-F "[email protected];type=application/pdf"
# → { "id": "f1e2d3c4-…", "page_count": 3 }Creates a draft from a document, places fields, and sends it immediately. Returns a signing link per recipient (signers are also emailed automatically).
curl -X POST $BASE/envelopes \
-H "Authorization: Bearer $SIGGY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"document_id": "f1e2d3c4-…",
"title": "Engagement letter — Acme",
"routing_mode": "sequential",
"recipients": [
{ "name": "Jane Smith", "email": "[email protected]", "routing_order": 1 }
],
"fields": [
{ "recipient_index": 0, "type": "signature", "page": 3,
"x": 0.10, "y": 0.80, "w": 0.30, "h": 0.08 },
{ "recipient_index": 0, "type": "date_signed", "page": 3,
"x": 0.55, "y": 0.80, "w": 0.20, "h": 0.04 }
]
}'
# → { "id": "9a8b…", "status": "sent",
# "recipients": [ { "name": "Jane Smith", "email": "[email protected]",
# "sign_url": "https://siggy.com.au/sign/AbC123…" } ] }| Recipient | Notes |
|---|---|
| name, email | Required. |
| routing_order | Default 1. Lower signs first when sequential. |
| role | signer (default), in_person, copy. |
| access_code | Optional 2FA code. Professional+ only. |
| Field | Notes |
|---|---|
| recipient_index | 0-based index into recipients[]. |
| type | signature, initials, date_signed, name, email, company, title, date, text, number, checkbox. |
| page | 1-based page number. |
| x, y, w, h | Normalised 0–1, top-left origin (see Coordinates). |
| required | Default true. |
curl $BASE/envelopes/9a8b… -H "Authorization: Bearer $SIGGY_API_KEY"
# → { "id": "9a8b…", "title": "…", "status": "sent",
# "recipients": [ { "name": "Jane Smith", "email": "[email protected]", "status": "sent" } ] }Envelope status: sent → in_progress → completed (or declined / voided). Recipient status: pending → sent → signed.
curl "$BASE/envelopes?limit=25&offset=0&status=completed" \
-H "Authorization: Bearer $SIGGY_API_KEY"
# → { "data": [ { "id": "…", "title": "…", "status": "completed", … } ],
# "has_more": false, "limit": 25, "offset": 0 }Download the tamper-evident sealed PDF and the completion certificate (both available once the envelope is completed; 409 until then). Returns application/pdf.
Remind resends the signing email to whoever the envelope is waiting on. Void cancels an in-flight envelope and invalidates every signing link (audited; pass an optional { "reason": "…" }). Both return 409 once the envelope is completed, declined, or voided.
The recommended integration pattern. Design the document and field layout once in Templates, then send it programmatically with just a name and email — no field coordinates in your code. Provide exactly one recipient per template role, in role order.
curl "$BASE/templates" -H "Authorization: Bearer $SIGGY_API_KEY"
# → { "data": [ { "id": "tpl-…", "name": "Engagement letter",
# "roles": [ { "role": "signer", "routing_order": 1 } ] } ] }
curl -X POST "$BASE/templates/tpl-…/send" \
-H "Authorization: Bearer $SIGGY_API_KEY" -H "Content-Type: application/json" \
-d '{ "title": "Engagement letter — Acme",
"recipients": [ { "name": "Jane Smith", "email": "[email protected]" } ] }'
# → { "id": "9a8b…", "status": "sent", "recipients": [ { …, "sign_url": "…" } ] }Field positions are normalised so they're independent of page size or zoom. (x, y) is the top-left corner — (0,0) is the top-left of the page, (1,1) the bottom-right. w/h are fractions of the page, and x + w / y + h must stay ≤ 1.
Each envelope counts against your plan's monthly quota. When it's spent, POST /v1/envelopes returns 402 until next month or an upgrade.
| 400 | Invalid request — bad PDF, field geometry, or recipient. |
| 401 | Missing, unknown, or revoked API key. |
| 402 | Plan lacks API access, quota spent, or access code without Professional+. |
| 404 | Envelope not found (or not yours). |
| 413 | File too large (> 25 MB). |
| 422 | File failed the malware scan. |
All errors return { "detail": "…" }.
Instead of polling, register a webhook endpoint (Security → Webhooks) and Siggy will POST a signed JSON event to your URL when things happen. Each endpoint gets its own signing secret (whsec_…).
Events
envelope.sent, recipient.signed, envelope.completed, envelope.declined.
Payload
POST https://your-app.com/webhooks/siggy
Siggy-Signature: t=1718000000,v1=3a8f... ← HMAC-SHA256 of "{t}.{body}"
{
"id": "evt_…",
"type": "envelope.completed",
"created": 1718000000,
"data": {
"envelope": { "id": "…", "title": "…", "status": "completed", "document_id": "…" },
"recipients": [ { "id": "…", "name": "Jane", "email": "[email protected]", "status": "signed" } ]
}
}Verify the signature
Compute HMAC-SHA256(secret, "{t}.{raw_body}") and compare to the v1 value in the Siggy-Signature header (constant-time). Respond with 2xx to acknowledge — non-2xx responses are retried with backoff.
import hmac, hashlib # Python example
t, v1 = parse(header) # "t=...,v1=..."
expected = hmac.new(secret.encode(), f"{t}.{raw_body}".encode(), hashlib.sha256).hexdigest()
assert hmac.compare_digest(expected, v1)The pattern is the same everywhere: design a template in Siggy once → your platform calls POST /v1/templates/{id}/send → a webhook tells you when it's signed.
import requests
BASE = "https://siggy.com.au/api/be/v1"
H = {"Authorization": "Bearer sk_live_…"}
templates = requests.get(f"{BASE}/templates", headers=H).json()["data"]
tpl = next(t for t in templates if t["name"] == "Engagement letter")
sent = requests.post(f"{BASE}/templates/{tpl['id']}/send", headers=H, json={
"title": "Engagement letter — Acme",
"recipients": [{"name": "Jane Smith", "email": "[email protected]"}],
}).json()
print(sent["id"], sent["recipients"][0]["sign_url"])const BASE = "https://siggy.com.au/api/be/v1";
const H = { Authorization: `Bearer ${process.env.SIGGY_API_KEY}`,
"content-type": "application/json" };
const { data } = await fetch(`${BASE}/templates`, { headers: H }).then(r => r.json());
const tpl = data.find(t => t.name === "Engagement letter");
const sent = await fetch(`${BASE}/templates/${tpl.id}/send`, {
method: "POST", headers: H,
body: JSON.stringify({ recipients: [{ name: "Jane Smith", email: "[email protected]" }] }),
}).then(r => r.json());envelope.completed and friends.…/v1/templates/{id}/send with your Authorization header and the recipient mapped from any of Zapier's 6,000+ apps (new HubSpot contact, Google Sheets row, Xero invoice…).Same two halves: a custom webhook / "when an HTTP request is received" module as the trigger (paste its URL into Siggy webhooks), and an HTTP request module for sending. Typical Power Automate flow: new file in SharePoint → upload via /v1/documents → send → save the signed PDF back on envelope.completed.