Getting started

API Reference

v1 · additive-only

Send documents for signature, embed signing in your own app, and receive signed webhook events. Base URL https://www.sigpen.com/api/v1

Per-resource reference

Generated from the OpenAPI spec

Quick Start

#

Send your first document for signature in 3 steps. Copy any tab and run it.

1

Upload a document

POST a base64-encoded PDF (up to 25 MB). The response includes the documentidyou'll use in step 2.

POST /api/v1/documents
curl -X POST https://www.sigpen.com/api/v1/documents \
  -H "Authorization: Bearer sp_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "file": "'$(base64 -i contract.pdf)'",
    "file_name": "contract.pdf",
    "title": "Sales Agreement"
  }'
201 · RESPONSE
{
  "id": "cm9abc123...",
  "title": "Sales Agreement",
  "file_name": "contract.pdf",
  "status": "draft",
  "metadata": {},
  "created_at": "2026-03-30T12:00:00.000Z",
  "updated_at": "2026-03-30T12:00:00.000Z"
}
2

Send for signature

Each signer gets an invitation email with a signing link. Unsigned signers are reminded automatically after 48 hours.

Want to place signature boxes without the visual editor? Pass a fields array on each signer. See Signing for inline field placement.

POST /api/v1/documents/:id/send
curl -X POST https://www.sigpen.com/api/v1/documents/DOC_ID/send \
  -H "Authorization: Bearer sp_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "signers": [
      { "email": "jane@example.com", "name": "Jane Smith" }
    ],
    "message": "Please sign this agreement.",
    "expires_in_days": 30
  }'
200 · RESPONSE
{
  "id": "cm9abc123...",
  "status": "sent",
  "signers": [
    {
      "id": "cm9xyz789...",
      "email": "jane@example.com",
      "name": "Jane Smith",
      "status": "pending",
      "order": 1,
      "signed_at": null,
      "viewed_at": null
    }
  ],
  "expires_at": "2026-04-29T12:00:00.000Z",
  "sent_at": "2026-03-30T12:00:00.000Z"
}
3

Download the signed copy

Returns the signed PDF binary. Only available once the document status iscompleted(all signers have signed). Subscribe to thedocument.completedwebhook to know the moment that happens.

GET /api/v1/documents/:id/download
curl https://www.sigpen.com/api/v1/documents/DOC_ID/download \
  -H "Authorization: Bearer sp_live_YOUR_KEY" \
  -o signed-contract.pdf

Authentication

#

All API requests require an API key passed as a Bearer token in the Authorization header. You can also use the X-API-Key header if you prefer.

Test mode: Use keys starting with sp_test_ for development. Test documents don't count toward your monthly limit. Live keys (sp_live_) require a verified account email; requests from unverified accounts return 403 EMAIL_NOT_VERIFIED.

API key prefixes

PrefixModeUsage
sp_live_ProductionReal documents, counted toward limits
sp_test_TestDon’t count toward your monthly limit
Authenticated request
curl https://www.sigpen.com/api/v1/documents \
  -H "Authorization: Bearer sp_live_YOUR_KEY"

Rate limits

PlanRequests / minRequests / dayBurst / sec
Developer (Free)102003
Professional305005
Business1005,00015
Enterprise1005,00015

Test-mode keys (sp_test_) run in the sandbox and share a fixed limit of 10 req/min, 200 req/day, 3 req/sec regardless of plan. A 429 response includes a retry_after value (seconds) and X-RateLimit-* headers, honor retry_after and retry.

The Developer plan includes 10 live documents/month and up to 3 webhook endpoints; paid plans allow up to 20 webhook endpoints.

Webhooks Guide

#

Receive real-time HTTP callbacks when document events occur. Manage endpoints via the Webhooks API or the Developer Dashboard. The HMAC secret is returned only at creation time, so store it securely.

Event types

EventFires when
document.sentDocument is sent for signing
document.viewedA signer opens the signing page for the first time
document.signedAn individual signer completes their signature
document.completedAll signers have signed and the document is fully executed
document.declinedA signer declines to sign (this also voids the document)
document.voidedThe sender voids the document
document.expiredThe signing deadline passes without completion
test.webhookSent by POST /webhooks/:id/test so you can verify your endpoint

Delivery format

Every delivery POSTs a JSON envelope and carries four headers: X-SigPen-Signature, X-SigPen-Timestamp, X-SigPen-Event, and X-SigPen-Delivery-ID. Failed deliveries are retried 7 times over 24 hours (0s, 1m, 5m, 30m, 2h, 8h, 24h); after 10 consecutive exhausted deliveries the webhook is disabled automatically.

