Back to API Docs
Code Examples

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

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

bash
curl https://signforge.io/api/v1/envelopes/ENVELOPE_ID \
  -H "X-API-Key: sf_live_YOUR_KEY"

Download signed PDF

bash
curl https://signforge.io/api/v1/envelopes/ENVELOPE_ID/documents/signed \
  -H "X-API-Key: sf_live_YOUR_KEY" \
  -o signed_contract.pdf

List all envelopes

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

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

javascript
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

python
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

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

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

python
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=True

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

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

python
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

Google Sheets Trigger
HTTP Request (Read PDF)
HTTP Request (Quick Sign)
IF (Status Check)
Slack Notification

HTTP Request node config

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

javascript
// 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

javascript
// Retool Query — listEnvelopes
// Type: GET, Path: /envelopes?limit=50
// No body needed — bind the result to a Table component

WhatsApp Bot (Twilio)

Send signing links via WhatsApp using Twilio and the link delivery mode.

python
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", 200

Airtable Script

Run this script in Airtable's Scripting extension to send documents from your base for signing.

javascript
// 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.

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

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

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