Code Examples
Copy-paste integration samples. Get e-signatures working in your stack in minutes.
cURL
The fastest way to test the API. Works on macOS, Linux, and Windows (with Git Bash or WSL).
Send a document for signing
# Encode PDF to base64
PDF_B64=$(base64 -w 0 contract.pdf) # Linux
# PDF_B64=$(base64 -i contract.pdf) # macOS
curl -X POST https://signforge.io/api/v1/quick-sign \
-H "X-API-Key: sf_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"Service Agreement\",
\"pdf_base64\": \"$PDF_B64\",
\"signer_email\": \"client@example.com\",
\"signer_name\": \"Jane Doe\"
}"Check envelope status
curl https://signforge.io/api/v1/envelopes/ENVELOPE_ID \
-H "X-API-Key: sf_live_YOUR_KEY"Download signed PDF
curl https://signforge.io/api/v1/envelopes/ENVELOPE_ID/documents/signed \
-H "X-API-Key: sf_live_YOUR_KEY" \
-o signed_contract.pdfList all envelopes
curl "https://signforge.io/api/v1/envelopes?status=completed&limit=20" \
-H "X-API-Key: sf_live_YOUR_KEY"Python
Full workflow: create, send, poll for completion, and download the signed document.
import requests
import base64
import time
API_KEY = "sf_live_YOUR_KEY"
BASE = "https://signforge.io/api/v1"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
# 1. Send for signing
with open("contract.pdf", "rb") as f:
pdf_b64 = base64.b64encode(f.read()).decode()
resp = requests.post(f"{BASE}/quick-sign", headers=HEADERS, json={
"title": "Consulting Agreement",
"pdf_base64": pdf_b64,
"signer_email": "client@company.com",
"signer_name": "Alex Chen",
})
data = resp.json()
envelope_id = data["envelope_id"]
print(f"Sent! Envelope: {envelope_id}")
# 2. Poll for completion
while True:
status = requests.get(
f"{BASE}/envelopes/{envelope_id}", headers=HEADERS
).json()
print(f" Status: {status['status']}")
if status["status"] == "completed":
break
time.sleep(30) # Check every 30 seconds
# 3. Download signed PDF
signed = requests.get(
f"{BASE}/envelopes/{envelope_id}/documents/signed",
headers={"X-API-Key": API_KEY},
)
with open("signed_contract.pdf", "wb") as f:
f.write(signed.content)
print("Downloaded signed PDF!")Tip: For production, use webhooks instead of polling. Register a webhook for envelope.completed and you'll get a POST request as soon as the document is signed.
Node.js
Express webhook handler that receives signing events and saves the signed PDF.
const express = require("express");
const crypto = require("crypto");
const fs = require("fs");
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = "your_webhook_secret";
const API_KEY = "sf_live_YOUR_KEY";
const BASE = "https://signforge.io/api/v1";
// Verify webhook signature
function verifySignature(body, signature) {
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(JSON.stringify(body))
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
app.post("/webhook", async (req, res) => {
const sig = req.headers["x-webhook-signature"];
if (!verifySignature(req.body, sig)) {
return res.status(401).send("Invalid signature");
}
const { event, envelope_id, data } = req.body;
console.log(`Event: ${event} for envelope ${envelope_id}`);
if (event === "envelope.completed") {
// Download the signed PDF
const response = await fetch(
`${BASE}/envelopes/${envelope_id}/documents/signed`,
{ headers: { "X-API-Key": API_KEY } }
);
const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync(`signed_${envelope_id}.pdf`, buffer);
console.log("Saved signed PDF!");
}
res.sendStatus(200);
});
app.listen(3000, () => console.log("Webhook server on port 3000"));Verify a Signed Document
Upload a signed PDF to check authenticity, or look up by document hash in the transparency log. No API key required.
Python — Upload verification
import requests
# Upload a signed PDF to verify
with open("signed_contract.pdf", "rb") as f:
resp = requests.post(
"https://signforge.io/api/verify/upload",
files={"file": ("signed_contract.pdf", f, "application/pdf")},
)
result = resp.json()
if result["verified"]:
print(f"Document verified via {result['method']}")
print(f"Signed by: {result['signer']['name']} ({result['signer']['email']})")
print(f"Signed at: {result['signed_at']}")
if result.get("transparency_log"):
print(f"Log entry: #{result['transparency_log']['entry_id']}")
else:
print("Document could not be verified")cURL — Hash-based transparency log lookup
# Compute SHA-256 of a signed PDF
HASH=$(sha256sum signed_contract.pdf | cut -d' ' -f1)
# Look up in the transparency log
curl https://signforge.io/api/transparency/hash/$HASH
# Or verify by code (printed on the signed PDF)
curl https://signforge.io/api/verify/sf_abc123def456Full docs: See the Verification API reference for offline verification scripts, response schemas, and the transparency log.
Template Workflow
Create templates in the dashboard once, then use the API to generate pre-filled envelopes for each new signer.
import requests
API_KEY = "sf_live_YOUR_KEY"
BASE = "https://signforge.io/api/v1"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
# 1. List available templates
resp = requests.get(f"{BASE}/templates?search=NDA", headers=HEADERS)
templates = resp.json()["items"]
print(f"Found {len(templates)} templates")
# 2. Pick the first matching template
template = templates[0]
print(f"Using: {template['name']} ({len(template.get('field_count', 0))} fields)")
# 3. Create envelope from template with prefilled data
resp = requests.post(
f"{BASE}/templates/{template['id']}/use",
headers=HEADERS,
json={
"recipients": [
{"email": "client@company.com", "name": "Jane Doe"}
],
"title": f"NDA — Jane Doe — April 2026",
"prefill_fields": {
"date": "2026-04-14",
"company_name": "Acme Corp",
"client_name": "Jane Doe",
},
"send_immediately": True,
},
)
data = resp.json()
print(f"Envelope created: {data['id']}")
print(f"Status: {data['status']}") # "sent" if send_immediately=TrueAnchor Field Placement
Place signature and form fields by finding text in the PDF instead of specifying coordinates. The API finds the anchor text and positions fields relative to it.
import requests, base64
API_KEY = "sf_live_YOUR_KEY"
BASE = "https://signforge.io/api/v1"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
# 1. Create an envelope with a PDF
with open("contract.pdf", "rb") as f:
pdf_b64 = base64.b64encode(f.read()).decode()
resp = requests.post(f"{BASE}/envelopes", headers=HEADERS, json={
"title": "Service Agreement",
"pdf_base64": pdf_b64,
"recipients": [{"email": "client@company.com", "name": "Jane Doe"}],
})
envelope_id = resp.json()["id"]
# 2. Place fields by finding text anchors
resp = requests.post(
f"{BASE}/envelopes/{envelope_id}/anchor-fields",
headers=HEADERS,
json={
"anchors": [
{
"anchor_text": "Signature:",
"field_type": "signature",
"recipient_index": 0,
"offset_x": 0.15, # 15% to the right of anchor
"offset_y": 0.01, # 1% below anchor
"width": 0.25,
"height": 0.04,
},
{
"anchor_text": "Date:",
"field_type": "date",
"recipient_index": 0,
"offset_x": 0.08,
"offset_y": 0.01,
},
{
"anchor_text": "Print Name:",
"field_type": "text",
"recipient_index": 0,
"offset_x": 0.18,
"offset_y": 0.01,
},
]
},
)
result = resp.json()
print(f"Created: {result['fields_created']} fields")
print(f"Not found: {result['fields_not_found']} anchors")
# 3. Send the envelope
requests.post(f"{BASE}/envelopes/{envelope_id}/send", headers=HEADERS)
print("Sent for signing!")AI Document Generation
Generate agreements, contracts, and forms using AI. This uses JWT authentication (dashboard session), not API keys.
import requests
# Note: AI endpoints use JWT auth (dashboard session), not API keys
JWT_TOKEN = "your_access_token"
BASE = "https://signforge.io/api"
HEADERS = {"Authorization": f"Bearer {JWT_TOKEN}", "Content-Type": "application/json"}
# 1. List available document types
types = requests.get(f"{BASE}/ai/document-types", headers=HEADERS).json()
for t in types:
print(f" {t['slug']}: {t['name']}")
# 2. Generate a document
resp = requests.post(f"{BASE}/ai/generate-document", headers=HEADERS, json={
"document_type": "nda",
"context": "Two-way NDA between Acme Corp and Beta LLC for a software partnership",
"party_names": ["Acme Corp", "Beta LLC"],
})
doc = resp.json()
print(f"Generated: {doc['title']}")
print(f"Pages: {doc['page_count']}")
# 3. Optionally revise
resp = requests.post(f"{BASE}/ai/revise-document", headers=HEADERS, json={
"document_id": doc["id"],
"revision_prompt": "Add a 2-year non-compete clause",
})
revised = resp.json()
print(f"Revised: {revised['title']}")Note: AI document generation is a paid feature (Pro+ plans). The API uses JWT authentication from the dashboard, not API keys. AI-generated documents automatically get signature fields placed.
n8n Workflow
Create an n8n workflow that sends a document for signing when a new row is added to Google Sheets.
Workflow overview
HTTP Request node config
{
"method": "POST",
"url": "https://signforge.io/api/v1/quick-sign",
"headers": {
"X-API-Key": "sf_live_YOUR_KEY",
"Content-Type": "application/json"
},
"body": {
"title": "={{ $json.document_title }}",
"pdf_base64": "={{ $json.pdf_content }}",
"signer_email": "={{ $json.signer_email }}",
"signer_name": "={{ $json.signer_name }}"
}
}Use the “Read Binary File” node before this to read a PDF from Google Drive or an upload, then convert to base64 with an expression.
Retool
Build an internal tool for sending contracts with a Retool form, table, and API queries.
Resource setup
Type: REST API
Base URL: https://signforge.io/api/v1
Headers: X-API-Key: sf_live_YOUR_KEY
Query: Send for signing
// Retool Query — sendForSigning
// Type: POST, Path: /quick-sign
{
"title": {{ textInput_title.value }},
"pdf_base64": {{ fileInput_pdf.value[0].base64 }},
"signer_email": {{ textInput_email.value }},
"signer_name": {{ textInput_name.value }}
}Query: List envelopes
// Retool Query — listEnvelopes
// Type: GET, Path: /envelopes?limit=50
// No body needed — bind the result to a Table componentWhatsApp Bot (Twilio)
Send signing links via WhatsApp using Twilio and the link delivery mode.
from flask import Flask, request
from twilio.rest import Client
import requests
import base64
app = Flask(__name__)
SIGNFORGE_KEY = "sf_live_YOUR_KEY"
BASE = "https://signforge.io/api/v1"
TWILIO_SID = "YOUR_SID"
TWILIO_TOKEN = "YOUR_TOKEN"
TWILIO_FROM = "whatsapp:+14155238886"
twilio = Client(TWILIO_SID, TWILIO_TOKEN)
@app.route("/whatsapp", methods=["POST"])
def handle_message():
body = request.form.get("Body", "").strip()
sender = request.form.get("From", "")
if body.lower().startswith("sign "):
# Parse: "sign Jane jane@example.com"
parts = body.split(" ", 2)
if len(parts) < 3:
reply = "Usage: sign <Name> <email>"
else:
name, email = parts[1], parts[2]
# Create envelope with link delivery (no email)
with open("template_nda.pdf", "rb") as f:
pdf_b64 = base64.b64encode(f.read()).decode()
resp = requests.post(f"{BASE}/quick-sign", json={
"title": "NDA Agreement",
"pdf_base64": pdf_b64,
"signer_email": email,
"signer_name": name,
}, headers={
"X-API-Key": SIGNFORGE_KEY,
"Content-Type": "application/json",
})
data = resp.json()
signing_url = data.get("signing_url", "")
reply = f"NDA sent! Signing link:\n{signing_url}"
# Also send via WhatsApp
twilio.messages.create(
body=f"Hi {name}, please sign your NDA:\n{signing_url}",
from_=TWILIO_FROM,
to=f"whatsapp:{email}", # Replace with phone
)
else:
reply = "Send 'sign <Name> <email>' to create an NDA."
twilio.messages.create(body=reply, from_=TWILIO_FROM, to=sender)
return "OK", 200Airtable Script
Run this script in Airtable's Scripting extension to send documents from your base for signing.
// Airtable Scripting Extension
const API_KEY = "sf_live_YOUR_KEY";
const BASE_URL = "https://signforge.io/api/v1";
// Get the selected record
const table = base.getTable("Contracts");
const record = await input.recordAsync("Select a contract:", table);
if (!record) {
output.text("No record selected.");
} else {
const title = record.getCellValueAsString("Contract Title");
const email = record.getCellValueAsString("Signer Email");
const name = record.getCellValueAsString("Signer Name");
const attachments = record.getCellValue("PDF");
if (!attachments || attachments.length === 0) {
output.text("No PDF attached to this record.");
} else {
// Download the PDF and convert to base64
const pdfUrl = attachments[0].url;
const pdfResp = await fetch(pdfUrl);
const pdfBuffer = await pdfResp.arrayBuffer();
const pdfBytes = new Uint8Array(pdfBuffer);
// Convert to base64
let binary = "";
for (let i = 0; i < pdfBytes.length; i++) {
binary += String.fromCharCode(pdfBytes[i]);
}
const pdfBase64 = btoa(binary);
// Send for signing
const resp = await fetch(BASE_URL + "/quick-sign", {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: title,
pdf_base64: pdfBase64,
signer_email: email,
signer_name: name,
}),
});
const data = await resp.json();
// Update the record with envelope ID
await table.updateRecordAsync(record, {
"Envelope ID": data.envelope_id,
"Status": data.status,
});
output.text(
"Sent for signing!\n" +
"Envelope ID: " + data.envelope_id + "\n" +
"Status: " + data.status
);
}
}Batch Shell Script
Send multiple documents from a CSV file. Each row: title, email, name, pdf_path.
#!/bin/bash
# batch_sign.sh — Send documents from CSV
# Usage: ./batch_sign.sh contracts.csv
API_KEY="sf_live_YOUR_KEY"
BASE="https://signforge.io/api/v1"
CSV_FILE="$1"
if [ -z "$CSV_FILE" ]; then
echo "Usage: $0 <csv_file>"
exit 1
fi
# Skip header row, process each line
tail -n +2 "$CSV_FILE" | while IFS=, read -r title email name pdf_path; do
echo "Sending: $title to $email..."
# Encode PDF
PDF_B64=$(base64 -w 0 "$pdf_path" 2>/dev/null || base64 -i "$pdf_path")
# Send via API
RESPONSE=$(curl -s -X POST "$BASE/quick-sign" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"$title\",
\"pdf_base64\": \"$PDF_B64\",
\"signer_email\": \"$email\",
\"signer_name\": \"$name\"
}")
ENVELOPE_ID=$(echo "$RESPONSE" | grep -o '"envelope_id":"[^"]*"' | cut -d'"' -f4)
echo " -> Envelope: $ENVELOPE_ID"
# Rate limit courtesy (1 second between requests)
sleep 1
done
echo "Done! All documents sent."Example CSV
title,email,name,pdf_path
NDA Agreement,alice@company.com,Alice Smith,./docs/nda.pdf
Service Contract,bob@agency.com,Bob Johnson,./docs/service.pdf
Consulting Terms,carol@startup.io,Carol Lee,./docs/consulting.pdfAPI Reference
Full REST API documentation with endpoint reference.
Verification API
Verify signed documents — public, no auth required.
Embed Widget
Embed signing UIs in your app with an iframe and JS SDK.
MCP Server
Use SignForge from Claude Desktop and AI agents.