API v1 — Stable

Developer API

Send documents for e-signature, embed signing UIs, and automate workflows. The easiest way to add legally binding signatures to your app.

Getting Started

1

Create an account

Sign up free at signforge.io/register

2

Generate an API key

Go to Dashboard → Developers and create a live or test key.

3

Make your first request

Send a document for signing with a single API call:

bash
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": "NDA Agreement",
    "pdf_base64": "<base64-encoded-pdf>",
    "signer_email": "jane@example.com",
    "signer_name": "Jane Doe"
  }'

Authentication

All V1 API endpoints authenticate via the X-API-Key header.

Live keys

Prefix: sf_live_

Create real envelopes, send emails, consume quota.

Test keys

Prefix: sf_test_

Sandbox mode — watermarked PDFs, no emails sent, no quota consumed.

Sandbox Mode

Test keys (prefixed sf_test_) enable sandbox mode for safe development and testing.

Watermarked PDFs

Signed PDFs include a visible "SANDBOX" watermark so they can't be mistaken for production documents.

No emails sent

Signing invitations are suppressed. Use the signing_url from the API response to test the signing flow.

No quota consumed

Sandbox operations don't count against your monthly envelope quota.

Endpoints Reference

Envelopes

POST/api/v1/envelopesCreate envelope
POST/api/v1/envelopes/{id}/sendSend for signing
GET/api/v1/envelopes/{id}Get status
GET/api/v1/envelopesList envelopes
GET/api/v1/envelopes/{id}/documents/{kind}Download PDF
GET/api/v1/envelopes/{id}/archiveDownload ZIP archive
POST/api/v1/envelopes/{id}/voidVoid envelope
DELETE/api/v1/envelopes/{id}Delete
POST/api/v1/quick-signOne-call sign
POST/api/v1/envelopes/{id}/embed-urlGet embed URL
POST/api/v1/envelopes/{id}/anchor-fieldsPlace fields by text anchor

Templates

GET/api/v1/templatesList templates
GET/api/v1/templates/{id}Get with fields
POST/api/v1/templates/{id}/useCreate envelope from template

Bulk Signing

POST/api/v1/bulk-sign-sessionsOne authority signs N envelopes in one session
GET/api/v1/bulk-sign-sessions/{id}Session status + per-envelope results

Webhooks

POST/api/v1/webhooksRegister
GET/api/v1/webhooksList
PATCH/api/v1/webhooks/{id}Update
DELETE/api/v1/webhooks/{id}Delete
GET/api/v1/webhooks/{id}/deliveriesDelivery log

Events include bulk_sign.completed and bulk_sign.failed alongside the envelope lifecycle events.

Templates

Create reusable templates in the dashboard, then generate pre-filled envelopes via the API. Templates preserve field placements so you don't need to specify coordinates each time.

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"}

# List available templates
templates = requests.get(f"{BASE}/templates", headers=HEADERS).json()
template = templates["items"][0]
print(f"Using template: {template['name']}")

# Create an envelope from template with prefilled fields
resp = requests.post(
    f"{BASE}/templates/{template['id']}/use",
    headers=HEADERS,
    json={
        "recipients": [
            {"email": "client@company.com", "name": "Jane Doe"}
        ],
        "title": "NDA — Jane Doe",
        "prefill_fields": {
            "date": "2026-04-14",
            "company_name": "Acme Corp"
        },
        "send_immediately": True,
    },
)
data = resp.json()
print(f"Envelope: {data['id']}, Status: {data['status']}")

Anchor-Based Field Placement

Instead of specifying exact coordinates, find text in the PDF and place fields relative to it. The API searches for the anchor text and positions the field automatically.

python
# Place fields by finding text anchors in the PDF
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,
                "offset_y": 0.01,
                "width": 0.25,
                "height": 0.04,
            },
            {
                "anchor_text": "Date:",
                "field_type": "date",
                "recipient_index": 0,
                "offset_x": 0.08,
                "offset_y": 0.01,
            },
        ]
    },
)
result = resp.json()
print(f"Created {result['fields_created']} fields, {result['fields_not_found']} not found")

Tip: Anchor placement works best with unique text labels like “Signature:” or “Client Name:”. If the text appears multiple times, specify page_index to narrow the search.

Quick Sign

The fastest way to get a document signed. One call creates the envelope, uploads the PDF, adds the signer, and sends the signing email.

python
import requests, base64

with open("contract.pdf", "rb") as f:
    pdf_b64 = base64.b64encode(f.read()).decode()

