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
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 portal.
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 5 minutes. 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) | 60/min per IP |
POST /api/v1/envelopes | 10/min |
POST /api/v1/orgs | 5/min |
POST /api/v1/demo-envelope | 3/min |
GET /api/v1/document-summary | 10/min |
GET /api/v1/verify | 20/min |
GET /api/v1/document-pdf | 5/min |
POST /api/v1/envelopes allows 20/min for authenticated users.Webhooks
When all signers complete an envelope, Abundera Sign sends an HMAC-SHA256 signed webhook to your callback_url.
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" }
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)
);Create Envelope
Create a new signing envelope. Generates unique signing tokens per signer and sends invitation emails automatically.
| Field | Type | Description |
|---|---|---|
| templaterequired | string | Template ID (e.g., "nda-standard") |
| signersrequired | array | Array of signer objects, each with email, name, role, and optional phone |
| org_idoptional | string | Organization ID to create the envelope under. Requires membership (member+ role). Envelope will be visible to all org members. |
| fieldsoptional | object | Pre-filled field values (key-value pairs matching template fields) |
| messageoptional | string | Personal message from sender included in the signing invitation email. Max 1,000 characters. |
| callback_urloptional | string | Webhook URL called on completion |
| metadataoptional | object | Custom metadata stored with envelope |
| watermarkoptional | string | Text watermark on PDF pages (e.g., "CONFIDENTIAL") |
| footeroptional | object | Custom footer: { left, center, right, rule } |
| sign_orderoptional | boolean | Enforce sequential signing order. Default: false |
| expiry_daysoptional | integer | Days until expiration. Default: 30, max: 365 |
| retention_yearsoptional | integer | Document retention period. Default: 3, max: 99 |
| reminder_daysoptional | integer | Days between auto-reminders. Default: 3, max: 30 |
| max_remindersoptional | integer | Max auto-reminders per signer. Default: 3, max: 10 |
| access_codeoptional | string | PIN code signers must enter (min 4 chars, stored hashed) |
| geo_lockoptional | array | Restrict signing to specific countries. Array of ISO 3166-1 alpha-2 codes (e.g., ["US", "CA"]). |
| block_vpnoptional | boolean | Block signing from VPN/proxy connections. Uses ASN-based detection. Default: false. |
| require_photooptional | string | Require or request a webcam photo of the signer. "required" blocks signing without a photo. "optional" allows skipping. Records camera availability and permission state. |
| require_geolocationoptional | string | Require or request browser GPS location. "required" blocks signing without location access. "optional" allows denying. Records precise coordinates and accuracy. |
| locked_fieldsoptional | array | Array of field names that signers cannot edit (sender pre-filled values are locked). e.g., ["company_name", "effective_date"] |
| attach_pdfoptional | boolean | Attach signed PDF to completion email. Default: true. Set to false for large documents. |
| require_audio_statementoptional | string | Require or request a sworn audio statement. Signer records themselves reading a statement aloud. "required" or "optional". |
| require_video_statementoptional | string | Require or request a sworn video statement. Signer records face + voice reading a statement. "required" or "optional". |
| require_id_verificationoptional | string | Require government-issued ID verification (passport, driver's license) via independent IDV provider (Veriff). "required" or "optional". |
| require_kbaoptional | string | Require Knowledge-Based Authentication — identity questions from public/private records (LexisNexis). US only. "required" or "optional". |
| ai_summaryoptional | boolean | Enable AI plain-English document summary for signers. Default: false. |
| allow_delegateoptional | boolean | Allow signers to delegate signing responsibility to another person. Max 2 delegation transfers per signer. Default: true |
| ccoptional | array | CC recipients who receive the completed document. Array of { email, name } objects. Max 10. |
| payment_amountoptional | number | Collect payment before signing (Stripe). Amount in minor units. |
| payment_currencyoptional | string | ISO 4217 currency code for payment. Default: "usd". |
| langoptional | string | Language for signing page, emails, and Certificate of Completion. ISO 639-1 code. Supported: en, es, fr, de, pt, ja, ar, zh, hi, bn, ru, ur. Default: "en". RTL supported for Arabic and Urdu. |
| require_attachmentsoptional | array | Require signers to upload file attachments. Array of objects with name (string, required), description (string, optional), and required (boolean, optional). Max 5 items. |
| embeddedoptional | boolean | Embedded signing mode. Skips invitation emails and returns signing_url per signer for iframe embedding. Default: false |
| test_modeoptional | boolean | Test mode. Skips emails, does not count against usage limits. Returns signing_url per signer. Default: false |
| in_personoptional | boolean | In-person (kiosk) signing mode. Skips invitation emails, returns signing_url per signer for device handoff. |
| sourceoptional | string | Envelope source: "self_service", "abundera", or "api" |
{
"template": "nda-standard",
"signers": [
{
"email": "jane@example.com",
"name": "Jane Doe",
"role": "recipient",
"phone": "+15551234567"
},
{
"email": "bob@example.com",
"name": "Bob Smith",
"role": "company"
}
],
"fields": {
"company_name": "Acme Corp",
"effective_date": "2026-03-15"
},
"cc": [
{ "email": "legal@example.com", "name": "Legal Team" }
],
"callback_url": "https://api.example.com/webhooks/sign",
"message": "Please review and sign this NDA at your earliest convenience.",
"sign_order": true,
"expiry_days": 14,
"retention_years": 7,
"reminder_days": 3,
"max_reminders": 5,
"access_code": "8472",
"geo_lock": ["US", "CA"],
"block_vpn": true,
"watermark": "CONFIDENTIAL",
"footer": { "left": "Acme Corp", "right": "Confidential", "rule": true },
"locked_fields": ["company_name"],
"require_photo": "optional",
"require_geolocation": "optional",
"require_audio_statement": "required",
"require_video_statement": "optional",
"require_id_verification": "required",
"require_kba": "optional",
"ai_summary": true,
"allow_delegate": false,
"attach_pdf": true,
"lang": "es",
"embedded": false,
"require_attachments": [
{ "name": "Government ID", "description": "Upload a photo of your ID", "required": true }
]
}| Field | Type | Description |
|---|---|---|
| emailrequired | string | Signer's email address |
| namerequired | string | Signer's full name |
| rolerequired | string | Role matching template (e.g., "recipient", "company") |
| phoneoptional | string | Phone number for SMS OTP verification (E.164 format) |
{
"id": "e3f8a1b2-...",
"status": "sent",
"template_id": "nda-standard",
"expires_at": "2026-04-04T12:00:00Z",
"signers": [
{
"id": "s1a2b3c4-...",
"email": "signer@example.com",
"name": "Jane Doe",
"role": "recipient",
"status": "sent",
"signing_url": "https://sign.abundera.ai/sign/?token=..."
}
],
"callback_secret": "a1b2c3..."
}Note: signing_url is only returned per signer when embedded, test_mode, or in_person is true (email delivery is skipped in these modes).
List Envelopes
List envelopes for the authenticated user. Without org_id, returns personal envelopes only. With org_id, returns all org envelopes (requires membership).
| Param | Type | Description |
|---|---|---|
| org_idoptional | string | Organization ID. Returns org-scoped envelopes visible to all members. Also accepted via X-Org-Id header. |
| pageoptional | integer | Page number. Default: 1 |
| limitoptional | integer | Results per page. Default: 20, max: 100 |
| statusoptional | string | Filter: sent, completed, voided |
| templateoptional | string | Filter by template ID |
{
"envelopes": [ ... ],
"pagination": {
"page": 1,
"limit": 20,
"total": 42,
"pages": 3
}
}Get Envelope
Get full envelope details including signers, audit event count, and comment count.
{
"id": "e3f8a1b2-...",
"template_id": "nda-standard",
"template_name": "Standard NDA",
"status": "sent",
"sender_email": "you@company.com",
"sender_name": "Your Name",
"created_at": "2026-03-05T12:00:00Z",
"expires_at": "2026-04-04T12:00:00Z",
"has_access_code": false,
"sign_order": false,
"signers": [
{
"id": "s1a2b3c4-...",
"email": "signer@example.com",
"name": "Jane Doe",
"role": "recipient",
"status": "pending",
"has_phone": true,
"phone_verified": false,
"reminder_count": 0
}
],
"audit_event_count": 3,
"comment_count": 0
}Envelope Status
Check envelope status. Supports JWT (sender), signing token (signer), or public access for demo envelopes.
| Param | Type | Description |
|---|---|---|
| id | string | Envelope ID (for JWT or demo lookup) |
| token | string | Signing token (for signer lookup) |
Void Envelope
Void/cancel an in-flight envelope. Notifies all unsigned signers via email. Cannot void completed envelopes.
| Field | Type | Description |
|---|---|---|
| envelope_idrequired | string | Envelope ID to void |
{
"id": "e3f8a1b2-...",
"status": "voided",
"notified_signers": 2
}400 Cannot void completed envelope 404 Not found
Bulk Create Envelopes
Send the same template to multiple recipients in one call. Each recipient gets their own envelope. Max 100 recipients. Only markdown templates are supported.
| Field | Type | Description |
|---|---|---|
| template required | string | Template ID (must be markdown-based) |
| recipients required | array | Array of {email, name, role, phone?, fields?} — max 100 |
| shared_fields optional | object | Fields shared across all envelopes |
| sender_role optional | string | Sender's role in the template default: party_a |
| callback_url optional | string | Webhook URL for all envelopes |
{
"created": 8,
"failed": 0,
"envelopes": [
{ "id": "...", "recipient": "alice@example.com", "status": "sent" }
]
}Demo Envelope
Create a demo envelope for testing. No authentication required. Returns signing URLs for immediate use. Max 90-day expiry.
| Field | Type | Description |
|---|---|---|
| template optional | string | Template ID default: demo |
| name optional | string | Signer name default: Test Signer |
| email optional | string | Signer email default: test@example.com |
| fields optional | object | Template field values |
| expiry_days optional | integer | Days until expiry default: 7, max: 90 |
{
"envelope_id": "uuid",
"signers": [
{ "id": "...", "signing_url": "https://sign.abundera.ai/sign/?token=..." }
],
"expires_at": "2026-03-12T00:00:00Z"
}Submit Signature
Submit a signature for a document. Updates the signer record, checks if all signers have completed, and triggers completion flow (PDF generation, emails, webhook) when the last signer signs.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token from the invitation email |
| consentrequired | boolean | ESIGN Act consent acknowledgement (must be true) |
| signature_dataoptional | string | Base64-encoded signature image (PNG) or typed name |
| signature_typeoptional | string | Signature method: "typed", "drawn", or "uploaded" |
| field_valuesoptional | object | Field values filled in by the signer (key-value pairs) |
{
"status": "signed",
"all_signed": true,
"download_url": "https://sign.abundera.ai/api/v1/download?envelope=...&token=...",
"envelope_id": "e3f8a1b2-...",
"signer_id": "s1a2b3c4-..."
}download_url is only returned when all_signed is true (all signers have completed). The PDF is generated asynchronously — it may take a few seconds to be available at the download URL.400 Token/consent missing 404 Invalid token 409 Already signed/declined 410 Voided or expired
Decline to Sign
Decline to sign a document with a reason. Notifies the sender via email with the decline reason.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
| reasonrequired | string | Decline reason (max 1000 chars) |
{
"status": "declined",
"envelope_id": "e3f8a1b2-...",
"signer_id": "s1a2b3c4-..."
}Send Reminder
Send a reminder email to unsigned signers. Generates fresh signing tokens. Can target a specific signer or all unsigned.
| Field | Type | Description |
|---|---|---|
| envelope_idrequired | string | Envelope ID |
| signer_idoptional | string | Target specific signer. If omitted, reminds all unsigned. |
{
"envelope_id": "e3f8a1b2-...",
"reminded": [{ "id": "...", "email": "...", "name": "..." }],
"count": 1
}Resend Invitation
Resend the signing invitation with a fresh token. Optionally update the signer's email address (reassignment).
| Field | Type | Description |
|---|---|---|
| envelope_idrequired | string | Envelope ID |
| signer_idrequired | string | Signer ID to resend to |
| new_emailoptional | string | New email address (reassign signer) |
{
"signer_id": "s1a2b3c4-...",
"email": "new-signer@example.com",
"status": "sent"
}Delegate Signing
Delegate signing responsibility to another person. The original signer is marked as delegated and a new signing invitation is sent to the delegate. Delegation can be chained up to 2 levels deep. The sender can disable delegation by setting allow_delegate: false in envelope metadata.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token of the original signer |
| delegate_emailrequired | string | Email address of the delegate (max 254 chars). Validated against disposable email blocklist. |
| delegate_namerequired | string | Full name of the delegate (max 200 chars) |
| reasonoptional | string | Reason for delegation (max 1000 chars) |
{
"status": "delegated",
"delegate_email": "delegate@example.com",
"delegate_name": "Jane Delegate",
"envelope_id": "e3f8a1b2-..."
}400 Self-delegation, duplicate signer, max chain limit (2), validation error 403 Delegation disabled for this envelope 409 Already signed/declined/delegated 410 Voided, completed, or expired
Archive Envelope
Archive or unarchive an envelope. Only envelopes with completed or voided status can be archived. Archived envelopes are excluded from list results by default (use ?include_archived=true to include them).
| Field | Type | Description |
|---|---|---|
| envelope_idrequired | string | Envelope ID |
| actionrequired | string | "archive" or "unarchive" |
{
"id": "e3f8a1b2-...",
"status": "archived",
"archived_at": "2026-03-10T12:00:00Z"
}{
"id": "e3f8a1b2-...",
"status": "unarchived"
}400 Invalid action, wrong envelope status, already archived/not archived 404 Envelope not found or not owned
Download Signed PDF
Download the completed signed PDF. Authenticated via JWT (sender) or download token (from completion email).
| Param | Type | Description |
|---|---|---|
| enveloperequired | string | Envelope ID |
| tokenoptional | string | Download token (from completion email) |
200 Returns application/pdf binary
Verify Document
Publicly verify a signed document's authenticity. Checks audit chain integrity, manifest hash, RFC 3161 timestamp, and GitHub anchor.
| Param | Type | Description |
|---|---|---|
| id | string | Verification ID (vrf_xxx format, public) or raw UUID (requires JWT). See Verification IDs. |
| hash | string | SHA-256 hash of a signed PDF — looks up the envelope by document hash (public, self-gating) |
{
"verified": true,
"envelope": {
"id": "e3f8a1b2-...",
"status": "completed",
"completed_at": "2026-03-05T12:00:00Z"
},
"signers": [
{
"name": "J****e D****e",
"email": "j****e@example.com",
"signed_at": "...",
"viewing_duration_ms": 45200,
"scroll_depth_pct": 100,
"viewport": "1440x900",
"ceremony_event_count": 12,
"has_photo": true,
"photo_permission": "granted",
"geolocation": { "latitude": 36.1699, "longitude": -115.1398, "accuracy": 12 },
"geolocation_permission": "granted"
}
],
"integrity": {
"audit_chain_valid": true,
"manifest_hash": "a1b2c3...",
"document_hash": "d4e5f6...",
"github_anchor": { "commit_sha": "...", "url": "..." },
"rfc3161_timestamp": { "tsa_url": "...", "requested_at": "..." }
}
}j***e@example.com)./verify also supports drag-and-drop PDF upload. The SHA-256 hash of the uploaded file is computed client-side and compared against the stored document hash — no file data leaves the browser.Plain-English Document Summary
Generate an automatically generated plain-English description of the document to help signers understand what they are signing. Results cached for 7 days (document content is immutable per envelope). This is a comprehension aid, not legal advice.
| Param | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token (min 32 chars) |
{
"summary": "This is a standard NDA that...",
"generated_at": "2026-03-05T12:00:00Z",
"model": "claude-haiku-4-5-20251001",
"cached": false
}Get Document Data
Retrieve rendered document HTML and field definitions for the signing page. Enforces access code and OTP gates if configured. Returns the signer's role, fields to fill, and document content.
| Param | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token (min 32 chars) |
| access_codeoptional | string | PIN code if envelope requires one |
{
"html": "<h1>NDA Agreement</h1>...",
"fields": [
{ "name": "company_name", "type": "text", "required": true }
],
"signer_role": "counterparty",
"template_name": "Mutual NDA",
"features": { "ai_summary": true, "require_photo": "optional", "require_geolocation": null }
}409 Already signed
410 Document voided or link expired
List Templates
List available document templates. Returns global templates by default. Pass org_id to also include org-private templates (requires membership). Supports ?category=, ?tag=, and ?search= filters.
Create Template
Create a new document template with markdown content and field definitions.
| Field | Type | Description |
|---|---|---|
| idrequired | string | Unique template identifier (slug) |
| namerequired | string | Human-readable name |
| org_idoptional | string | Organization ID. Creates an org-private template (requires admin+ role). Without this, templates are personal. |
| descriptionoptional | string | Template description |
| rolesoptional | array | Signer roles (e.g., ["sender", "recipient"]) |
| markdownoptional | string | Markdown template content with field placeholders |
| metadataoptional | object | Template metadata (watermark, footer defaults) |
Get Template
Retrieve a single template by ID, including full markdown content, fields, and metadata.
Update Template
Update an existing template's name, description, fields, markdown, or metadata. All fields are optional — only provided fields are updated.
Delete Template
Delete a template. Fails with 409 if the template has active (non-voided) envelopes.
List Comments
List all comments on an envelope, ordered by creation date.
Add Comment
Add a comment to an envelope. Max 2000 characters.
| Field | Type | Description |
|---|---|---|
| commentrequired | string | Comment text (max 2000 chars) |
Audit Trail
Get the full hash-chained audit trail for an envelope with integrity verification.
{
"envelope_id": "e3f8a1b2-...",
"chain_valid": true,
"chain_errors": [],
"event_count": 5,
"events": [
{
"action": "created",
"actor": "sender@example.com",
"is_bot": false,
"entry_hash": "a1b2c3...",
"previous_hash": "",
"created_at": "2026-03-05T12:00:00Z"
}
]
}entry_hash is computed from its content + the previous_hash, forming a tamper-evident chain. If any event is modified, all subsequent hashes break."is_bot": true. Detection uses User-Agent pattern matching and Google IP range identification. Bot events are labeled "[Email Prefetch]" in the PDF Certificate of Completion.Court-Ready Declaration
Generate a court-ready "Declaration of Custodian of Records" PDF for a completed envelope. This legal declaration summarizes all technical measures used to authenticate the signing and can be filed as a court exhibit.
Returns application/pdf — a multi-page legal declaration document.
- Platform Description: Security measures, compliance (ESIGN Act, UETA)
- Document Details: Template, envelope ID, timestamps, geo-restrictions
- Signer Authentication: IP, country, VPN status, viewing duration, scroll depth, viewport, ceremony events, ESIGN consent, identity photo status, browser GPS coordinates
- Audit Chain Integrity: SHA-256 hash chain verification result
- Cryptographic Evidence: Manifest hash, PDF hash, GitHub anchor, RFC 3161 timestamp
- Public Verification: Link to verification page
- Records Custodian Statement: Business records declaration under penalty of perjury
completed status. Only the sender (owner) can generate the declaration.Request Court Declaration
Request a Court-Ready Declaration via email OTP verification. This is a two-step process: Step 1 submits the request and sends a verification code to the requester's email. Step 2 verifies the code. Signers on the envelope are auto-approved; third-party requesters (attorneys, courts) are queued for admin review. Supports file attachments (court orders, government ID) via multipart form data.
| Field | Type | Description |
|---|---|---|
| envelope_idrequired | string | Envelope ID (must be completed) |
| emailrequired | string | Requester's email address |
| namerequired | string | Requester's full name (min 2 chars) |
| relationshiprequired | string | One of: signer, attorney, court_officer, law_enforcement, other |
| phoneoptional | string | Phone number |
| case_nameoptional | string | Court case name |
| case_numberoptional | string | Court case number |
| jurisdictionoptional | string | Jurisdiction (e.g., "US District Court, Nevada") |
| deadlineoptional | string | Filing deadline |
| notesoptional | string | Additional notes |
{
"request_id": "a1b2c3d4-...",
"email_sent": true,
"email_masked": "j***@example.com",
"expires_in_seconds": 600
}| Field | Type | Description |
|---|---|---|
| actionrequired | string | Must be "verify" |
| request_idrequired | string | Request ID from Step 1 |
| coderequired | string | 6-digit OTP code (max 3 attempts) |
{
"verified": true,
"status": "approved",
"message": "Your declaration is ready. A one-time download link has been sent to your email."
}{
"verified": true,
"status": "pending_review",
"message": "Your identity has been verified. Your request is being reviewed."
}multipart/form-data with fields attachment_0, attachment_1, attachment_2 (max 3 files, 10MB each). Accepted types: PDF, JPEG, PNG, WebP. An id_document field can also be included for government ID.Approve / Deny Declaration Request
Admin-only endpoint to approve or deny a third-party declaration request. On approval, a one-time burn-on-use download token (72-hour TTL) is generated and emailed to the requester. On denial, the requester is notified with an optional reason.
| Field | Type | Description |
|---|---|---|
| request_idrequired | string | Declaration request ID |
| Field | Type | Description |
|---|---|---|
| request_idrequired | string | Declaration request ID |
| actionrequired | string | Must be "deny" |
| denial_reasonoptional | string | Reason for denial (included in notification to requester) |
{
"status": "approved",
"request_id": "a1b2c3d4-...",
"email": "requester@example.com",
"message": "Request approved. One-time download link sent to requester."
}{
"status": "denied",
"request_id": "a1b2c3d4-...",
"message": "Request denied. Requester has been notified."
}403 Admin access required 404 Request not found 409 Already approved or denied
Download Declaration
One-time burn-on-use download for Court-Ready Declaration PDFs. The token is destroyed after the first successful download. Links expire after 72 hours. The PDF is generated fresh at download time with the latest evidence data and includes a PAdES digital signature.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | One-time download token (from approval email) |
Returns application/pdf with Content-Disposition: attachment. The download token is permanently destroyed after successful generation.
410 Token expired, already used, or envelope no longer available
Create Organization
Create a new organization. The authenticated user becomes the owner. Limited to 10 organizations per user.
| Field | Type | Description |
|---|---|---|
| namerequired | string | Organization name. Max 100 characters. Control characters are stripped. |
{
"id": "org_a1b2c3d4",
"name": "Acme Corp",
"owner_user_id": "user_xyz",
"owner_email": "alice@acme.com"
}List Organizations
List all organizations the authenticated user belongs to.
{
"orgs": [
{
"id": "org_a1b2c3d4",
"name": "Acme Corp",
"role": "owner",
"owner_email": "alice@acme.com",
"created_at": "2026-03-06T12:00:00Z"
}
]
}Get Organization
Get organization details. Requires membership (any role).
{
"org": {
"id": "org_a1b2c3d4",
"name": "Acme Corp",
"owner_user_id": "user_xyz",
"owner_email": "alice@acme.com",
"created_at": "2026-03-06T12:00:00Z"
},
"role": "owner"
}Update Organization
Update organization name. Requires admin+ role.
| Field | Type | Description |
|---|---|---|
| namerequired | string | New organization name. Max 100 characters. |
Delete Organization
Delete an organization. Requires owner role. Cannot delete if there are active (sent/partial) envelopes. Existing envelopes and templates are unlinked from the org (not deleted).
List Members
List all members of an organization. Requires membership (any role).
{
"members": [
{
"id": "mem_xyz",
"user_id": "user_xyz",
"email": "alice@acme.com",
"name": "Alice",
"role": "owner",
"joined_at": "2026-03-06T12:00:00Z"
}
]
}Update Member Role
Change a member's role. Requires admin+ role. Only the owner can assign the admin role. Cannot change the owner's role.
| Field | Type | Description |
|---|---|---|
| rolerequired | string | New role: "viewer", "member", "admin", or "owner" |
Remove Member
Remove a member from the organization. Requires admin+ role. Cannot remove the owner.
Invite Member
Send an email invitation to join the organization. Requires admin+ role. Only the owner can invite with the admin role. Invitation expires after 7 days. An email with an accept link is sent automatically.
| Field | Type | Description |
|---|---|---|
| emailrequired | string | Email address to invite |
| roleoptional | string | Role to assign on acceptance. Default: "member". Options: "viewer", "member", "admin" |
List Invitations
List pending invitations for an organization. Requires admin+ role.
{
"invitations": [
{
"id": "inv_abc123",
"email": "bob@acme.com",
"role": "member",
"invited_by": "alice@acme.com",
"status": "pending",
"created_at": "2026-03-06T12:00:00Z",
"expires_at": "2026-03-13T12:00:00Z"
}
]
}Revoke Invitation
Revoke a pending invitation. Requires admin+ role.
Accept Invitation
Accept an organization invitation using the token from the invitation email. The authenticated user's email must match the invited email. On success, the user becomes a member with the invited role.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Invitation token from the accept link |
{
"message": "Invitation accepted",
"org_id": "org_a1b2c3d4",
"role": "member"
}| Role | Level | Capabilities |
|---|---|---|
| viewer | 1 | Read-only access to org envelopes and templates |
| member | 2 | Create envelopes, modify envelopes, add comments |
| admin | 3 | Manage members, send invitations, manage org templates |
| owner | 4 | Full control — update org, delete org, assign admin role |
Get Branding
Retrieve white-label branding configuration for the authenticated user. Returns { "configured": false } if no branding has been set up.
{
"company_name": "Acme Corp",
"logo_url": "https://acme.com/logo.svg",
"primary_color": "#60a5fa",
"accent_color": "#a78bfa",
"custom_domain": "sign.acme.com",
"email_from_name": "Acme Signing",
"footer_text": "Powered by Acme Corp",
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-05T14:30:00Z"
}Update Branding
Create or update white-label branding settings. Only include the fields you want to change — omitted fields are not modified.
| Field | Type | Description |
|---|---|---|
| company_nameoptional | string | Company name (max 100 chars) |
| logo_urloptional | string | URL to company logo (max 2048 chars) |
| primary_coloroptional | string | Hex color code (e.g. #60a5fa) |
| accent_coloroptional | string | Hex color code (e.g. #a78bfa) |
| custom_domainoptional | string | Custom signing domain (max 253 chars) |
| email_from_nameoptional | string | Custom sender name for signing emails |
| footer_textoptional | string | Custom footer text for signing pages (max 500 chars) |
{
"company_name": "Acme Corp",
"logo_url": "https://acme.com/logo.svg",
"primary_color": "#60a5fa",
"accent_color": "#a78bfa",
"custom_domain": "sign.acme.com",
"email_from_name": "Acme Signing",
"footer_text": "Powered by Acme Corp",
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-05T14:30:00Z"
}400 Validation error (invalid hex color, empty update, etc.) 403 Business plan required
Upload Logo
Upload a logo image for white-label branding. Accepts multipart/form-data with an image file. The uploaded image URL is automatically saved to your branding configuration.
multipart/form-data)| Field | Type | Description |
|---|---|---|
| filerequired | file | Image file (PNG, JPG, SVG, WebP) |
{
"logo_url": "https://sign.abundera.ai/branding/logos/abc123.png"
}400 No file uploaded or invalid format 403 Business plan required
List Clauses
List reusable clause library entries. Supports search and category filtering. Clauses can be inserted into templates during envelope creation.
| Field | Type | Description |
|---|---|---|
| searchoptional | string | Search by title or content |
| categoryoptional | string | Filter by category (e.g., indemnification, confidentiality) |
{
"clauses": [
{
"id": "cl_a1b2c3d4-...",
"title": "Standard Indemnification",
"content": "Each party shall indemnify and hold harmless...",
"category": "indemnification",
"tags": ["legal", "liability"],
"created_at": "2026-03-10T08:00:00Z",
"updated_at": "2026-03-10T08:00:00Z"
}
]
}Create Clause
Create a reusable clause for your clause library. Clauses can be referenced and inserted into templates.
| Field | Type | Description |
|---|---|---|
| titlerequired | string | Clause title (max 200 chars) |
| contentrequired | string | Clause content in markdown (max 10,000 chars) |
| categoryoptional | string | Category label (e.g., indemnification, confidentiality, termination) |
| tagsoptional | array | Array of string tags for filtering |
{
"id": "cl_a1b2c3d4-...",
"title": "Standard Indemnification",
"content": "Each party shall indemnify and hold harmless...",
"category": "indemnification",
"tags": ["legal", "liability"],
"created_at": "2026-03-10T08:00:00Z"
}400 Missing title or content 403 Business plan required
Get Clause
Retrieve a single clause by ID. Returns the full clause content and metadata.
{
"id": "cl_a1b2c3d4-...",
"title": "Standard Indemnification",
"content": "Each party shall indemnify and hold harmless...",
"category": "indemnification",
"tags": ["legal", "liability"],
"created_at": "2026-03-10T08:00:00Z",
"updated_at": "2026-03-10T08:00:00Z"
}404 Clause not found
Update Clause
Update an existing clause. Only include the fields you want to change — omitted fields are not modified.
| Field | Type | Description |
|---|---|---|
| titleoptional | string | Updated title (max 200 chars) |
| contentoptional | string | Updated content in markdown (max 10,000 chars) |
| categoryoptional | string | Updated category |
| tagsoptional | array | Updated tags array (replaces existing) |
{
"id": "cl_a1b2c3d4-...",
"title": "Updated Indemnification",
"content": "The receiving party shall indemnify...",
"category": "indemnification",
"tags": ["legal", "updated"],
"updated_at": "2026-03-11T10:00:00Z"
}400 Empty update 404 Clause not found
Delete Clause
Permanently delete a clause from the library. This does not affect envelopes that already used this clause.
No content.
404 Clause not found
Flag Clause for Negotiation
Signer flags a clause for negotiation. The sender is notified and can accept, reject, or counter-propose changes. The envelope remains in sent status until the negotiation is resolved.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
| clause_idrequired | string | ID of the clause to flag |
| reasonoptional | string | Explanation of the objection or requested change (max 2000 chars) |
{
"negotiation_id": "neg_x1y2z3-...",
"clause_id": "cl_a1b2c3d4-...",
"status": "flagged",
"created_at": "2026-03-11T10:00:00Z"
}400 Missing clause_id 404 Clause not found 409 Already flagged
Get Negotiations
Get all negotiation flags for the current signer's envelope. Returns the status of each flagged clause.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
{
"negotiations": [
{
"negotiation_id": "neg_x1y2z3-...",
"clause_id": "cl_a1b2c3d4-...",
"status": "flagged",
"reason": "The indemnification scope is too broad.",
"response_text": null,
"created_at": "2026-03-11T10:00:00Z"
}
]
}Update Negotiation
Sender responds to a negotiation flag. Can accept the objection, reject it, or provide a counter-proposal.
| Field | Type | Description |
|---|---|---|
| envelope_idrequired | string | Envelope ID |
| clause_idrequired | string | Clause ID to respond to |
| actionrequired | string | Response action: accept, reject, or counter |
| response_textoptional | string | Counter-proposal or explanation (max 2000 chars) |
{
"negotiation_id": "neg_x1y2z3-...",
"status": "accepted",
"action": "accept",
"updated_at": "2026-03-11T12:00:00Z"
}400 Invalid action 403 Not the envelope sender 404 Negotiation not found
Withdraw Negotiation Flag
Signer withdraws a previously flagged clause negotiation. Only possible while the flag is still in flagged status (not yet responded to by the sender).
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
| clause_idrequired | string | Clause ID to withdraw the flag for |
No content.
404 Flag not found 409 Already responded to — cannot withdraw
List All Negotiations
List all clause negotiations for an envelope. Returns flags from all signers with their current status. Only the envelope sender can access this endpoint.
| Field | Type | Description |
|---|---|---|
| envelope_idrequired | string | Envelope ID |
{
"negotiations": [
{
"negotiation_id": "neg_x1y2z3-...",
"clause_id": "cl_a1b2c3d4-...",
"signer_email": "signer@example.com",
"signer_name": "Jane Doe",
"status": "flagged",
"reason": "The indemnification scope is too broad.",
"response_text": null,
"created_at": "2026-03-11T10:00:00Z"
}
]
}400 Missing envelope_id 403 Not the envelope sender 404 Envelope not found
Respond to Negotiation
Sender responds to a specific negotiation flag with an action and optional text. The signer is notified of the response via email.
| Field | Type | Description |
|---|---|---|
| envelope_idrequired | string | Envelope ID |
| negotiation_idrequired | string | Negotiation flag ID |
| actionrequired | string | One of: accept, reject, counter |
| textoptional | string | Response explanation or counter-proposal text (max 2000 chars) |
{
"negotiation_id": "neg_x1y2z3-...",
"status": "countered",
"action": "counter",
"text": "We can limit indemnification to direct damages only.",
"updated_at": "2026-03-11T14:00:00Z"
}400 Invalid action or missing negotiation_id 403 Not the envelope sender 404 Negotiation not found 409 Already responded
Create Signing Link
Create a reusable signing link for a template. Anyone with the link URL can claim it — an envelope and signer are created on claim. Useful for public-facing forms, onboarding flows, or self-service signing.
| Field | Type | Description |
|---|---|---|
| templaterequired | string | Template ID (must have markdown content) |
| fieldsoptional | object | Pre-filled field values for envelopes created from this link |
| max_usesoptional | integer | Maximum number of claims (1 to 100,000). Unlimited if omitted. |
| expiry_daysoptional | integer | Days until link expires (default: 90, max: 365) |
| metadataoptional | object | Envelope metadata applied to all claimed envelopes (e.g., geo_lock, lang) |
| org_idoptional | string | Organization ID (requires member+ role) |
{
"id": "l1a2b3c4-...",
"url": "https://sign.abundera.ai/sign/?link=<token>",
"template": "nda-standard",
"max_uses": null,
"use_count": 0,
"expires_at": "2026-06-08T12:00:00Z",
"status": "active"
}400 Missing template, invalid max_uses 403 Professional+ plan required 404 Template not found
List Signing Links
List signing links created by the authenticated user, with pagination. Supports org-scoped queries via X-Org-Id header or ?org_id= parameter.
| Field | Type | Description |
|---|---|---|
| pageoptional | integer | Page number (default: 1) |
| limitoptional | integer | Results per page (1-100, default: 20) |
{
"links": [
{
"id": "l1a2b3c4-...",
"template_id": "nda-standard",
"status": "active",
"max_uses": 100,
"use_count": 12,
"expires_at": "2026-06-08T12:00:00Z",
"created_at": "2026-03-10T12:00:00Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}Signing Link Info
Get signing link details so the frontend can display the claim form. Returns the template name and link status without requiring authentication.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing link token |
{
"template_name": "Non-Disclosure Agreement",
"sender_name": "John Smith",
"status": "active"
}400 Missing token 404 Invalid link 410 Expired or max uses reached
Claim Signing Link
Claim a signing link — creates an envelope and signer, then sends a signing invitation email. The signing link's use count is incremented. No authentication required; the link token provides access.
| Field | Type | Description |
|---|---|---|
| link_tokenrequired | string | Signing link token |
| namerequired | string | Signer's full name |
| emailrequired | string | Signer's email address (validated against disposable email blocklist) |
{
"envelope_id": "e3f8a1b2-...",
"signing_url": "https://sign.abundera.ai/sign/?token=<token>",
"template": "nda-standard",
"template_name": "Non-Disclosure Agreement",
"expires_at": "2026-04-09T12:00:00Z"
}400 Missing fields, invalid email 404 Invalid link token 410 Expired or max uses reached
Create Document Package
Create a document package that groups 2 to 10 related envelopes together. Packages let you track multi-document transactions as a single unit. All referenced envelopes must belong to the authenticated user.
| Field | Type | Description |
|---|---|---|
| namerequired | string | Package name (max 200 chars) |
| envelope_idsrequired | array | Array of 2-10 envelope IDs to group |
{
"id": "pkg_a1b2c3d4-...",
"name": "Series A Closing Documents",
"envelope_ids": ["e1a2b3c4-...", "e5f6g7h8-..."],
"envelope_count": 2,
"created_at": "2026-03-11T10:00:00Z"
}400 Missing name, fewer than 2 or more than 10 envelopes 404 Envelope not found or not owned by user
List / Get Packages
List all document packages for the authenticated user. Pass ?id= to retrieve a single package with full envelope details.
| Field | Type | Description |
|---|---|---|
| idoptional | string | Package ID to retrieve a single package |
{
"packages": [
{
"id": "pkg_a1b2c3d4-...",
"name": "Series A Closing Documents",
"envelope_count": 3,
"created_at": "2026-03-11T10:00:00Z"
}
]
}{
"id": "pkg_a1b2c3d4-...",
"name": "Series A Closing Documents",
"envelopes": [
{
"id": "e1a2b3c4-...",
"template_name": "Stock Purchase Agreement",
"status": "completed"
}
],
"created_at": "2026-03-11T10:00:00Z"
}404 Package not found
Send OTP
Send a 6-digit OTP code to the signer's phone via SMS. Rate limited: max 3 sends per signer, 60-second cooldown between sends.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
{
"sent": true,
"phone_masked": "***-***-1234",
"expires_in_seconds": 600
}Verify OTP
Verify a 6-digit OTP code. Max 3 attempts per code — after that, request a new one.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
| coderequired | string | 6-digit OTP code |
{
"verified": true
}
// or on wrong code:
{
"verified": false,
"attempts_remaining": 2
}Get Signer Profile
Get auto-fill suggestions for the signer based on their previously saved profile. Returns saved name and field values if the signer has opted in to profile storage. Helps returning signers complete forms faster.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
{
"name": "Jane Doe",
"fields": {
"company_name": "Acme Corp",
"title": "VP Engineering"
},
"saved_at": "2026-03-10T08:00:00Z"
}{
"name": null,
"fields": {}
}Save Signer Profile
Save signer profile data for future auto-fill. Requires explicit consent from the signer. Stored values are scoped to the signer's email and encrypted at rest.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
| fieldsrequired | object | Key-value map of field names and values to save |
| consentrequired | boolean | Must be true — explicit consent to store profile data |
{
"saved": true,
"field_count": 3
}400 Missing fields or consent not true
Delete Signer Profile
Permanently delete all saved signer profile data (GDPR right to erasure). Removes all stored field values and name associated with the signer's email.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
No content.
Geography Locking
Restrict document signing to specific countries. When geo_lock is set on an envelope, signers outside the allowed countries will see a blocked message and cannot access the document.
CF-IPCountry header — no client-side geolocation or permissions needed.// In POST /api/v1/envelopes request body: { "geo_lock": ["US", "CA", "GB"] }
Country codes use ISO 3166-1 alpha-2 format (e.g., US, CA, GB, DE). The signer's country is recorded in the audit trail regardless of geo-lock settings.
{
"error": "Signing is restricted to specific countries",
"gate": "geo_blocked",
"country": "RU"
}VPN / Proxy Blocking
Block signing from VPN, proxy, and datacenter IP addresses. Uses ASN-based detection to identify hosting providers and known VPN services.
// In POST /api/v1/envelopes request body: { "block_vpn": true }
VPN detection status is recorded in the audit trail and displayed on the Certificate of Completion for all envelopes, regardless of whether blocking is enabled.
{
"error": "Signing from VPN/proxy connections is not allowed",
"gate": "vpn_blocked"
}Government ID Verification
Verify signer identity using government-issued photo ID via Veriff. Enable by setting require_id_verification: true in envelope metadata. Max 3 sessions per signer. Results are stored for the signing session and recorded in the audit trail.
Create IDV Session
Create a Veriff ID verification session. The signer is redirected to Veriff's hosted flow to scan their government ID. Session expires in 30 minutes.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
{
"session_url": "https://magic.veriff.me/v/...",
"session_id": "abc123...",
"expires_in_seconds": 1800
}400 IDV not required for this envelope 409 Already verified or signed 429 Max sessions reached (3) 502 Veriff API error
Verify IDV Result
Check the result of an ID verification session. Fetches the decision from Veriff and stores the result. Call this after the signer completes the Veriff flow.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
{
"verified": true,
"document_type": "DRIVERS_LICENSE",
"document_country": "US"
}{
"verified": false,
"status": "declined",
"reason": "Document expired"
}410 Session expired -- start a new one 502 Veriff API error
Knowledge-Based Authentication
Verify signer identity through knowledge-based authentication using personal information and credit-bureau questions. Enable by setting require_kba: true in envelope metadata. Max 3 attempts per signer. Supports two providers: Persona (instant database verification) and LexisNexis (quiz-based). The active provider is configured via the KBA_PROVIDER environment variable.
Start KBA Session
Start a KBA session by submitting personal identifying information. With Persona, verification is instant (pass/fail). With LexisNexis, returns multiple-choice questions that must be answered within 2 minutes via the verify endpoint.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
| namerequired | string | Full legal name (first and last, min 2 chars) |
| dobrequired | string | Date of birth (YYYY-MM-DD, must be 18+) |
| address_line1required | string | Street address (min 3 chars) |
| address_cityrequired | string | City (min 2 chars) |
| address_staterequired | string | US state code (e.g., CA, NY, DC) |
| address_ziprequired | string | US ZIP code (5 digits or ZIP+4) |
| ssn_last4required | string | Last 4 digits of SSN |
{
"passed": true,
"provider": "persona"
}{
"questions": [
{
"id": "q1",
"text": "Which of the following addresses have you been associated with?",
"choices": ["123 Main St", "456 Oak Ave", "789 Pine Rd", "None of the above"]
}
],
"expires_in": 120
}400 Validation error, KBA not required 409 Already verified or signed 422 Unable to generate questions 429 Max attempts reached (3)
Verify KBA Answers
Submit answers to KBA questions (LexisNexis provider only). Must be submitted within 2 minutes of the start request. Each answer must reference a valid question_id from the start response.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
| answersrequired | array | Array of { question_id, answer } objects |
{
"passed": true,
"correct_count": 4,
"attempts_remaining": 2
}400 Invalid question_id, KBA not required 410 Session expired (2-minute timeout)
Payment Gate
Create a Stripe Checkout session for pay-to-sign envelopes. The envelope must be completed and have payment_amount configured in its metadata. Accepts either a signing token or JWT for authentication. Returns a Stripe Checkout URL to redirect the payer.
| Field | Type | Description |
|---|---|---|
| envelope_idrequired | string | Envelope ID (must be completed with payment configured) |
| tokenoptional | string | Signing token (alternative to JWT auth). Must belong to a signer on this envelope. |
{
"checkout_url": "https://checkout.stripe.com/c/pay/...",
"session_id": "cs_live_..."
}400 Envelope not completed, no payment configured 401 No auth provided 403 Not the envelope owner or signer 409 Payment already completed
Bot & Email Prefetch Detection
Audit trail events automatically detect and flag bot traffic, including email link prefetchers from Gmail, Outlook, and other providers. This prevents false "document viewed" events from polluting the audit trail.
Detection methods:
- User-Agent matching — known bot, crawler, and prefetcher patterns
- Google IP ranges — Gmail's link prefetcher uses real Chrome user agents but originates from Google IP ranges (172.253.*, 209.85.*, 66.249.*, etc.)
Bot events are:
- Flagged with
"is_bot": truein the audit trail API response - Labeled as [Email Prefetch] on the PDF Certificate of Completion
- Shown with a badge on the public verification page
Contact Sender
Send a message from a signer to the envelope sender. Allows signers to ask questions about the document before signing without leaving the signing flow. The sender receives the message via email.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
| messagerequired | string | Message text (max 2000 chars) |
{
"sent": true,
"sender_email": "sender@company.com"
}400 Missing or empty message 409 Envelope already completed or voided 429 Rate limited
Document Compare
AI-powered comparison between the contract being signed and a document the signer uploads (e.g., a previous version or their own template). Returns a plain-English summary of the key differences. Powered by Workers AI.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
| contract_textrequired | string | Text of the current contract being signed |
| uploaded_textrequired | string | Text of the document to compare against |
| langoptional | string | Language for the summary (default: en). Supports all 20 platform languages. |
{
"summary": "Key differences: 1) The uploaded version has a 2-year non-compete clause while the current contract specifies 1 year. 2) The liability cap is $500K in the current version vs. $1M in the uploaded version. 3) The governing law changed from California to Delaware."
}400 Missing contract_text or uploaded_text 502 AI service unavailable
Download Unsigned PDF
Download an unsigned PDF of the document with all currently filled field values rendered. Allows the signer to review the document offline or share it with legal counsel before signing. The PDF includes a "DRAFT — NOT YET SIGNED" watermark.
| Field | Type | Description |
|---|---|---|
| tokenrequired | string | Signing token |
Returns the PDF binary with Content-Type: application/pdf and Content-Disposition: attachment; filename="document-draft.pdf".
404 Invalid token or envelope not found 409 Already signed 410 Envelope voided or expired
Email Delivery Log
View the email delivery log for envelopes you own. Shows delivery status, timestamps, and provider details for every email sent as part of the signing workflow (invitations, reminders, completions, etc.).
| Field | Type | Description |
|---|---|---|
| envelope_idoptional | string | Filter by envelope ID |
| statusoptional | string | Filter by status: sent, delivered, bounced, failed |
| limitoptional | integer | Results per page (1-100, default: 50) |
| offsetoptional | integer | Pagination offset (default: 0) |
{
"emails": [
{
"id": "em_a1b2c3d4-...",
"envelope_id": "e1a2b3c4-...",
"to": "signer@example.com",
"type": "signing_invitation",
"status": "delivered",
"provider": "zeptomail",
"sent_at": "2026-03-11T10:00:00Z",
"delivered_at": "2026-03-11T10:00:02Z"
}
],
"total": 15,
"limit": 50,
"offset": 0
}Current User
Get the currently authenticated user's profile, sign-specific plan tier, and current month envelope usage. Accepts JWT via Authorization header or __Secure-abundera_session cookie.
{
"authenticated": true,
"id": "u1a2b3c4-...",
"email": "you@company.com",
"name": "Your Name",
"role": "owner",
"sign_tier": "professional",
"envelope_limit": 200,
"envelopes_used": 42,
"year_month": "2026-03"
}{
"authenticated": false
}Health Check
Check platform health — verifies D1 database, KV store, and R2 storage connectivity.
{
"status": "healthy",
"checks": {
"d1": "ok",
"kv": "ok",
"r2": "ok"
},
"timestamp": "2026-03-05T12:00:00Z"
}503 returned when any check fails (status: "degraded")
Usage Stats
Get your current plan tier, monthly envelope limits, and usage for the current billing period.
{
"plan": "professional",
"monthly_limit": 200,
"used": 42,
"remaining": 158,
"year_month": "2026-03",
"overage_rate": "$1.00"
}Join Waitlist
Join the product waitlist. No authentication required. Protected by honeypot field — bots that fill hidden fields are silently rejected.
| Field | Type | Description |
|---|---|---|
| emailrequired | string | Email address to add to the waitlist |
{
"ok": true
}Contact Form
Submit a contact form message. No authentication required. Protected by honeypot field — bots that fill hidden fields are silently rejected.
| Field | Type | Description |
|---|---|---|
| namerequired | string | Sender's full name |
| emailrequired | string | Sender's email address |
| messagerequired | string | Message content |
{
"ok": true
}Evidence Package
Every completed envelope generates a comprehensive evidence package stored in R2:
| Artifact | Description |
|---|---|
original.md | Template markdown snapshot (immutable record of what was presented) |
field-values.json | All field values with metadata and template version |
signatures/{role}.json | Per-signer signature evidence (type, data, consent, IP, UA, timestamp, identity photo, GPS coordinates) |
audit-trail.json | Hash-chained audit log snapshot with integrity verification |
evidence-manifest.json | SHA-256 hashes of every artifact + envelope metadata |
signed.pdf | Certified PDF with Certificate of Completion + Cryptographic Proof pages |
timestamp.tsr | RFC 3161 trusted timestamp response (ASN.1/DER encoded) |
abundera/audit-anchors) as an independent, tamper-evident record outside Cloudflare's control.