- Authenticated routes — bearer-token, org-scoped. Used by the in-app Document OS UI and your own integrations.
- Public routes — unauthenticated, used by the signer page and the clickwrap consent widget.
401 behavior.
Authentication
Authenticated routes expect the session bearer token forwarded byapps/app:
/api/public/sign/:token and /api/public/clickwrap/:agreementId) require no auth. The capability is the token or agreement ID embedded in the URL.
Document usage
GET /api/documents/usage
Returns the current calendar-month document usage for the organization.
Response:
| Field | Type | Description |
|---|---|---|
used | integer | Documents rendered (generated) this calendar month. |
included | integer or null | The plan’s monthly allotment from DOCUMENTS_INCLUDED_PER_MONTH. null when unset (the meter still counts, but the allotment is not enforced). |
periodLabel | string | Human-readable month label, e.g. "June 2026". |
included triggers overage billing, not a hard gate.
AI draft
POST /api/documents/draft
Generate a Zod-validated canonical draft from a natural-language prompt. The AI proposes; a human reviews in the wizard before generation. The response is a pre-filled canonical suitable for wizard pre-fill, never a completed document.
Requires the AI gateway to be configured (@repo/ai + a chat model in models.json).
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Natural-language description of the document. |
reason | Meaning |
|---|---|
no_json | The model did not return parseable JSON. |
invalid_draft | The JSON did not pass Zod validation against the canonical schema. |
error | Unexpected error (e.g. AI gateway unavailable). |
E-signature
POST /api/documents/:id/signature
Create and send a signature envelope for a generated document. The document must be in GENERATED status. Signing parties are derived automatically from the document’s canonical data (for typed OREA packs); you may adjust them in the request.
Returns per-signer signing URLs. The raw tokens appear only in the url fields of the response—they are never stored or logged. Deliver them to the signers; do not log them yourself.
Path parameters:
| Param | Description |
|---|---|
id | The document ID. |
| Field | Type | Default | Description |
|---|---|---|---|
mode | "parallel" or "sequential" | "parallel" | Parallel sends links to all signers at once. Sequential requires each signer to complete before the next receives their link. |
expiresInDays | integer | none | Days until signing links expire. Omit for no expiry. |
cannot_send_for_signature):
Returned when the document is not in GENERATED status, has no signing parties, or is not a typed OREA pack.
GET /api/documents/:id/signatures
List all signature envelopes for a document. Never exposes token hashes.
Response:
GET /api/signatures/:envelopeId
Fetch the current status of a specific envelope, including per-signer status.
Path parameters:
| Param | Description |
|---|---|
envelopeId | The envelope ID returned from POST /api/documents/:id/signature. |
envelopes array above.
Error (404): Returned when the envelope does not exist in the caller’s organization.
DELETE /api/signatures/:envelopeId
Void a signature envelope. Records a voided event with the given reason.
Query parameters:
| Param | Default | Description |
|---|---|---|
reason | "voided by agent" | Human-readable reason for the void. |
cannot_void): Returned when the envelope is already in a terminal status (completed, declined, voided, expired).
Public signer endpoint
GET /api/public/sign/:token
Open the document for the signer identified by the token. Records an opened event. Returns the signer’s view of the document if the token is valid and non-terminal.
Rate limit: 60 requests per IP per minute.
Response (active):
gone response is returned for unknown tokens, expired links, already-signed links, and voided envelopes—no information is disclosed about the reason.
POST /api/public/sign/:token
Advance the signer through the signing flow. The action field controls which step is executed.
Rate limit: 30 requests per IP per minute.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | One of authenticate, consent, sign, decline. |
codeVerified | boolean | For authenticate | Whether the caller has verified the auth code (SMS OTP or KBA). |
reason | string | For decline | Optional decline reason (treated as data, never executed). |
turnstileToken | string | For sign, decline | Cloudflare Turnstile challenge token. Required when TURNSTILE_SECRET_KEY is configured. |
| Action | What it does | Required prior state |
|---|---|---|
authenticate | Records the authenticated event. | opened |
consent | Records the consented event (explicit intent to sign electronically). | authenticated |
sign | Records the signed event; if all signers are done, completes the envelope and generates the certificate. | consented |
decline | Records the declined event with the optional reason. | Any active state |
sign, also includes:
invalid_state): Returned when the action is out of order (e.g. sign without prior consent).
{ "state": "gone" } for unknown/expired/terminal tokens.
Obligations
GET /api/documents/obligations
Return upcoming and overdue obligations across the organization, soonest first. Org-scoped.
Query parameters:
| Param | Default | Description |
|---|---|---|
withinDays | 30 | Look-ahead window in days. Maximum 365. |
| Field | Type | Description |
|---|---|---|
type | string | closing, condition_waiver, deposit_due, expiry, or renewal. |
urgency | string | pending, due_soon, or overdue. |
amountCents | integer or null | For deposit obligations, the amount in cents. |
dealId | string or null | Linked deal, if any. |
Semantic search
GET /api/documents/search
Search the organization’s documents by natural language. Returns ranked document IDs.
Requires the AI gateway to be configured (embedding model).
Query parameters:
| Param | Required | Description |
|---|---|---|
q | Yes | Natural-language query string. |
| Field | Type | Description |
|---|---|---|
documentId | string | The matched document’s ID. |
score | number | Cosine similarity score (0–1). Higher is more similar. |
q returns { "hits": [] } without calling the AI gateway. Without the gateway configured, all queries return an empty hits array.
Clickwrap (Click)
POST /api/clickwrap
Publish (upsert) a clickwrap agreement. If the body text has not changed since the last call with the same key, the call is idempotent. Changing the body creates a new version (new versionHash).
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Stable identifier for this agreement within your organization. |
title | string | Yes | Display title shown on the consent page. |
body | string | Yes | Full agreement text. SHA-256 hashed to produce the versionHash. |
id in the consent widget URL: https://app.winnerr.ai/consent/<id>.
GET /api/clickwrap/:agreementId/acceptances
List acceptance evidence for an agreement. Returns the most recent 200 acceptances, newest first. Org-scoped.
Path parameters:
| Param | Description |
|---|---|
agreementId | The agreement ID from POST /api/clickwrap. |
| Field | Type | Description |
|---|---|---|
versionHash | string | The exact version of the agreement text that was accepted. |
subjectRef | string or null | Optional caller-supplied identifier (person ID, email). |
acceptedAt | string | ISO 8601 timestamp of the acceptance (immutable). |
Public clickwrap endpoints
GET /api/public/clickwrap/:agreementId
Fetch the agreement text for the consent widget. Used by the public /consent/:agreementId page.
Rate limit: 120 requests per IP per minute.
Response:
{ "state": "gone" } — agreement does not exist or is not active.
POST /api/public/clickwrap/:agreementId
Record an immutable acceptance. Body is optional.
Rate limit: 60 requests per IP per minute.
Request body (optional):
| Field | Type | Required | Description |
|---|---|---|---|
subjectRef | string | No | Optional identifier linking this acceptance to a person or lead in your CRM (person ID, email, or other stable reference). |
{ "state": "gone" } — agreement does not exist or is not active.
Events emitted
The Document OS routes emit the following events on the internal event bus:| Event | Fired when |
|---|---|
DOCUMENT_SIGNED | All signers on an envelope have signed (envelope reaches completed). Downstream: obligation re-sync, deal-stage advance, closing-checklist spawn. |