response = requests.post(
    "https://signforge.io/api/v1/quick-sign",
    headers={"X-API-Key": "sf_live_YOUR_KEY"},
    json={
        "title": "Service Agreement",
        "pdf_base64": pdf_b64,
        "signer_email": "client@company.com",
        "signer_name": "Alex Chen",
    },
)

data = response.json()
print(f"Envelope: {data['envelope_id']}")
print(f"Status: {data['status']}")

Bulk Signing

Send personalised contracts at scale, then have your authorised signatory sign all of them in a single, authenticated session — no signing one-by-one, no pasted signature image. Every document still gets its own cryptographic mark, bound to that exact file, with portable proof anyone can verify.

Different from “bulk send”: Bulk Send fans one document out to many recipients. Bulk Signing lets one authorised person execute many documents at once — something DocuSign and Adobe bulk-send can't do without forcing your signer to click every document, or pasting a static signature image (which isn't a real, per-document signature).

  1. Create your envelopes in bulk (each with the company block and the recipient's block) via POST /api/v1/envelopes/batch.
  2. Open one bulk-sign session for the authority across all of them.
  3. Your authorised signer opens one link, reviews a representative copy + the full batch manifest, and consents once.
  4. SignForge applies the company's mark to every document and routes each to its recipient. A bulk_sign.completed webhook fires when done.
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"}

# 'envelope_ids' is your batch of two-signer envelopes (company = signing_order 1,
# recipient = signing_order 2, sequential, anchored on both blocks).
resp = requests.post(
    f"{BASE}/bulk-sign-sessions",
    headers=HEADERS,
    json={
        "signer_email": "vphr@yourco.com",
        "signer_name": "Asha Verma",
        "envelope_ids": envelope_ids,           # or "batch_id": "..."
        "representative_envelope_id": envelope_ids[0],
        "consent_statement": "I, as authorised signatory, execute the company "
                             "block on all letters in this batch.",
    },
).json()

print(resp["sign_url"])        # hand this to your authorised signer
print(resp["envelope_count"])  # e.g. 700

# Poll progress (or subscribe to bulk_sign.completed / bulk_sign.failed)
status = requests.get(
    f"{BASE}/bulk-sign-sessions/{resp['session_id']}", headers=HEADERS
).json()
print(status["signed"], "/", status["total"])

The company's mark is recorded as an advanced electronic seal (eIDAS concept; recognised as an electronic signature/seal under eIDAS, ESIGN/UETA, and India's IT Act, by concept). Each document stays independently verifiable and references the one batch consent in its proof.

Webhooks

Get real-time notifications when envelopes change state. All webhook payloads are signed with HMAC-SHA256.

Events

envelope.created

Envelope was created

envelope.sent

Envelope sent for signing

envelope.completed

All recipients signed

envelope.voided

Sender voided the envelope

envelope.expired

Envelope reached expiry

recipient.viewed

Recipient opened document

recipient.signed

Recipient completed signing

recipient.declined

Recipient declined to sign

Payload Format

json
{
  "event": "envelope.completed",
  "envelope_id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2026-03-25T12:00:00Z",
  "data": {
    "title": "Service Agreement",
    "status": "completed",
    "recipients": [
      {
        "email": "signer@example.com",
        "name": "Jane Doe",
        "status": "signed",
        "signed_at": "2026-03-25T11:59:00Z"
      }
    ],
    "download_urls": {
      "signed": "/api/v1/envelopes/{id}/documents/signed",
      "certificate": "/api/v1/envelopes/{id}/documents/certificate"
    }
  }
}

HMAC Verification

javascript
const crypto = require("crypto");

function verifyWebhook(body, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// In your Express handler:
app.post("/webhook", (req, res) => {
  const sig = req.headers["x-webhook-signature"];
  if (!verifyWebhook(req.rawBody, sig, WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }
  const event = req.body;
  console.log("Event:", event.event, event.envelope_id);
  res.sendStatus(200);
});

Rate Limits & Quotas

PlanAPI Envelopes / MonthRate Limit
Free560 req/min
Pro10060 req/min
Business50060 req/min
EnterpriseUnlimitedCustom

Test/sandbox keys are exempt from monthly quotas but still subject to rate limits.

Error Codes

CodeMeaning
400Invalid request — check the request body
401Missing or invalid API key
403Account suspended or feature not available
404Resource not found
422Validation error — details in response
429Rate limit or quota exceeded
500Internal server error

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.

Code Examples

Integration samples for n8n, Retool, cURL, and more.