{"openapi":"3.1.0","info":{"title":"SumoSign API","version":"1.0.0","description":"Enterprise-grade signing API for AI agents. The API key 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 and an append-only audit trail.\n\nRate limits (in-memory, per org + API key): default 100 req/min; POST /v1/documents 20/min; GET /v1/documents/{id}/analyze 20/min; POST /v1/envelopes 60/min; POST /v1/templates 30/min; POST /v1/envelopes/{id}/send 30/min and 500/day. Exceeding a limit returns 429 with Retry-After. PM2 cluster mode tracks limits per process until Redis is added.\n\nUsage: envelopes sent are counted per org per billing period (Stripe subscription period when on file, otherwise UTC calendar month). Increment on successful send queue accept only — idempotent send replays do not count. GET /v1/usage returns the current period totals. Plan envelope caps are unlimited by default; set PLAN_ENVELOPE_LIMITS in shared constants to enforce hard caps (429 when exceeded).\n\nIdempotency: optional `Idempotency-Key` header (client UUID) on POST /v1/envelopes and POST /v1/envelopes/{id}/send. Same key + same body replays the cached response for 24h (scoped per organization). Same key + different body returns 409."},"servers":[{"url":"https://api.sumosign.app"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Scoped API key (ss_live_...) issued in the SumoSign portal. Also accepted via the x-api-key header."}},"schemas":{"Envelope":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"documentId":{"type":"string","format":"uuid"},"name":{"type":"string"},"message":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","sent","viewed","partially_signed","completed","voided","expired"]},"routingMode":{"type":"string","enum":["parallel","sequential"]},"createdAt":{"type":"string","format":"date-time"},"sentAt":{"type":"string","format":"date-time","nullable":true},"completedAt":{"type":"string","format":"date-time","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true},"completedPdfObjectKey":{"type":"string","nullable":true},"certificateObjectKey":{"type":"string","nullable":true},"recipients":{"type":"array","items":{"type":"object"}},"fields":{"type":"array","items":{"type":"object"}}}},"RecipientInput":{"type":"object","required":["email","name"],"properties":{"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"type":"string","enum":["signer","cc","viewer"],"default":"signer"},"routingOrder":{"type":"integer","minimum":0,"default":0},"accessCode":{"type":"string","minLength":4,"maxLength":64}}},"FieldInput":{"type":"object","required":["type","page","x","y","width","height","recipientIndex"],"properties":{"type":{"type":"string","enum":["signature","initials","date_signed","text","checkbox"]},"page":{"type":"integer","minimum":1,"description":"1-based page number"},"x":{"type":"number","description":"PDF user units, origin bottom-left"},"y":{"type":"number"},"width":{"type":"number"},"height":{"type":"number"},"required":{"type":"boolean","default":true},"recipientIndex":{"type":"integer","minimum":0,"description":"Index into the recipients array this field belongs to"}}},"UsageSummary":{"type":"object","required":["plan","periodStart","periodEnd","periodAlignment","envelopesSent","envelopesCreated","limit"],"properties":{"plan":{"type":"string","enum":["starter","pro","business","enterprise"]},"periodStart":{"type":"string","format":"date-time"},"periodEnd":{"type":"string","format":"date-time","nullable":true},"periodAlignment":{"type":"string","enum":["stripe_billing","calendar_month_utc"],"description":"stripe_billing when a Stripe subscription current_period_end is on file; otherwise calendar month UTC."},"envelopesSent":{"type":"integer","minimum":0},"envelopesCreated":{"type":"integer","minimum":0,"description":"Reserved; not incremented in MVP (send-only tracking)."},"limit":{"type":"integer","nullable":true,"description":"Hard cap on envelopes sent per period for the plan tier, or null for unlimited."}}}}},"paths":{"/v1/documents":{"post":{"summary":"Upload a PDF","security":[{"apiKey":[]}],"description":"Requires scope documents:create. Multipart body with a `file` part (PDF, max 25 MB).","requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"Document metadata including pageCount and sha256 contentHash."},"429":{"description":"Rate limit exceeded. Limits are per organization + API key (agent routes) or per IP (/auth). Response includes Retry-After (seconds). Default: 100 req/min; send: 30/min and 500/day; upload/analyze: 20/min."}}}},"/v1/documents/{id}/analyze":{"get":{"summary":"Analyze a PDF for field placement suggestions","security":[{"apiKey":[]}],"description":"Requires scope documents:read. Text extraction only (no OCR). Returns likely signing pages, a clause outline, and field suggestions with confidence scores — ready for create_envelope fields. If hasExtractableText is false, the PDF is likely scanned; use OCR or human placement instead.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ documentId, pageSizes[], pageCount, hasExtractableText, likelySigningPages, outline[], suggestions[] }. pageSizes is [{ width, height }] in PDF points (index 0 = page 1) for bounds-checking placements."},"429":{"description":"Rate limit exceeded. Limits are per organization + API key (agent routes) or per IP (/auth). Response includes Retry-After (seconds). Default: 100 req/min; send: 30/min and 500/day; upload/analyze: 20/min."}}}},"/v1/documents/{id}":{"get":{"summary":"Get document metadata","security":[{"apiKey":[]}],"description":"Requires scope documents:read.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Document metadata."}}}},"/v1/envelopes":{"post":{"summary":"Create an envelope","security":[{"apiKey":[]}],"description":"Requires scope documents:create. Create from a document (documentId + inline fields) or from a template (templateId + recipients). At least one recipient must be a signer. Send `Idempotency-Key` (UUID) to safely retry without creating duplicate envelopes.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated UUID. When present on POST /v1/envelopes or POST /v1/envelopes/{id}/send, replays with the same key and body return the cached response (same status + body) for 24h. Same key with a different body returns 409. Scoped per organization (not per API key).","schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name","recipients"],"properties":{"documentId":{"type":"string","format":"uuid"},"templateId":{"type":"string","format":"uuid"},"name":{"type":"string"},"message":{"type":"string"},"routingMode":{"type":"string","enum":["parallel","sequential"],"default":"parallel"},"expiresInDays":{"type":"integer","minimum":1,"maximum":365},"recipients":{"type":"array","items":{"type":"object","required":["email","name"],"properties":{"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"type":"string","enum":["signer","cc","viewer"],"default":"signer"},"routingOrder":{"type":"integer","minimum":0,"default":0},"accessCode":{"type":"string","minLength":4,"maxLength":64}}},"minItems":1},"fields":{"type":"array","items":{"type":"object","required":["type","page","x","y","width","height","recipientIndex"],"properties":{"type":{"type":"string","enum":["signature","initials","date_signed","text","checkbox"]},"page":{"type":"integer","minimum":1,"description":"1-based page number"},"x":{"type":"number","description":"PDF user units, origin bottom-left"},"y":{"type":"number"},"width":{"type":"number"},"height":{"type":"number"},"required":{"type":"boolean","default":true},"recipientIndex":{"type":"integer","minimum":0,"description":"Index into the recipients array this field belongs to"}}},"minItems":1}}}}}},"responses":{"201":{"description":"The created envelope in status draft.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"documentId":{"type":"string","format":"uuid"},"name":{"type":"string"},"message":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","sent","viewed","partially_signed","completed","voided","expired"]},"routingMode":{"type":"string","enum":["parallel","sequential"]},"createdAt":{"type":"string","format":"date-time"},"sentAt":{"type":"string","format":"date-time","nullable":true},"completedAt":{"type":"string","format":"date-time","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true},"completedPdfObjectKey":{"type":"string","nullable":true},"certificateObjectKey":{"type":"string","nullable":true},"recipients":{"type":"array","items":{"type":"object"}},"fields":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Validation failed. Off-page placements return { issue: \"field_out_of_bounds\", field, page: { width, height } } — adjust coordinates to the page dimensions and retry."},"409":{"description":"Idempotency-Key was already used with a different request body for this route within the last 24 hours."},"429":{"description":"Rate limit exceeded. Limits are per organization + API key (agent routes) or per IP (/auth). Response includes Retry-After (seconds). Default: 100 req/min; send: 30/min and 500/day; upload/analyze: 20/min."}}},"get":{"summary":"List envelopes","security":[{"apiKey":[]}],"description":"Requires scope documents:read. Returns the most recent 100 envelopes.","responses":{"200":{"description":"Envelope list."}}}},"/v1/envelopes/{id}":{"get":{"summary":"Get envelope status","security":[{"apiKey":[]}],"description":"Requires scope documents:read. Includes per-recipient status and all fields — poll this to track signing progress.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Envelope.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"documentId":{"type":"string","format":"uuid"},"name":{"type":"string"},"message":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","sent","viewed","partially_signed","completed","voided","expired"]},"routingMode":{"type":"string","enum":["parallel","sequential"]},"createdAt":{"type":"string","format":"date-time"},"sentAt":{"type":"string","format":"date-time","nullable":true},"completedAt":{"type":"string","format":"date-time","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true},"completedPdfObjectKey":{"type":"string","nullable":true},"certificateObjectKey":{"type":"string","nullable":true},"recipients":{"type":"array","items":{"type":"object"}},"fields":{"type":"array","items":{"type":"object"}}}}}}}}}},"/v1/envelopes/{id}/preview":{"get":{"summary":"Download a field-placement preview PDF","security":[{"apiKey":[]}],"description":"Requires scope documents:download. Returns 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 — no email is sent and no envelope is consumed.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"application/pdf bytes."}}}},"/v1/envelopes/{id}/send":{"post":{"summary":"Send the envelope","security":[{"apiKey":[]}],"description":"Requires scope documents:send. Validates the draft, flips status to sent, enqueues an envelope.dispatch job (pg-boss), and returns immediately. Token issuance and signing-invite emails run asynchronously in the API worker. Poll GET /v1/envelopes/{id} until recipient statuses leave pending. The envelope.sent audit event is recorded on queue accept (when status becomes sent), not after emails deliver. Only drafts can be sent. Send `Idempotency-Key` (UUID) to safely retry without re-enqueueing dispatch or duplicating audit events.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated UUID. When present on POST /v1/envelopes or POST /v1/envelopes/{id}/send, replays with the same key and body return the cached response (same status + body) for 24h. Same key with a different body returns 409. Scoped per organization (not per API key).","schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Send accepted; dispatch queued.","content":{"application/json":{"schema":{"type":"object","required":["jobId","status","envelopeId"],"properties":{"jobId":{"type":"string","format":"uuid","description":"pg-boss job id for envelope.dispatch"},"status":{"type":"string","enum":["queued"]},"envelopeId":{"type":"string","format":"uuid"}}}}}},"409":{"description":"Idempotency-Key was already used with a different request body for this route within the last 24 hours."},"429":{"description":"Rate limit exceeded. Limits are per organization + API key (agent routes) or per IP (/auth). Response includes Retry-After (seconds). Default: 100 req/min; send: 30/min and 500/day; upload/analyze: 20/min."}}}},"/v1/usage":{"get":{"summary":"Current billing-period usage","security":[{"apiKey":[]}],"description":"Requires scope documents:read. Returns envelopes sent in the current billing period for the organization. Period aligns to the Stripe subscription current_period_end when on file; otherwise UTC calendar month. Idempotent send replays are not counted.","responses":{"200":{"description":"Usage summary for the active period.","content":{"application/json":{"schema":{"type":"object","required":["plan","periodStart","periodEnd","periodAlignment","envelopesSent","envelopesCreated","limit"],"properties":{"plan":{"type":"string","enum":["starter","pro","business","enterprise"]},"periodStart":{"type":"string","format":"date-time"},"periodEnd":{"type":"string","format":"date-time","nullable":true},"periodAlignment":{"type":"string","enum":["stripe_billing","calendar_month_utc"],"description":"stripe_billing when a Stripe subscription current_period_end is on file; otherwise calendar month UTC."},"envelopesSent":{"type":"integer","minimum":0},"envelopesCreated":{"type":"integer","minimum":0,"description":"Reserved; not incremented in MVP (send-only tracking)."},"limit":{"type":"integer","nullable":true,"description":"Hard cap on envelopes sent per period for the plan tier, or null for unlimited."}}}}}},"429":{"description":"Rate limit exceeded. Limits are per organization + API key (agent routes) or per IP (/auth). Response includes Retry-After (seconds). Default: 100 req/min; send: 30/min and 500/day; upload/analyze: 20/min."}}}},"/v1/envelopes/{id}/void":{"post":{"summary":"Void the envelope","security":[{"apiKey":[]}],"description":"Requires scope documents:send. Revokes all signing tokens. Reason is audit-logged.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string"}}}}}},"responses":{"201":{"description":"Envelope in status voided."}}}},"/v1/envelopes/{id}/audit":{"get":{"summary":"Get the audit trail","security":[{"apiKey":[]}],"description":"Requires scope audit_logs:read. Append-only, hash-chained events with actor attribution (user, api_key, recipient, system).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Chronological audit events."}}}},"/v1/envelopes/{id}/download":{"get":{"summary":"Download the signed PDF","security":[{"apiKey":[]}],"description":"Requires scope documents:download. Available once status is completed.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"application/pdf bytes."}}}},"/v1/envelopes/{id}/certificate":{"get":{"summary":"Download the Certificate of Completion","security":[{"apiKey":[]}],"description":"Requires scope documents:download.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"application/pdf bytes."}}}},"/v1/templates":{"post":{"summary":"Create a template from a PDF with text anchors","security":[{"apiKey":[]}],"description":"Requires scope templates:write. Upload a PDF first, embed anchors like {{signature_1}} / {{date_signed_1}}, then create the template. Anchors are parsed once into stored field coordinates.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["documentId","name"],"properties":{"documentId":{"type":"string","format":"uuid"},"name":{"type":"string"}}}}}},"responses":{"201":{"description":"Template with recipientDefinitions and fieldDefinitions."},"429":{"description":"Rate limit exceeded. Limits are per organization + API key (agent routes) or per IP (/auth). Response includes Retry-After (seconds). Default: 100 req/min; send: 30/min and 500/day; upload/analyze: 20/min."}}},"get":{"summary":"List templates","security":[{"apiKey":[]}],"description":"Requires scope templates:read.","responses":{"200":{"description":"Template list."}}}},"/v1/templates/starter-pack":{"post":{"summary":"Seed SumoSign starter templates","security":[{"apiKey":[]}],"description":"Requires scope templates:write. Creates three sample templates (test envelope, simple NDA, one-page agreement) with baked-in anchors. Idempotent per template name.","responses":{"201":{"description":"Created or existing starter templates."}}}},"/v1/templates/{id}":{"get":{"summary":"Get a template","security":[{"apiKey":[]}],"description":"Requires scope templates:read.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Template detail."}}}},"/v1/webhooks":{"post":{"summary":"Create a webhook subscription","security":[{"apiKey":[]}],"description":"Requires scope webhooks:write. Returns the HMAC signing secret (whsec_...) exactly once. Deliveries carry an x-sumosign-signature header: t=<unix>,v1=hex(hmac_sha256(secret, \"<t>.<body>\")). Subscribe to specific audit event types or \"*\".","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"},"minItems":1}}}}}},"responses":{"201":{"description":"Subscription including the one-time secret."}}},"get":{"summary":"List webhook subscriptions","security":[{"apiKey":[]}],"responses":{"200":{"description":"Subscriptions (secret prefix only)."}}}},"/v1/sign/{token}":{"get":{"summary":"Recipient signing session (public)","description":"No API key — the one-time token from the signing email is the sole authority. Records the first view in the audit trail.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Envelope summary, recipient, and the fields to complete."}}}},"/v1/sign/{token}/document":{"get":{"summary":"The PDF for review (public)","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"application/pdf bytes."}}}},"/v1/sign/{token}/consent":{"post":{"summary":"Record electronic-business consent (public)","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Consent recorded with IP and user agent."}}}},"/v1/sign/{token}/complete":{"post":{"summary":"Sign (public)","description":"The human affirmative act. Requires consent: true and values for all required fields. date_signed fields are server-filled.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["consent","values"],"properties":{"consent":{"type":"boolean","enum":[true]},"values":{"type":"array","items":{"type":"object","required":["fieldId","value"],"properties":{"fieldId":{"type":"string","format":"uuid"},"value":{"oneOf":[{"type":"string"},{"type":"boolean"}],"description":"signature/initials: PNG data URL or typed text; text: string; checkbox: boolean"}}}}}}}}},"responses":{"201":{"description":"{ ok: true }. Envelope advances or completes."}}}},"/v1/sign/{token}/decline":{"post":{"summary":"Decline to sign (public)","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string"}}}}}},"responses":{"201":{"description":"Recipient declined; token revoked."}}}}}}