- 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:
Generation is never blocked by the meter—going over
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:
Response (success):
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:
Request body:
Response (201):
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:
Response: Same shape as a single item from the
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:
Response:
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:
Actions:
Response (success):
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:
Response:
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:
Response:
An empty
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:
Response (201):
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:
Response:
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):
Response:
{ "state": "gone" } — agreement does not exist or is not active.