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"));

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.

Embed Widget

Embed signing UIs in your app with an iframe and JS SDK.

MCP Server

Use SignForge from Claude Desktop and AI agents.