# SumoSign API > Enterprise-grade signing API for AI agents. The API key (ss_live_...) authenticates the sending agent and can never complete a signature. Every signature is made by a human recipient through a one-time signing token with consent capture. All actions land in an append-only, hash-chained audit log with actor attribution. ## Base URL - Production: https://api.sumosign.app (domain is .app, not .com) - Local dev: http://localhost:3001 - All paths below are relative to the base URL (e.g. POST https://api.sumosign.app/v1/documents). ## Authentication - Header: `Authorization: Bearer ss_live_...` (or `x-api-key`). - Keys are issued in the SumoSign portal, scoped (e.g. documents:create, documents:send, documents:download, audit_logs:read, webhooks:write), hashed at rest, and revocable. ## Rate limits - Tracked per organization + API key (not per IP). Exceeding a limit returns HTTP 429 with a `Retry-After` header (seconds). - Default: 100 requests/minute on all agent endpoints. - Stricter limits: upload 20/min; analyze 20/min; create envelope 60/min; create template 30/min; send 30/min and 500/day. - In-memory counters (PM2 cluster ×2 = per-process limits until Redis). Back off when you receive 429. ## Usage (billing period) - `GET /v1/usage` — requires documents:read. Returns { plan, periodStart, periodEnd, periodAlignment, envelopesSent, envelopesCreated, limit } for the current billing period. - Period alignment: `stripe_billing` when the org has a Stripe subscription with current_period_end; otherwise `calendar_month_utc` (first day of month UTC). - `envelopesSent` increments on successful send queue accept (same moment as envelope.sent audit). Idempotent send replays do not increment. - `envelopesCreated` is reserved for future use (not incremented in MVP). - Plan caps: unlimited by default (limit: null). Hard caps return HTTP 429 on send when PLAN_ENVELOPE_LIMITS is set for a tier. ## Idempotency (recommended on create + send) - Header: `Idempotency-Key: ` (optional but recommended on mutating calls). - Supported on `POST /v1/envelopes` and `POST /v1/envelopes/{id}/send`. - Scoped per organization (same key across API keys in one org collides intentionally). - Same key + same request body → cached response (same HTTP status + JSON body) for 24h. Create replays return the same envelope id; send replays return the same { jobId, status, envelopeId } without re-enqueueing dispatch. - Same key + different body → HTTP 409 Conflict. - Create body hash: stable canonical JSON of the full body. Send has no body; route includes envelope id. ## Core flow 1. `POST /v1/documents` — multipart upload of a PDF (field name `file`). Returns { id, pageCount, contentHash }. 2. Field placement (pick one — see **Field placement ladder** below): - **Template (repeat sends):** embed text anchors in the PDF (`{{signature_1}}`, `{{date_signed_1}}`, `{{initials_1}}`, `{{text_1}}`, `{{checkbox_1}}`), then `POST /v1/templates` with { documentId, name }. Or call `POST /v1/templates/starter-pack` for three sample templates. - **Analyze (one-off PDF):** `GET /v1/documents/{id}/analyze` — returns { pageCount, hasExtractableText, likelySigningPages, outline[], suggestions[] }. Use suggestions[] as fields on `POST /v1/envelopes` (confirm/adjust before send). No OCR — scanned PDFs return hasExtractableText: false. - **Coordinates (manual):** pass fields inline on `POST /v1/envelopes` with documentId. 3. `POST /v1/envelopes` — JSON body with exactly one of: - { documentId, name, recipients, fields, routingMode?, message?, expiresInDays? } - { templateId, name, recipients, routingMode?, message?, expiresInDays? } — fields inherited from the template; recipient count must match the template. Coordinates are PDF user units, origin bottom-left. Off-page fields are rejected with HTTP 400 { issue: "field_out_of_bounds", field, page: { width, height } } — read the page dimensions from the error (or from analyze pageSizes) and correct. Returns the envelope in status "draft". 4. **Preview before send (recommended when coordinates came from analyze or estimation):** `GET /v1/envelopes/{id}/preview` — returns the PDF with every field drawn as a dashed, labeled box. No email is sent and no envelope is consumed. Show it to a human for confirmation, then send. 5. `POST /v1/envelopes/{id}/send` — accepts the send (202), flips status to sent, enqueues token + email dispatch via pg-boss. Returns { jobId, status: "queued", envelopeId }. Poll `GET /v1/envelopes/{id}` until recipients show status sent (signing invites delivered). Audit event envelope.sent fires on queue accept, not after mail delivery. 6. Poll `GET /v1/envelopes/{id}` — statuses: draft, sent, viewed, partially_signed, completed, voided, expired. Includes per-recipient status. 7. On "completed": `GET /v1/envelopes/{id}/download` (signed PDF), `GET /v1/envelopes/{id}/certificate` (Certificate of Completion), `GET /v1/envelopes/{id}/audit` (audit trail). ## Templates - `POST /v1/templates` — body { documentId, name }. Requires templates:write. Parses anchors from the uploaded PDF. - Anchor tokens are automatically covered with white at completion, so they never appear in the signed PDF. When generating PDFs yourself you may additionally render anchors in white (invisible) text — placement works either way. - `GET /v1/templates`, `GET /v1/templates/{id}` — requires templates:read. - `POST /v1/templates/starter-pack` — seeds SumoSign sample templates for integration testing. ## Document analyze - `GET /v1/documents/{id}/analyze` — requires documents:read. Text-based field suggestions for one-off PDFs (labels like "Signature:", "Date:", underscore runs). Each suggestion includes { type, page, x, y, width, height, confidence, matchedText, recipientIndex? }. Also returns pageSizes: [{ width, height }] (PDF points, index 0 = page 1) so you can keep placements in bounds. recipientIndex on suggestions is a heuristic — confirm assignment before send. ## Envelope preview - `GET /v1/envelopes/{id}/preview` — requires documents:download. The source PDF with every field drawn as a dashed box labeled with field type and recipient name, plus a PREVIEW banner. Use before send to verify placement with a human; no email, no envelope consumed. ## Field placement ladder (never blind-guess coordinates) 1. **Template** — repeatable docs; embed `{{signature_N}}` anchors or use an existing template. 2. **SumoSign analyze** — `GET /v1/documents/{id}/analyze` after upload; agent confirms suggestions before send. 3. **Agent-side text extraction** — PyMuPDF / pdfplumber locally if analyze misses labels. 4. **Embed anchors** — agent-generated PDFs include `{{signature_N}}` at generation time. 5. **OCR** — if hasExtractableText is false (scanned PDF); agent-side or human review. 6. **Vision** — last resort only; never auto-send without human/agent confirmation. ## Other operations - `POST /v1/envelopes/{id}/void` — body { reason }. Revokes signing tokens. - `POST /v1/webhooks` — body { url, events: ["envelope.completed", ...] or ["*"] }. Returns the HMAC secret once. Verify deliveries via x-sumosign-signature: t=,v1=hex(hmac_sha256(secret, ".")). ## The human checkpoint (do not attempt to bypass) Recipients sign at /v1/sign/{token} endpoints using the emailed one-time token: view session, fetch the PDF, POST consent, POST complete (with consent: true and field values), or POST decline. Agents cannot and must not perform this step — it is the human affirmative act that keeps the workflow enforceable under ESIGN 15 U.S.C. §7001(h) and UETA. ## Machine-readable spec - OpenAPI 3.1: /openapi.json