Always verify the signature to confirm the request came from SigPen. Compute the HMAC over the raw body bytes, not re-serialized JSON (key order and whitespace can differ).

WEBHOOK · delivery payload
{
  "id": "evt_01j9x8y7z6...",
  "type": "document.completed",
  "created_at": "2026-06-10T18:05:00.000Z",
  "data": {
    "id": "doc_123",
    "title": "Consulting Agreement",
    "status": "completed",
    "signers": [ ... ]
  }
}
Verify signatures
const crypto = require('crypto');

function verifyWebhook(body, signature, timestamp, secret) {
  const payload = `${timestamp}.${body}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return `sha256=${expected}` === signature;
}

// In your Express handler, verify against the RAW body bytes,
// not re-serialized JSON (key order/whitespace can differ):
app.post('/webhooks/sigpen', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-sigpen-signature'];
  const ts  = req.headers['x-sigpen-timestamp'];
  if (!verifyWebhook(req.body.toString('utf8'), sig, ts, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  // Process the event...
  res.sendStatus(200);
});

Embedded Signing

#

Generate a short-lived signing URL with POST /documents/:id/embedded-sign-url and render it in an iframe or with the embed.js widget. URLs include a JWT token and expire after 60 minutes.

Tip: Listen for postMessage events from the iframe. Each message is { source: 'sigpen', event, ... }. The embed.js widget wires these to onSign/onDecline/onError callbacks. See the Embed docs and interactive sandbox for a full walkthrough.

Error Handling

#

All errors return a consistent JSON structure with a machine-readable code and a human-readable message.

CodeMeaning
200OK. Request succeeded
201Created. Resource created (upload, webhook registration)
204No Content. Successful deletion
401Unauthorized. Missing or invalid API key
403Forbidden. Feature not available on your plan
404Not Found. Document or resource doesn’t exist
409Conflict. Document is in the wrong state for this action
422Validation Error. Request body failed validation
429Rate Limited. Too many requests or monthly quota exceeded
500Internal Error. Something went wrong on our end

429 responses include a retry_after field (in seconds) telling you when to retry. Rate-limited responses also set the standard Retry-After HTTP header.

422 · RESPONSE
{
  "error": {
    "type": "validation_error",
    "message": "The 'reason' field is required when voiding a document.",
    "code": "VOID_REASON_REQUIRED",
    "doc_url": "https://www.sigpen.com/docs/api#errors"
  }
}

v1 is stable. We add new fields and endpoints in a backward-compatible way, so existing integrations keep working. Any breaking change would ship under a new version, never silently.

June 11, 2026 · Per-resource API reference

  • The API reference is now generated from the OpenAPI spec, with a dedicated page per resource under /docs/api/<resource>: Documents, Signing, Embedded Signing, Templates, Webhooks, Bulk Send, Forms Library, and Account.
  • Machine-readable spec published at /openapi.json (the YAML original stays at /openapi.yaml).
  • Spec now documents signers[].fields inline placement and the bulk-send skip_email and subject parameters.
  • skip_email on POST /documents/:id/send: create signing sessions without invitation emails, completing the embedded-signing flow (embed.js + embedded-sign-url) without the legacy template endpoint.

June 10, 2026 · Documentation corrections and hidden capabilities

  • Documented inline field placement on send: signers[].fields places signature/text/date boxes per signer with no editor step.
  • Documented the webhook delivery envelope, all four delivery headers, the test.webhook event, and the retry/auto-disable policy.
  • Added reference entries for POST/PATCH/DELETE /templates, GET/PATCH /webhooks/:id, and POST /webhooks/:id/test.
  • The from-form sample now uses form_id; removed the unimplemented subject, reminder_frequency, and signers[].role send parameters (reminders are automatic after 48h); validation errors return 422, not 400.

OpenAPI Specification

Fetch the spec as JSON or YAML to auto-generate client SDKs or import into tools like Postman, Swagger UI, or Insomnia. AI agents can start from llms.txt.

Tooling
# Import into Postman
# File → Import → paste URL:
https://www.sigpen.com/openapi.json

# Generate a TypeScript SDK
npx openapi-generator-cli generate \
  -i https://www.sigpen.com/openapi.json \
  -g typescript-fetch \
  -o ./sigpen-sdk