Abundera Sign API
Create legally defensible e-signatures with cryptographic proof. Hash-chained audit trails, RFC 3161 timestamps, GitHub evidence anchoring, signer evidence scoring, signing ceremony proof, AI contract summaries, webcam identity capture, browser GPS, court-ready declarations, and public document verification, all through a simple REST API.
https://sign.abundera.aiQuick Start
Send a document for signature in one API call:
POST /api/v1/envelopes Authorization: Bearer <jwt_token> { "template": "nda-standard", "signers": [ { "email": "signer@example.com", "name": "Jane Doe", "role": "recipient" } ], "fields": { "company_name": "Acme Corp", "effective_date": "2026-03-15" } }
The signer receives an email with a secure link. When they sign, you get an HMAC-signed webhook. The completed PDF includes a Certificate of Completion, cryptographic proof page with hash-chain visualization, signer evidence scores, RFC 3161 timestamp, and a tamper-evident seal on every page.
API Resources
Well-known discovery
Auto-discovery endpoints under /.well-known/ (RFC 8615):
Authentication
The API supports two authentication methods for authenticated endpoints: JWT Bearer tokens and product-scoped API keys.
JWT Tokens
Obtain a JWT from the Abundera auth service. JWTs are verified against the JWKS endpoint at https://abundera.ai/v1/auth/jwks.
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
API Keys
API keys use the abnd_sign_* prefix and are passed as Bearer tokens. Create and manage API keys in the Abundera dashboard.
Authorization: Bearer abnd_sign_xxx...
API keys are product-scoped, each key is tied to Abundera Sign specifically. Keys are validated server-side against abundera.ai/v1/auth/validate-key and cached in KV for 60 seconds. Authenticated users (JWT or API key) receive 2× the per-IP rate limits. API keys are available on all tiers.
Token Refresh
Access tokens are short-lived. Use the refresh endpoint to obtain new tokens without re-authenticating:
Exchange a refresh token for a new access token and refresh token pair.
| Field | Type | Description |
|---|---|---|
| refresh_token required | string | The refresh token from your last authentication |
{
"access_token": "eyJhbGciOi...",
"refresh_token": "rt_abc123...",
"expires_in": 900,
"token_type": "Bearer"
}Plan Tiers
Some endpoints and features require a specific plan tier. If your plan doesn't include access, you'll receive a 403 response with an upgrade_url.
| Plan | Tier Level | Key Features |
|---|---|---|
| Starter | starter | Core API, webhooks, hash-chained audit trails, RFC 3161 timestamps, Certificate of Completion, identity scoring, ceremony proof, access codes, auto reminders, public verification, bot detection |
| Professional | professional | + Bulk send, signing order, SMS OTP, AI summaries, geo-lock, identity photo capture, browser GPS, comments, GitHub anchoring, custom email branding |
| Business | business | + Template CRUD, audit export, court-ready declarations, VPN/proxy blocking, custom retention (99yr), white label |
tier. The API checks this automatically, no extra headers needed.Auth Types
| Type | Used By | Description |
|---|---|---|
JWT | Envelope management | Bearer token in Authorization header |
API Key | Envelope management | abnd_sign_* Bearer token in Authorization header |
Token | Signing flow | Signing token in request body or URL |
Public | Verification | No authentication required |
Secret | Cron jobs | CRON_SECRET in query parameter |
Signing IDs
Signing request emails use opaque sgn_xxx IDs instead of raw hex tokens for improved security. These IDs are used in signing URLs:
https://sign.abundera.ai/sign/?token=sgn_xxxxxxxxxxxxxxxx
| Property | Details |
|---|---|
| Format | sgn_ prefix + 16 alphanumeric characters (20 chars total) |
| Entropy | ~4.7×1028 possible values |
| Lifetime | One-time use, automatically revoked after signer completes signing |
| Storage | verification_ids table with owner_type = 'signing' |
| Backward compatibility | Legacy raw hex tokens are still accepted |
Verification IDs
Public documents use opaque vrf_xxx IDs instead of raw UUIDs. QR codes embedded in sealed PDFs link to the verification page using these IDs:
https://sign.abundera.ai/verify/?id=vrf_xxxxxxxxxxxxxxxx
The /api/v1/verify endpoint supports three lookup methods:
| Method | Parameter | Auth Required |
|---|---|---|
vrf_xxx ID | ?id=vrf_xxx | None (public) |
| Raw UUID | ?id=e3f8a1b2-... | JWT required |
| SHA-256 hash | ?hash=a1b2c3... | None (self-gating, requires knowledge of the hash) |
Errors
All errors return JSON with an error field. HTTP status codes follow standard conventions:
| Code | Meaning |
|---|---|
| 400 | Invalid request, check required fields |
| 401 | Missing or invalid JWT |
| 403 | Forbidden, not the envelope sender, or gate required |
| 404 | Resource not found |
| 409 | Conflict, already signed/declined |
| 410 | Gone, document voided or expired |
| 429 | Rate limited or tier limit exceeded |
| 500 | Internal server error |
// Error response format { "error": "envelope_id is required" }
Rate Limiting
API requests are rate-limited per IP address. Authenticated requests (JWT or API key) automatically receive 2× the per-IP limits using the user's identity as the rate-limit key. When rate-limited, you'll receive a 429 response.
| Endpoint | Limit |
|---|---|
| General (all endpoints) | 30/min per IP (60/min for authenticated users) |
POST /api/v1/envelopes | 10/min |
POST /api/v1/orgs | 5/min |
POST /api/v1/demo-envelope | 1/min |
GET /api/v1/document-summary | 5/min |
GET /api/v1/verify | 10/min |
GET /api/v1/document-pdf | 3/min |
POST /api/v1/envelopes allows 20/min for authenticated users.Webhooks
Abundera Sign sends HMAC-SHA256 signed webhooks to your callback_url for envelope and signer lifecycle events. Configure per-envelope callbacks or use global webhooks for all envelopes.
POST {callback_url} Content-Type: application/json X-Signature-256: a1b2c3d4e5... { "event": "envelope.completed", "envelope_id": "abc-123-...", "completed_at": "2026-03-05T12:00:00Z" }
Webhook Event Types
| Event | Description |
|---|---|
signer.viewed | Signer opened the signing page |
signer.signed | Signer completed their signature |
signer.declined | Signer declined to sign with a reason |
signer.delegated | Signer delegated signing to another person |
envelope.completed | All signers completed, sealed PDF and evidence package generated |
envelope.voided | Sender voided the envelope |
envelope.expired | Envelope passed its expiry date without completion |
payment.completed | Stripe payment gate completed by signer |
clause.flagged | Signer flagged a clause for negotiation |
clause.responded | Sender responded to a clause negotiation flag |
clause.accepted | Clause negotiation accepted by both parties |
clause.counter_proposed | Signer submitted a counter-proposal to a clause response |
Verifying Webhook Signatures
Compute HMAC-SHA256(callback_secret, request_body) and compare with the X-Signature-256 header using constant-time comparison.
// Node.js verification example
const crypto = require('crypto');
const expected = crypto
.createHmac('sha256', callbackSecret)
.update(rawBody)
.digest('hex');
const valid = crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);Embedded Signing
Embed the signing experience directly in your app using an iframe. No redirects, signers complete the flow within your UI.
How it works
- Create an envelope with
embedded: true, this skips invitation emails and returnssigning_urlper signer - Embed the
signing_urlin an iframe in your app - Listen for completion via webhook (
signer.signed,envelope.completed) or poll the status endpoint
JavaScript (iframe)
// 1. Create envelope via your backend
const res = await fetch('/your-api/create-signing', { method: 'POST' });
const { signing_url } = await res.json();
// 2. Embed in iframe
const iframe = document.createElement('iframe');
iframe.src = signing_url;
iframe.style.cssText = 'width:100%;height:800px;border:none;border-radius:8px';
iframe.allow = 'camera;microphone'; // if using photo/video identity
document.getElementById('signing-container').appendChild(iframe);React
function SigningEmbed({ signingUrl }) {
return (
<iframe
src={signingUrl}
style={{ width: '100%', height: '800px', border: 'none', borderRadius: '8px' }}
allow="camera;microphone"
title="Sign Document"
/>
);
}Backend: Create Embedded Envelope
// Node.js / Python / any HTTP client
const response = await fetch('https://sign.abundera.ai/api/v1/envelopes', {
method: 'POST',
headers: {
'Authorization': 'Bearer abnd_sign_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
template: 'nda-simple',
embedded: true, // ← key flag
signers: [{ name: 'Jane Doe', email: 'jane@example.com', role: 'signer' }],
fields: { company_name: 'Acme Corp' },
}),
});
const envelope = await response.json();
// envelope.signers[0].signing_url → embed this in your iframeSecurity Notes
- Signing URLs are single-use tokens, they are revoked after signing
- Your domain must be on the allowed origins list (contact support)
- The iframe includes all security features: access codes, SMS OTP, identity verification
- Webhooks fire normally, use
callback_urlto get notified when signing completes
See code examples and framework guides →
MCP Server (AI Agents)
The Abundera Sign MCP Server lets AI coding assistants interact with the Abundera Sign API through the Model Context Protocol. Works with any MCP-compatible client (Cursor, Windsurf, VS Code, and more). Send documents for signature, check envelope status, manage templates, and review audit trails, all through natural language.
Available Tools
| Tool | Description |
|---|---|
list_templates | List templates with optional category, tag, or search filters |
get_template | Get template details including fields and roles |
create_envelope | Create and send a document for e-signature |
list_envelopes | List envelopes with status, template, and pagination filters |
get_envelope | Get full envelope details including signers and audit counts |
void_envelope | Void (cancel) an envelope and notify unsigned signers |
send_reminder | Send a reminder to unsigned signers |
get_audit_trail | Get the hash-chained audit trail for an envelope |
check_usage | Check current month's envelope usage and plan limits |
Setup
Add to your MCP client configuration (e.g., mcp.json or equivalent):
{
"mcpServers": {
"sign-abundera": {
"command": "node",
"args": ["/path/to/mcp/index.js"],
"env": {
"ABUNDERA_SIGN_API_KEY": "abnd_sign_..."
}
}
}
}Setup for Cursor / Windsurf
Add the same configuration to .cursor/mcp.json (Cursor) or your Windsurf MCP settings file.
Example
Ask your AI assistant: "Send an NDA to jane@example.com using the nda-simple template with company name Acme Corp"
The agent calls create_envelope:
{
"template_id": "nda-simple",
"signers": [{ "name": "Jane Doe", "email": "jane@example.com", "role": "signer" }],
"field_values": { "company_name": "Acme Corp" }
}abnd_sign_* prefix). MCP server source and README ship in the @abundera/sign-mcp npm package.Full endpoint reference
This page covers integration guides (authentication, webhooks, embedded signing, MCP). The complete REST API reference for every endpoint lives in our interactive docs: