Getting started
API Reference
v1 · additive-onlySend 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 specQuick Start
#Send your first document for signature in 3 steps. Copy any tab and run it.
Upload a document
POST a base64-encoded PDF (up to 25 MB). The response includes the documentidyou'll use in step 2.
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"
}'{
"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"
}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.
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
}'{
"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"
}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.
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
| Prefix | Mode | Usage |
|---|---|---|
| sp_live_ | Production | Real documents, counted toward limits |
| sp_test_ | Test | Don’t count toward your monthly limit |
curl https://www.sigpen.com/api/v1/documents \ -H "Authorization: Bearer sp_live_YOUR_KEY"
Rate limits
| Plan | Requests / min | Requests / day | Burst / sec |
|---|---|---|---|
| Developer (Free) | 10 | 200 | 3 |
| Professional | 30 | 500 | 5 |
| Business | 100 | 5,000 | 15 |
| Enterprise | 100 | 5,000 | 15 |
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
| Event | Fires when |
|---|---|
| document.sent | Document is sent for signing |
| document.viewed | A signer opens the signing page for the first time |
| document.signed | An individual signer completes their signature |
| document.completed | All signers have signed and the document is fully executed |
| document.declined | A signer declines to sign (this also voids the document) |
| document.voided | The sender voids the document |
| document.expired | The signing deadline passes without completion |
| test.webhook | Sent 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).
{
"id": "evt_01j9x8y7z6...",
"type": "document.completed",
"created_at": "2026-06-10T18:05:00.000Z",
"data": {
"id": "doc_123",
"title": "Consulting Agreement",
"status": "completed",
"signers": [ ... ]
}
}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.
| Code | Meaning |
|---|---|
| 200 | OK. Request succeeded |
| 201 | Created. Resource created (upload, webhook registration) |
| 204 | No Content. Successful deletion |
| 401 | Unauthorized. Missing or invalid API key |
| 403 | Forbidden. Feature not available on your plan |
| 404 | Not Found. Document or resource doesn’t exist |
| 409 | Conflict. Document is in the wrong state for this action |
| 422 | Validation Error. Request body failed validation |
| 429 | Rate Limited. Too many requests or monthly quota exceeded |
| 500 | Internal 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.
{
"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"
}
}Changelog
#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.
# 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