DrakonSign API
Version 0.18.0 · OpenAPI document · interactive reference
REST API for DrakonSign — electronic signatures with a tamper-evident trust layer. When an envelope completes, DrakonSign produces a signed PDF, a separate completion-snapshot certificate and a hash-chained audit trail. When both the signing key and RFC 3161 timestamp authority are available, the PDF receives a self-issued PAdES B-T seal and the chain receives an RFC 3161 anchor only if the authority answers. A timestamp outage produces a recorded PAdES B-B degradation when the signature itself validates, and otherwise records a timestamp present but unverified, a signature that did not verify, or an unknown seal state; a sealing failure can leave recorded unsealed completion artifacts rather than an unqualified sealed outcome. The digital_seal value on the completion is measured from the produced file and is authoritative.
DrakonSign is a hosted service operated by Drakon Systems Ltd from London: the application and database run on Fly.io lhr, and live documents are held in a single-region London (lhr) Tigris bucket. Encrypted nightly backups go to a separate, globally distributed Tigris bucket with 30-day retention. Authenticate with an organisation API key in the X-API-Key header; the dashboard uses a session cookie, and billing is session-only so a leaked key cannot manage a subscription.
Authentication. Programmatic callers send an organisation API key
in the X-API-Key header. Create one from
Account → API keys in the dashboard; the key is shown once, at
creation, and only its hash is stored. The dashboard itself uses a session
cookie, and a few surfaces — billing, and anything that manages keys — are
session-only by design, so a leaked API key cannot manage the subscription.
This page is rendered by the API itself and loads no scripts and no third-party assets.
Contents
- Health (3)
- Auth (7)
- Account security (8)
- Documents (29)
- Templates (3)
- Signing (13)
- Billing (3)
- Account (15)
- Webhooks (8)
Health
GET /api/health
Health Check
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response |
GET /api/health/ready
Readiness
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response |
GET /api/ping
Ping
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response |
Auth
POST /api/auth/accept-invite
Accept Invite
Accept an org invitation and sign in. POST only, token in the body.
Never a GET: SameSite=Lax permits a top-level cross-site GET, so a GET accept would be CSRF-able from a link in any email — someone could be joined to a workspace by clicking a link that looked like something else. The token arrives in the URL fragment, which the static /invite page reads client-side and POSTs here, so it never reaches an access log.
On success this mints an ordinary session through the same _start_session the magic-link flow uses — there is no second session mechanism, and the new member's device gets the same new-sign-in alert every other sign-in produces. MFA cannot apply: the account is being created by this request, so it has no second factor to present.
Request body required
application/json — AcceptInviteIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/auth/from-checkout
From Checkout
Instant entry after Stripe checkout: turn a paid session_id into a session.
401 if the checkout isn't a genuine paid session; 409 if it's paid but the provisioning webhook hasn't landed yet (the client should retry shortly).
Request body required
application/json — FromCheckoutIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/auth/logout
Logout
Sign out THIS device, and only this device.
Before M11 this bumped a version number and signed every device out — a surprise for anyone who closed a session on a borrowed laptop and found their phone logged out too. "Sign out everywhere" is now a separate, deliberate action on the security page.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
GET /api/auth/me
Me
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/auth/mfa
Verify Mfa
Complete sign-in by presenting the second factor.
Failure is a single generic 401 for every cause — no challenge, expired challenge, attempts exhausted, wrong code, replayed timestep, spent recovery code, locked enrolment. The client cannot tell them apart, which is what keeps this from being an oracle about the account's state.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_mfa | cookie | string or null |
Request body required
application/json — MfaVerifyIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/auth/request-link
Request Link
Email a sign-in link if the address maps to an account. Always 200.
Only the same indexed lookup runs before the response for either branch. For a candidate account, fenced minting, the atomic JTI/frozen-request commit and delivery all begin after the body is flushed. Within that background phase the provider still cannot run before the commit, and account closure is rechecked under the shared organization fence.
Request body required
application/json — RequestLinkIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/auth/verify
Verify
Exchange a one-time login token for a session cookie.
When the account has a confirmed second factor this returns {"mfa_required": true} and an ds_mfa challenge cookie INSTEAD of a session: possession of the mailbox is one factor, and on its own it is exactly the thing MFA exists to stop being sufficient.
Request body required
application/json — VerifyIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
Account security
GET /api/account/security
Get Security
The security page's whole state: MFA status, code count, live devices.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/account/security/mfa/begin
Begin Mfa
Stage a pending TOTP secret and return it with a locally rendered QR.
Returning the secret in plaintext here is unavoidable and correct — it is the one moment it has to reach the authenticator app — and it is why this route needs fresh reauthentication: the response IS the enrolment material.
Refuses outright when MFA is already on. Re-enrolling is a "replace my factor" operation and must go through disable (which requires the current factor) rather than quietly overwriting an active secret.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
403 | This session did not prove its credentials recently enough. The body is {"detail": {"code": "reauthentication_required", "message": ...}} — request a fresh sign-in link and retry. | |
409 | Two-step sign-in is already on; disable it first. | |
503 | Enrolment is closed while MFA_ENCRYPTION_KEY is rotated. Existing sign-ins, existing second factors and every other route are unaffected; retry shortly. | |
422 | Validation Error | HTTPValidationError |
POST /api/account/security/mfa/confirm
Confirm Mfa
Activate the pending secret and issue recovery codes, shown once.
Turning MFA on also signs every OTHER device out. If the reason someone is enrolling is "I think somebody else has access", leaving that somebody's session alive would defeat the entire exercise. This device stays signed in — it has just proved the new factor — but on a NEW cookie value, so a copy of the credential it was using dies with the change instead of surviving it.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Request body required
application/json — ConfirmMfaIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
403 | This session did not prove its credentials recently enough. The body is {"detail": {"code": "reauthentication_required", "message": ...}} — request a fresh sign-in link and retry. | |
400 | The code did not verify against the pending secret, or that secret has expired. Start enrolment again. | |
503 | Enrolment is closed while MFA_ENCRYPTION_KEY is rotated. Existing sign-ins, existing second factors and every other route are unaffected; retry shortly. | |
422 | Validation Error | HTTPValidationError |
POST /api/account/security/mfa/disable
Disable Mfa
Turn the second factor off. Requires fresh sign-in AND a current code.
Every other device is signed out and this one's cookie value is replaced, for the same reason as enabling: the account's authentication just got weaker, and a copy of the credential in use must not be one of the things that survives it.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Request body required
application/json — DisableMfaIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
403 | This session did not prove its credentials recently enough. The body is {"detail": {"code": "reauthentication_required", "message": ...}} — request a fresh sign-in link and retry. | |
401 | The submitted second factor was not accepted. One generic message covers every cause, deliberately. | |
409 | Two-step sign-in is not on for this account. | |
422 | Validation Error | HTTPValidationError |
POST /api/account/security/mfa/recovery-codes
Regenerate Recovery Codes
Replace the whole recovery-code set. The old codes stop working at once.
Other sessions are deliberately NOT revoked here: this is a routine hygiene action ("I used one, give me a fresh sheet"), not a response to compromise, and signing someone's other devices out for it would train them to avoid doing it.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Request body required
application/json — RegenerateCodesIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
403 | This session did not prove its credentials recently enough. The body is {"detail": {"code": "reauthentication_required", "message": ...}} — request a fresh sign-in link and retry. | |
401 | The submitted second factor was not accepted. One generic message covers every cause, deliberately. | |
409 | Two-step sign-in is not on for this account. | |
422 | Validation Error | HTTPValidationError |
GET /api/account/security/sessions
List Sessions
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/account/security/sessions/revoke-all
Revoke Other Sessions
Sign out every OTHER device, keeping this one.
Keeping the current session is the whole usability of the control: the person doing it is reacting to something and should not be thrown out of the page they are reacting on. "Sign out of this one too" is the ordinary Sign out button, one click away.
Deliberately NOT behind the reauthentication gate. Every gated action WEAKENS the account (bind a new factor, remove the existing one, reprint the codes); this one strengthens it. Someone who has just seen a sign-in they do not recognise must be able to act in one click, not go and fetch a fresh link from the mailbox that may itself be the problem.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
DELETE /api/account/security/sessions/{session_id}
Revoke Session
End one named session.
Ownership is checked against the CALLER'S user id, and a row belonging to anyone else is a flat 404 — not a 403. A distinguishable "exists but isn't yours" would turn session ids into a cross-tenant existence oracle, and the id itself tells an attacker nothing they can use.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
session_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
404 | No live session with that id belongs to the caller. A session owned by someone else is deliberately indistinguishable from one that never existed. | |
422 | Validation Error | HTTPValidationError |
Documents
GET /api/documents
List Documents
Org-scoped document list, newest first (the dashboard's home query).
Sync on purpose, like every handler here that touches the database: the Session is synchronous, so the connection-pool checkout and the query round-trip block the calling thread — that wait belongs in the threadpool, not on the event loop (see send_for_signing).
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
limit | query | integer | ||
offset | query | integer | ||
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | array of DocumentListOut |
422 | Validation Error | HTTPValidationError |
POST /api/documents
Upload Document
Sync on purpose: hashing, the capacity check and the blob write all block, so FastAPI must dispatch this to a threadpool (see send_for_signing). The body is read from UploadFile's spooled file with a hard cap so an oversized body can't exhaust the VM's memory.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Request body required
multipart/form-data — Body_upload_document_api_documents_post
Responses
| Status | Meaning | Body |
|---|---|---|
201 | Successful Response | DocumentOut |
400 | The upload is not a valid supported PDF. | |
409 | The organization is closed. | |
413 | The upload exceeds the configured cap. | |
500 | Upload failed and storage artifact cleanup remains pending. | |
507 | The organization storage allowance or volume capacity is exhausted. | |
422 | Validation Error | HTTPValidationError |
GET /api/documents/cancellation-notices/manual-review
List Cancellation Manual Review
Return the authenticated org's sanitized intervention queue.
Recipient addresses, document titles, frozen provider HTML and raw provider errors are deliberately absent. The queue is owner-session-only; no API caller and no admin/member session may adjudicate an unknowable provider outcome.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
limit | query | integer | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | array of CancellationManualReviewOut |
422 | Validation Error | HTTPValidationError |
GET /api/documents/{document_id}
Read Document
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | DocumentOut |
422 | Validation Error | HTTPValidationError |
DELETE /api/documents/{document_id}
Delete Document
Attempt to erase an envelope in this request — irreversible on success.
Blobs and database rows are destroyed, not flagged. Two outcomes:
- {"status": "deleted"} — no signature was ever issued; the envelope and its entire record are gone. - {"status": "erased"} — a signature had been issued; the tamper-evident audit record is retained so the signature stays integrity-verifiable. The files and field values are destroyed; what remains is the hash-chained event log — event types, timestamps, network metadata, the envelope title and the SHA-256 fingerprints of the destroyed artifacts (DPA §9.2).
Session or API key: the DPA promises deletion "through the dashboard or API". API keys additionally need the can_delete scope, granted when the key is minted — a leaked everyday integration key must not be able to raze the org's envelopes. Idempotent on the residue — repeating the DELETE returns "erased" without touching the retained record.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Database erasure committed. If verified post-commit blob removal fails or raises, cleanup_pending=true and a recovery warning are returned. | |
403 | An API key without delete scope cannot erase documents. | |
404 | Tenant-scoped document not found. | |
409 | Erasure is blocked by provider-crossed lifecycle email or AI submission uncertainty that requires controlled support reconciliation. | |
503 | Lifecycle delivery coordination is busy; retry safely. | |
422 | Validation Error | HTTPValidationError |
POST /api/documents/{document_id}/ai/analysis
Analyse Document
Key clauses + risk flags for the *sender*, before they send.
Advisory only: never stored on the audit chain, the seal or the certificate. Returns available: false rather than an error whenever we cannot produce something useful — the org hasn't opted in, no key is configured, the PDF has no extractable text, or the model gave us nothing. AI must not be able to turn a working page into an error.
Sync on purpose (see send_for_signing): this blocks on Anthropic and must run in a threadpool, not on the event loop.
The result is cached against the original's SHA-256, so repeat views cost nothing and a client calling this in a loop cannot run up our Anthropic bill. Draft-only for the same reason — it is a pre-send aid, and a sent envelope's text does not change.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | ClauseAnalysisOut |
422 | Validation Error | HTTPValidationError |
POST /api/documents/{document_id}/ai/suggest-fields
Suggest Document Fields
Auto-detect signing fields in the stored PDF and persist them.
Detection is deterministic text-anchor matching, so these land with placement_source="anchor". The "/ai/" in the path is a legacy name that integrations depend on — it does not mean a model ran.
Sync on purpose (see send_for_signing): the blocking storage read plus CPU-bound PDF text extraction must run in the threadpool, not on the event loop.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
201 | Successful Response | array of FieldOut |
422 | Validation Error | HTTPValidationError |
GET /api/documents/{document_id}/anchor
Read Anchor
One RFC 3161 checkpoint token + metadata, for independent verification.
Without kind, returns the envelope's primary TERMINAL checkpoint — which for a completed envelope is the completion anchor this endpoint has always returned. Use GET /anchors for the whole set.
Sync on purpose — synchronous database work (see list_documents).
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
kind | query | string or null | ||
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | ChainAnchorOut |
422 | Validation Error | HTTPValidationError |
POST /api/documents/{document_id}/anchor
Create Anchor
Anchor a terminal prefix, or return an existing *validated* checkpoint.
Without kind this targets the envelope's primary TERMINAL checkpoint — for a completed envelope, the COMPLETED-event prefix this endpoint has always anchored. kind=signatures recovers the checkpoint the completion certificate reports; kind=declined|voided|expired recovers a terminal outcome whose checkpoint the TSA was not available for.
201 new checkpoint · 200 already checkpointed and valid · 409 no valid target prefix or stored token invalid · 502 TSA down/invalid new token · 503 trusted-root configuration missing. A corrupt/forged stored token is evidence and is never silently replaced. Retry applies only while no row exists for that kind.
Sync on purpose (see send_for_signing): anchoring blocks on the TSA round-trip (sealing._run joins its worker thread for up to the TSA timeout), so this must run in the threadpool, not on the event loop.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
kind | query | string or null | ||
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Existing checkpoint over the target prefix was cryptographically validated. | ChainAnchorOut |
201 | A new RFC 3161 checkpoint was created over the target event's prefix. | |
400 | An unknown checkpoint kind was requested. | |
409 | The retained history has no target event for the requested (or terminal) checkpoint kind, its exact prefix fails integrity, or the stored token is not exactly over that prefix and cannot be replaced in place. | |
502 | No anchor exists and the timestamp authority did not answer, or it returned a new token that failed immediate validation. | |
503 | Trusted TSA roots are absent or malformed, so neither an existing token nor a newly requested token can be independently validated. | |
422 | Validation Error | HTTPValidationError |
GET /api/documents/{document_id}/anchors
Read Anchors
Every stored checkpoint for the envelope, tokens included.
This is the endpoint the completion certificate points a reader at: the certificate reproduces the signatures checkpoint it could describe when its bytes were frozen, and the completion checkpoint that covers the certificate's own SHA-256 is fetched from here.
Sync on purpose — synchronous database work (see list_documents).
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | ChainAnchorListOut |
422 | Validation Error | HTTPValidationError |
GET /api/documents/{document_id}/audit
Read Audit Trail
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | array of AuditEventOut |
422 | Validation Error | HTTPValidationError |
GET /api/documents/{document_id}/audit/verify
Verify Audit Trail
Recompute the full hash chain and validate every typed checkpoint.
An RFC 3161 checkpoint covers only the event prefix present at its target event. The response reports each checkpoint's covered prefix separately from the later internally chained events outside that timestamp, and the top-level anchor_* fields describe the envelope's primary TERMINAL checkpoint — completion for a completed envelope, the declined/voided/ expired checkpoint otherwise.
Sync on purpose (see send_for_signing): rehashing the full chain and the anchors' CMS signature validation are CPU-bound work that blocks the calling thread — it belongs in the threadpool, not on the event loop.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | AuditVerifyOut |
422 | Validation Error | HTTPValidationError |
GET /api/documents/{document_id}/certificate
Download Certificate
The certificate of completion (PDF).
The exact stored bytes must match the certificate's recorded SHA-256 before a DOWNLOADED event is appended or any content is served. A missing fingerprint, missing blob or mismatch returns 409. Successful responses are private/no-store and carry nosniff.
Sync + document-first lock, same as the signed download: this read also appends to the audit chain, so it must serialise against a concurrent erasure rather than race it.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | SHA-256-verified completion certificate; private/no-store and nosniff. | string (binary) |
409 | Document is not completed or certificate evidence is missing/corrupt. | |
422 | Validation Error | HTTPValidationError |
POST /api/documents/{document_id}/correct
Correct Document Route
Void this envelope and create a corrected DRAFT copy of it.
A sent envelope is evidence: its bytes, fields, recipients and signatures are never edited in place. This is the sanctioned way to fix a mistake — the original is cancelled and a new draft carries forward the exact original PDF, the recipient list and the field placements, with fresh ids, no signature values and no tokens. Both audit chains record the link and the reason, so the pair reads as one story from either end. The new draft is never auto-sent: review and send it like any other. Creating it reserves and consumes one new envelope immediately; that atomic reservation keeps the exact successor sendable if the allowance fills or subscription lapses later.
201 a corrected draft was created · 200 its previously created, still-editable draft is returned (idempotent — a double-click voids nothing twice, while undelivered durable notices may be retried) · 409 the envelope cannot be corrected (a draft is still editable, a completed envelope is never voided, an erased one has nothing to copy, the recorded successor is missing/erased/no longer a draft, or the stored original is missing or no longer matches its fingerprint) · 402 no new envelope is available (the source remains live and untouched) · 507 the copy would exceed the org's storage allowance.
Session or API key, and deliberately no extra key scope: this composes /void and a blob clone, both of which any org-scoped key may already do, so requiring the delete scope here would only push callers to the lower-level route. It destroys nothing — the source and every artifact and audit event it owns are preserved exactly as they were.
Sync on purpose (see send_for_signing): it blocks on a PDF read, a blob write, the document row lock and — after the commit — outbound email.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Request body required
application/json — CorrectIn
Responses
| Status | Meaning | Body |
|---|---|---|
201 | A new corrected draft was created. | CorrectionOut |
200 | Idempotent retry returning the existing corrected draft. | CorrectionOut |
400 | The correction reason or source PDF cannot be processed. | |
402 | The active/trial subscription has no envelope available. Nothing is voided or created. A structured response includes cleanup_pending=true and a recovery message if the temporary copy could not be removed. | |
404 | The organization/source is absent, or the correction committed and the replacement was concurrently erased; the latter returns structured correction_committed and consumed-allowance truth. | |
409 | The source is not correctable, or its one durable successor lineage has already been consumed and is unavailable. A structured response includes cleanup_pending=true and a recovery message if the temporary copy could not be removed. | |
503 | Lifecycle coordination is busy, or the correction committed and only durable cancellation-notification processing remains pending; retry safely to drain due work. | |
500 | Correction failed after publishing storage and immediate artifact cleanup could not be confirmed; cleanup_pending is returned. | |
507 | The corrected PDF copy cannot be stored within the organization's storage allowance; the source remains unchanged. | |
422 | Validation Error | HTTPValidationError |
GET /api/documents/{document_id}/download
Download Signed Pdf
The signed artifact. Every successful download is recorded on the audit chain.
The exact stored bytes must match the recorded SHA-256 (signed_sha256) before a DOWNLOADED event is appended or any content is served. A missing fingerprint, missing blob or mismatch returns 409. Successful responses are private/no-store and carry nosniff.
Sync (threadpool) and holding the same document-first lock the writers take: without it a concurrent DELETE can commit its erasure between our status check and our audit append, and this route would then hand out bytes the API says are gone and append DOWNLOADED to a sealed chain (or 500 on a duplicated chain sequence). Locked, the two serialise: whichever wins, the other sees committed truth.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | SHA-256-verified completed signed PDF; its recorded seal state is the one measured on that file (see the completion payload's digital_seal). Private/no-store and nosniff. | string (binary) |
409 | Document is not completed or the signed artifact is missing/corrupt. | |
422 | Validation Error | HTTPValidationError |
GET /api/documents/{document_id}/evidence
Download Evidence Bundle
The detached, write-once evidence bundle for a completed envelope.
This is the artifact that makes the evidence independently verifiable: the canonical audit records the hash chain covers, every RFC 3161 token in DER form, the authority's trust anchors as PEM, the artifact hashes and a deterministic verification procedure — none of which the human-readable certificate can carry, and none of which should require an authenticated call to Drakon to obtain.
Published once, after the completion checkpoint resolves; 409 until then, with the reason, because freezing a write-once artifact around "no completion checkpoint exists" while one is queued would be untrue.
Sync + document-first lock, same as the certificate download: it appends to the audit chain and must serialise against a concurrent erasure.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | SHA-256-verified evidence bundle: a PDF carrying the canonical evidence manifest as an embedded evidence.json attachment; private/no-store and nosniff. | string (binary) |
409 | The envelope is not completed, its completion checkpoint is still outstanding, or the stored bundle failed its integrity check. | |
422 | Validation Error | HTTPValidationError |
GET /api/documents/{document_id}/evidence/editions
List Evidence Editions
Every published edition of this envelope's evidence bundle.
Publication is write-once per edition and additive across them, so a recipient can be holding edition 1 while edition 2 is current. Before this existed, editions() could enumerate the rows internally but nothing in the product could reach an earlier one — a superseded edition was, in practice, unobtainable the moment it was superseded. Read-only: it publishes nothing and appends nothing.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | EvidenceEditionsOut |
404 | Unknown envelope for this tenant. | |
422 | Validation Error | HTTPValidationError |
GET /api/documents/{document_id}/evidence/editions/{edition}
Download Evidence Edition
One historical edition, byte for byte, verified against its own SHA-256.
Deliberately NOT a publish path: asking for edition 1 must never mint edition 3. It serves what was published or 404s, which is what makes the word "immutable" mean something a caller can rely on.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
edition | path | integer | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | SHA-256-verified bytes of ONE immutable edition, exactly as published. | string (binary) |
404 | Unknown envelope for this tenant, or no such edition. | |
409 | The stored bytes failed their integrity check. | |
422 | Validation Error | HTTPValidationError |
POST /api/documents/{document_id}/fields
Create Field
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Request body required
application/json — FieldIn
Responses
| Status | Meaning | Body |
|---|---|---|
201 | Successful Response | FieldOut |
422 | Validation Error | HTTPValidationError |
PATCH /api/documents/{document_id}/fields/{field_id}
Edit Field
Move/resize/retype/reassign a field on a draft (the visual composer's edit surface).
Sync on purpose — it waits on the document row lock (see create_signer).
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
field_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Request body required
application/json — FieldPatch
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | FieldOut |
422 | Validation Error | HTTPValidationError |
DELETE /api/documents/{document_id}/fields/{field_id}
Delete Field
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
field_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
204 | Successful Response | |
422 | Validation Error | HTTPValidationError |
GET /api/documents/{document_id}/original
Download Original Pdf
The original as uploaded — the composer renders placement on it.
No audit event: the sender previewing their own working copy is not part of the signing ceremony. DOWNLOADED is reserved for the signed evidence.
Sync on purpose (see send_for_signing): storage.get is a blocking read of a PDF up to the upload cap, and template-created drafts hit this route every time the composer opens — it must run in the threadpool, not stall the event loop.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Exact original bytes after SHA-256 verification under the organization-scoped document lock; private/no-store and nosniff. | string (binary) |
409 | Original storage or its recorded SHA-256 is missing, or the stored bytes fail integrity verification; private/no-store and nosniff. | |
422 | Validation Error | HTTPValidationError |
POST /api/documents/{document_id}/remind
Remind Signers
Queue and attempt a reminder for everyone whose turn it currently is.
Minting is how re-sending works (only the token's hash is stored), so each reminder supersedes the link already in the recipient's inbox. Link rotation, frozen provider requests and audit truth commit atomically; the response reports provider acceptance/pending/manual-review truth, never inbox delivery. Declared sync on purpose — the bounded outbox drain can call the provider.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | ReminderOut |
409 | The document has no currently eligible recipients to remind. | |
503 | Fresh links and durable reminder rows committed, but provider processing could not acquire its bounded lifecycle fence. | |
422 | Validation Error | HTTPValidationError |
POST /api/documents/{document_id}/save-as-template
Save As Template
Snapshot this DRAFT's PDF + recipients + fields into a reusable template.
The snapshot copies the blob — templates outlive their source envelope, so they must not share its storage (erasing the envelope destroys its files). Drafts only: once sent, an envelope is (becoming) evidence, and its content must not be copyable out from under the erasure rules. The service holds the org + document row locks, so a concurrent erasure serialises with the copy instead of racing it. Sync on purpose — file copy work belongs in the threadpool, not on the event loop (see send_for_signing).
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Request body required
application/json — SaveAsTemplateIn
Responses
| Status | Meaning | Body |
|---|---|---|
201 | Successful Response | TemplateOut |
404 | Tenant-scoped source document not found. | |
409 | The source is not a live draft or its original PDF is unavailable. | |
500 | Template copy failed and storage cleanup remains pending. | |
507 | The organization storage allowance or volume capacity is exhausted. | |
422 | Validation Error | HTTPValidationError |
POST /api/documents/{document_id}/send
Send For Signing
Finalize a draft and queue durable invitations for the first signer tier.
Sending is the metered action: it consumes one envelope from the org's plan allowance (402 when spent). Consumption, token hashes, frozen provider requests and SENT audit events commit together. Provider acceptance is a separate recoverable phase, so a pending or ambiguous email does not roll the envelope back or refund its allowance.
Declared sync on purpose: this handler blocks (email, and optionally an Anthropic call for the signer summary), so FastAPI must dispatch it to a threadpool. As an async def it ran on the event loop, where one slow AI call stalled every other request in the process — including a signer submitting a signature. Do not make this async without moving the blocking work off-loop.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | DocumentOut |
400 | Draft composition is incomplete or invalid. | |
402 | The subscription is lapsed, its allowance is exhausted, or authoritative trial reconciliation is required. | |
404 | The organization or tenant-scoped document does not exist. | |
422 | Validation Error | HTTPValidationError |
POST /api/documents/{document_id}/signers
Create Signer
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Request body required
application/json — SignerIn
Responses
| Status | Meaning | Body |
|---|---|---|
201 | Successful Response | SignerOut |
422 | Validation Error | HTTPValidationError |
POST /api/documents/{document_id}/signers/me
Create Signer Me
Add the signed-in dashboard user as an ordinary signing recipient ("Add me").
Sync on purpose — it waits on the document row lock (see create_signer).
Session-only and body-less on purpose: the identity is derived server-side from the ACCEPTED session's user row, never from anything the client sends — a stale /me cache or a cross-tab sign-in can't put the wrong person on the envelope, and an API key (whose bearer is a program, not the person at the keyboard) is refused by the dependency with a 401. This creates a normal recipient and nothing more: they still get the signing email, magic link and one-time code like everyone else — never a shortcut past the OTP ceremony.
Routing lands in the NEXT signing group after the current maximum (an empty draft starts at 0): the sender reviewing their own pack signs after the recipients they are adding it for, and never silently jumps the queue.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
201 | Successful Response | SignerOut |
422 | Validation Error | HTTPValidationError |
DELETE /api/documents/{document_id}/signers/{signer_id}
Delete Signer
Remove a recipient from a draft, along with any fields assigned to them.
Sync on purpose — it waits on the document row lock (see create_signer).
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
signer_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
204 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/documents/{document_id}/void
Void Document
Cancel an in-flight envelope. Terminal and irreversible, like decline.
Every outstanding signing link stops working the moment this commits; every recipient already contacted (including someone who signed earlier in a partially signed ceremony) gets a durable cancellation notice. Draft envelopes can't be voided (nothing was ever sent — delete them instead), and settled ones (completed, declined, expired) are history, not cancellable. Repeating a successful void drains any still-undelivered durable notices without adding another audit event. Sync on purpose — it may run a bounded post-commit outbox delivery phase.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
document_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Request body
application/json — VoidIn or null
Responses
| Status | Meaning | Body |
|---|---|---|
200 | The envelope is voided. Repeated calls are idempotent and only process due durable cancellation notices. | VoidOut |
409 | The envelope is not voidable in its current state. | |
404 | The tenant-scoped envelope is absent, or the void committed and a concurrent erasure removed it before response assembly; the latter returns structured void_committed truth. | |
503 | Lifecycle coordination is busy, or the void committed and only durable cancellation-notification processing remains pending; retry safely to drain due work. | |
422 | Validation Error | HTTPValidationError |
Templates
GET /api/templates
List Templates
Org-scoped template list, newest first (the 'New from template' picker).
Sync on purpose — the Session is synchronous, so the pool checkout and query block the calling thread; that wait belongs in the threadpool, not on the event loop (see documents.list_documents).
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | array of TemplateOut |
422 | Validation Error | HTTPValidationError |
DELETE /api/templates/{template_id}
Delete Template
Delete a template — row and PDF blob.
Same credential rule as delete_document: sessions always may, API keys only with the can_delete scope — a leaked everyday integration key must not be able to strip the org's templates. (Templates are working material, not evidence, so nothing is retained.)
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
template_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Template row deletion committed. If verified post-commit blob removal fails or raises, cleanup_pending=true and a recovery warning are returned. | |
403 | An API key without delete scope cannot delete templates. | |
404 | Tenant-scoped template not found. | |
422 | Validation Error | HTTPValidationError |
POST /api/templates/{template_id}/use
Use Template Route
Create a fresh DRAFT envelope from a template. Never auto-sends — the caller reviews (and can adjust) in the composer before sending. Sync on purpose: it blocks on file copy work and must run in FastAPI's threadpool, not on the event loop (see send_for_signing).
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
template_id | path | string | required | |
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Request body required
application/json — UseTemplateIn
Responses
| Status | Meaning | Body |
|---|---|---|
201 | Successful Response | DocumentOut |
400 | Recipient input is invalid. | |
404 | Tenant-scoped template not found. | |
409 | The template PDF/data is unavailable or the organization is closed. | |
500 | Draft copy failed and storage cleanup remains pending. | |
507 | The organization storage allowance or volume capacity is exhausted. | |
422 | Validation Error | HTTPValidationError |
Signing
GET /api/sign/receipt/{token}/certificate
Receipt Certificate
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
token | path | string | required | |
X-Receipt-Session | header | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | SHA-256-verified completion certificate for this signer. | string (binary) |
404 | Unknown, revoked or erased receipt credential. | |
401 | Explicit receipt-session confirmation is required. | |
409 | Completed evidence missing or integrity check failed. | |
422 | Validation Error | HTTPValidationError |
GET /api/sign/receipt/{token}/evidence
Receipt Evidence Bundle
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
token | path | string | required | |
edition | query | integer or null | A specific immutable edition. Omit for the current one — which is what every existing caller does, and its behaviour is unchanged. | |
X-Receipt-Session | header | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | SHA-256-verified evidence bundle for this signer: a PDF carrying the canonical evidence manifest as an embedded evidence.json attachment. | string (binary) |
404 | Unknown, revoked or erased receipt credential. | |
401 | Explicit receipt-session confirmation is required. | |
409 | The completion checkpoint is still outstanding, or the stored bundle failed its integrity check. | |
422 | Validation Error | HTTPValidationError |
GET /api/sign/receipt/{token}/evidence/editions
Receipt Evidence Editions
Which editions this signer's own envelope has published.
Behind the same explicit artifact: "evidence" confirmation session as the download, and scoped to the one document the receipt resolves to. Read-only: it publishes nothing and appends no audit event, because listing what exists is not a download.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
token | path | string | required | |
X-Receipt-Session | header | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | EvidenceEditionsOut |
404 | Unknown, revoked or erased receipt credential. | |
401 | Explicit receipt-session confirmation is required. | |
422 | Validation Error | HTTPValidationError |
POST /api/sign/receipt/{token}/session
Create Receipt Session
Exchange an explicit browser gesture for a short-lived header token.
Completion mail links only to the inert frontend receipt page. Automated GET/HEAD prefetch therefore cannot reach this POST, cannot obtain evidence, and cannot append a signer DOWNLOADED event.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
token | path | string | required |
Request body required
application/json — ReceiptSessionIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | ReceiptSessionOut |
404 | Unknown, revoked or erased receipt credential. | |
422 | Validation Error | HTTPValidationError |
GET /api/sign/receipt/{token}/signed
Receipt Signed Pdf
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
token | path | string | required | |
X-Receipt-Session | header | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | SHA-256-verified completed signed PDF for this completion signer; its recorded seal state is the one measured on that file (see the completion payload's digital_seal). | string (binary) |
404 | Unknown, revoked or erased receipt credential. | |
401 | Explicit receipt-session confirmation is required. | |
409 | Completed evidence missing or integrity check failed. | |
422 | Validation Error | HTTPValidationError |
POST /api/sign/session/decline
Session Decline
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
X-Signing-Session | header | string or null |
Request body required
application/json — DeclineIn
Responses
| Status | Meaning | Body |
|---|---|---|
204 | Successful Response | |
401 | The signing session is missing, expired or revoked. | |
409 | The envelope reached a state that can no longer be declined. | |
503 | Lifecycle-email delivery coordination is busy; no decline transition was committed and the request can be retried. | |
422 | Validation Error | HTTPValidationError |
GET /api/sign/session/document
Session Document
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
X-Signing-Session | header | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | SignDocumentView |
422 | Validation Error | HTTPValidationError |
GET /api/sign/session/pdf
Session Pdf
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
X-Signing-Session | header | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Exact original bytes after SHA-256 verification under the ceremony locks; private/no-store and nosniff. | string (binary) |
401 | The signing session is missing, expired or revoked. | |
409 | Original storage or its recorded SHA-256 is missing, or the stored bytes fail integrity verification; private/no-store and nosniff. | |
422 | Validation Error | HTTPValidationError |
POST /api/sign/session/sign
Session Sign
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
X-Signing-Session | header | string or null |
Request body required
application/json — SignIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | SignResultOut |
400 | Required field values or signing consent are invalid. | |
401 | The signing session is missing, expired or revoked. | |
409 | The document or audit/original integrity state prevents signing. | |
500 | A completion-artifact write failed and storage cleanup remains pending; the signature transition was not committed. | |
503 | Lifecycle-email delivery coordination is busy; no signature transition was committed and the request can be retried. | |
507 | Completion artifacts could not be stored; the signature transition was rolled back and can be retried after capacity is restored. | |
422 | Validation Error | HTTPValidationError |
GET /api/sign/{token}
View Link
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
token | path | string | required |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | SignerViewOut |
422 | Validation Error | HTTPValidationError |
POST /api/sign/{token}/decline
Decline
A magic link alone is not authority to void the legal ceremony.
Kept as an explicit compatibility refusal rather than silently removing the route: old clients receive actionable, non-terminal truth. The dependency still prevents token enumeration, but neither the reason nor the envelope is mutated until OTP verification created a signing session.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
token | path | string | required |
Request body required
application/json — DeclineIn
Responses
| Status | Meaning | Body |
|---|---|---|
403 | A magic link is not sufficient identity proof for a terminal decline; verify by OTP and use the signing-session route. | |
422 | Validation Error | HTTPValidationError |
POST /api/sign/{token}/otp
Send Otp
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
token | path | string | required |
Responses
| Status | Meaning | Body |
|---|---|---|
204 | Successful Response | |
202 | The OTP request and candidate hash are committed; provider acceptance remains pending or ambiguous. | |
404 | The signing ceremony or presented link is no longer live. | |
429 | OTP request budget exhausted or rate limited. | |
502 | The provider authoritatively rejected the staged OTP; any previous valid challenge remains usable. | |
422 | Validation Error | HTTPValidationError |
POST /api/sign/{token}/verify-otp
Verify Otp
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
token | path | string | required |
Request body required
application/json — OtpVerifyIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | SessionOut |
422 | Validation Error | HTTPValidationError |
Billing
POST /api/billing/checkout
Start Checkout
Start a Stripe Checkout for the caller's own organisation.
This is the path a returning customer takes instead of a public payment link. The public links provision *new* workspaces and cannot safely attach to an existing one (the checkout email is unverified), so an existing account that goes through one ends up paid-but-unattached. Here the workspace is known before Stripe is ever called.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Request body required
application/json — StartCheckoutIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/billing/portal
Billing Portal
Open a Stripe Customer Portal session for the caller's org (upgrade/cancel).
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/billing/webhook
Stripe Webhook
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
Stripe-Signature | header | string |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
Account
GET /api/account
Get Account
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
X-API-Key | header | string or null | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
GET /api/account/activity
List Org Activity
Append-only member and API-key activity for the caller's org.
An activity log, NOT evidence: it is not hash-chained and makes no tamper-evidence claim. It is deliberately a separate table from the document audit trail — widening audit_events to host org events would have changed hash_payload() and invalidated every existing document chain, anchor and certificate.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
PATCH /api/account/ai
Set Ai Enabled
Turn the optional AI features on or off for the caller's organisation.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Request body required
application/json — AiSettingIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
GET /api/account/api-keys
List Api Keys
The org's API keys. Never the secrets — prefixes are display only.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/account/api-keys
Create Api Key
Mint an API key for the org. The plaintext is returned once and never again.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Request body required
application/json — CreateKeyIn
Responses
| Status | Meaning | Body |
|---|---|---|
201 | Successful Response | |
422 | Validation Error | HTTPValidationError |
DELETE /api/account/api-keys/{key_id}
Revoke Api Key
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
key_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/account/close
Close Account
Close the account and begin its destructive live-data purge in this request.
The DPA (§9.2) promises purge within 30 days of closure; doing it inside the closure itself satisfies that with nothing left to schedule or forget. Every envelope is erased under the standard criteria (signed ones leave their tamper-evident audit residue — see DELETE /api/documents/{id} for exactly what that retains), users and API keys are deleted, the organization row is redacted, and any Stripe subscription is cancelled best-effort. Filesystem deletion is verified; a storage or Stripe failure is returned explicitly and logged for recovery rather than silently described as complete. Irreversible.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Request body required
application/json — CloseAccountIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
409 | Closure is blocked by provider-crossed lifecycle email or AI submission uncertainty that requires controlled support reconciliation. | |
503 | Organization lifecycle delivery coordination is busy; no closure is reported, so retry safely. | |
422 | Validation Error | HTTPValidationError |
GET /api/account/invites
List Invites
Pending invitations for the caller's org. Never the tokens.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/account/invites
Create Invite
Invite an address into the caller's workspace.
202, and the email is dispatched after the response, copying the shape of request-link so timing carries nothing. "Already a member here" and "already invited here" ARE distinguished — that is data the caller already owns. "This address belongs to another DrakonSign workspace" is NOT, and is never disclosed before the invitee accepts, or a cheap subscription becomes an enumeration oracle over the whole customer base.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Request body required
application/json — InviteIn
Responses
| Status | Meaning | Body |
|---|---|---|
202 | Successful Response | |
422 | Validation Error | HTTPValidationError |
DELETE /api/account/invites/{invite_id}
Revoke Invite
Withdraw a pending invitation. The emailed link stops working at once.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
invite_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/account/invites/{invite_id}/resend
Resend Invite
Re-issue an invitation. This ROTATES the token — the old link dies.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
invite_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
202 | Successful Response | |
422 | Validation Error | HTTPValidationError |
GET /api/account/members
List Members
The workspace's members. Admin+, session-only.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
PATCH /api/account/members/{user_id}
Change Member Role
Change a member's role. Never your own; never above your own rank.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
user_id | path | string | required | |
ds_session | cookie | string or null |
Request body required
application/json — RoleChangeIn
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
DELETE /api/account/members/{user_id}
Remove Member
Remove a member. Their sessions, magic link and API keys all stop working.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
user_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
GET /api/account/members/{user_id}/api-keys
Member Api Keys
The live keys a member minted — what removing them would stop working.
Surfaced so the removal warning states facts. If the departing admin minted the key an integration runs on, that integration stops the instant they are removed, and the person clicking Remove should see which.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
user_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
Webhooks
GET /api/account/webhooks
List Webhooks
The workspace's destinations, plus what this deployment can do.
configured is what the dashboard branches on. A deployment with no WEBHOOK_ENCRYPTION_KEY reports false and the UI explains that instead of offering a Create button that would 503 — the same shape as ai_configured.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/account/webhooks
Create Webhook
Register a destination. The signing secret is returned ONCE, here.
A rejected destination (not HTTPS, an IP literal, a private DNS answer, a port that is not allowlisted) is a 400 carrying the specific reason, so the customer can fix it now rather than discovering it when their first envelope completes and nothing arrives.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
ds_session | cookie | string or null |
Request body required
application/json — EndpointIn
Responses
| Status | Meaning | Body |
|---|---|---|
201 | Successful Response | |
422 | Validation Error | HTTPValidationError |
GET /api/account/webhooks/deliveries
List Deliveries
Newest-first delivery log for the caller's own org.
endpoint_id filters; it is NOT a tenancy input. The org filter is applied first and always, from the session context, so a foreign endpoint id narrows the caller's own rows to none rather than widening them to somebody else's.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
endpoint_id | query | string or null | ||
limit | query | integer | ||
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/account/webhooks/deliveries/{delivery_id}/replay
Replay Delivery
Queue the same event again, as a NEW delivery linked to its source.
202, not 200: the row is durable when this returns and the request goes out afterwards. The new delivery carries the same event_id (so the receiver's deduplication still recognises it) and frozen business-event data; only delivery_id changes, in both body and header, so the replay describes the envelope as it was without naming the wrong attempt.
The original row is never mutated. A failed delivery is a fact about what happened; rewriting it to pending would erase the record that we tried, exhausted the policy and gave up.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
delivery_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
202 | Successful Response | |
422 | Validation Error | HTTPValidationError |
DELETE /api/account/webhooks/{endpoint_id}
Delete Webhook
Remove a destination. The delivery history survives; the secret does not.
Soft delete: the endpoint row stays (deliveries hold a non-nullable FK onto it, and "we sent you the completion at 14:02 and you answered 200" is the record a support argument turns on) but it disappears from every customer-facing query and its sealed secret is destroyed in the same statement. What is retained is a record, not a usable credential to a destination the customer has told us to forget.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
endpoint_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/account/webhooks/{endpoint_id}/disable
Disable Webhook
Stop this destination: no new events, and the pending queue is cancelled.
Cancelling the backlog rather than holding it is deliberate — a customer who switches a misbehaving destination off does not want a stampede of stale events the moment they switch it back on. Anything they still want is an explicit replay from the delivery log.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
endpoint_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/account/webhooks/{endpoint_id}/enable
Enable Webhook
Re-enable a destination. FUTURE events only.
The destination is re-validated here, not trusted from the row: it may have been re-pointed at an internal address while it sat disabled, and enabling is exactly the moment to find that out.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
endpoint_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
POST /api/account/webhooks/{endpoint_id}/rotate-secret
Rotate Webhook Secret
Mint a new signing secret. **The old one stops verifying immediately.**
There is no overlap window, deliberately — see webhooks.rotate_secret for why a grace period is the wrong answer to "I think my secret leaked". The safe procedure is the customer's to run: accept both values in your receiver, rotate here, then drop the old one.
Parameters
| Name | In | Type | Notes | |
|---|---|---|---|---|
endpoint_id | path | string | required | |
ds_session | cookie | string or null |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Successful Response | |
422 | Validation Error | HTTPValidationError |
Schemas
AcceptInviteIn
| Field | Type | Notes | |
|---|---|---|---|
token | string | required | |
name | string or null |
AiSettingIn
| Field | Type | Notes | |
|---|---|---|---|
ai_enabled | boolean | required |
AuditEventOut
| Field | Type | Notes | |
|---|---|---|---|
sequence | integer | required | |
event_type | string | required | |
actor | string | required | |
signer_id | string or null | required | |
ip | string or null | required | |
user_agent | string or null | required | |
timestamp_utc | string | required | |
payload_json | string or null | required | |
prev_hash | string | required | |
hash | string | required |
AuditVerifyOut
| Field | Type | Notes | |
|---|---|---|---|
intact | boolean | required | |
first_broken_sequence | integer or null | required | |
event_count | integer | required | |
anchored | boolean | default False | |
anchor_expected | boolean | True when the retained audit history contains a terminal event (completed, declined, voided or expired), including erased residues. default False | |
anchor_missing_recorded | boolean | True when the chain immutably records a fail-open checkpoint failure FOR THE PRIMARY TERMINAL KIND. A degradation recorded for another kind (for example `signatures`) sets `any_checkpoint_missing_recorded` instead — presenting it here would describe one checkpoint's failure as the terminal one's. default False | |
any_checkpoint_missing_recorded | boolean | True when the chain records a fail-open checkpoint failure for ANY kind. Read `checkpoints[]` to see which. default False | |
state_consistent | boolean | False when the retained evidence contradicts itself — more than one terminal transition, a document status that disagrees with the audit history, or a stored checkpoint of a kind this release cannot explain. default True | |
anomalies | array of string | Plain statements of every inconsistency found. Empty when consistent. | |
anchor_event_count | integer or null | Number of audit events in the RFC 3161-timestamped terminal prefix. | |
unanchored_suffix_count | integer | Later events that recompute internally but are outside the largest VALIDATED timestamped prefix across all checkpoints. When no checkpoint validates — none stored, or every stored token refused — that prefix is empty and this equals event_count: nothing is covered. default 0 | |
anchored_at | string or null | ||
anchor_tsa | string or null | ||
anchor_valid | boolean or null | ||
anchor_validation_status | not_present | valid | invalid | unverifiable_configuration | default not_present | |
terminal_checkpoint_kind | completion | declined | voided | expired or null | Which terminal checkpoint this envelope's outcome calls for, if any. | |
checkpoints | array of CheckpointOut | Every checkpoint kind relevant to this envelope, present or not. |
Body_upload_document_api_documents_post
| Field | Type | Notes | |
|---|---|---|---|
title | string | required | |
file | string | required |
CancellationManualReviewOut
Sanitized org-scoped delivery view; never includes frozen email HTML.
| Field | Type | Notes | |
|---|---|---|---|
id | string | required | |
document_id | string | required | |
notice_kind | string | required | |
status | string | required | |
created_at | string (date-time) | required | |
manual_review_at | string (date-time) or null | ||
attempt_count | integer | required | |
provider_boundary_crossed | boolean | required | |
last_attempt_at | string (date-time) or null | ||
idempotency_expires_at | string (date-time) or null | ||
payload_sha256 | string | required | |
provider_message_id | string or null | ||
reason_code | string | required | |
pii_redacted | boolean | required | |
support_required | boolean | default True |
ChainAnchorListOut
| Field | Type | Notes | |
|---|---|---|---|
document_id | string | required | |
anchors | array of ChainAnchorOut | default [] |
ChainAnchorOut
| Field | Type | Notes | |
|---|---|---|---|
document_id | string | required | |
head_hash | string | required | |
event_count | integer | required | |
tsa_url | string | required | |
created_at | string (date-time) | required | |
token_b64 | string | required | |
anchor_kind | string | default completion | |
target_event_type | string | default completed | |
target_event_sequence | integer | default 0 |
CheckpointOut
One typed RFC 3161 checkpoint over the audit trail, described precisely.
| Field | Type | Notes | |
|---|---|---|---|
kind | string | required | |
kind_known | boolean | False when the stored checkpoint kind is not one this release defines. Such a row is reported as an anomaly and never as a valid checkpoint. default True | |
target_event_type | string | required | The audit event type this checkpoint's token was taken over. |
present | boolean | required | True when a stored token exists for this kind. |
expected | boolean | required | True when the retained audit history contains the target event, so a checkpoint of this kind should exist. |
missing_recorded | boolean | True when the chain immutably records that this checkpoint's fail-open attempt failed. default False | |
obligation_pending | boolean | True when a durable obligation for this checkpoint is still outstanding, so it is expected to arrive. The obligation is written in the same transaction as the outcome that owes it, so it survives any later failure. default False | |
validation_status | not_present | valid | invalid | unverifiable_configuration | default not_present | |
valid | boolean or null | ||
reason | string or null | ||
target_event_sequence | integer or null | ||
covered_event_count | integer or null | Number of audit events inside this checkpoint's timestamped prefix. | |
suffix_event_count | integer or null | Events after this checkpoint's prefix — internally chained, outside its timestamp. | |
head_hash | string or null | ||
tsa_url | string or null | ||
attested_at | string or null | TSA-attested generation time. Present only for a token that validated. |
ClauseAnalysisOut
Pre-send reading aid for the sender (M10), never stored on the envelope.
available is False whenever the org has not opted into AI, no key is configured, the PDF has no extractable text, or the model returned nothing usable. Callers render the panel only when it is True — AI is never allowed to turn a working page into an error.
| Field | Type | Notes | |
|---|---|---|---|
available | boolean | required | |
clauses | array of ClauseOut | default [] | |
risks | array of RiskOut | default [] |
ClauseOut
| Field | Type | Notes | |
|---|---|---|---|
title | string | required | |
detail | string | required |
CloseAccountIn
| Field | Type | Notes | |
|---|---|---|---|
confirm | boolean | default False |
ConfirmMfaIn
| Field | Type | Notes | |
|---|---|---|---|
code | string | required |
CorrectIn
| Field | Type | Notes | |
|---|---|---|---|
reason | string | required |
CorrectionOut
The result of a void-and-copy correction.
Reports what actually happened rather than assuming the happy path: created is False when an earlier request already produced this draft (the idempotent replay), voided_now is False when the source was already settled and had nothing left to cancel. Notification counters report only provider acceptance, pending work and terminal/manual-review states; they never imply that a recipient received or opened an email.
| Field | Type | Notes | |
|---|---|---|---|
created | boolean | required | |
source_id | string | required | |
source_status | string | required | |
voided_now | boolean | required | |
reason | string or null | ||
provider_accepted | integer | default 0 | |
notify_permanent_failed | integer | default 0 | |
notify_pending | integer | default 0 | |
notify_manual_review | integer | default 0 | |
notify_outcome_unknown | integer | default 0 | |
cleanup_pending | boolean | default False | |
warning | string or null | ||
draft | DocumentOut | required |
CreateKeyIn
| Field | Type | Notes | |
|---|---|---|---|
name | string | required | |
can_delete | boolean | default False |
DeclineIn
| Field | Type | Notes | |
|---|---|---|---|
reason | string or null |
DisableMfaIn
| Field | Type | Notes | |
|---|---|---|---|
code | string | required |
DocumentListOut
Summary row for the dashboard list — field rectangles stay on the detail endpoint.
| Field | Type | Notes | |
|---|---|---|---|
id | string | required | |
title | string | required | |
status | string | required | |
page_count | integer or null | required | |
created_at | string (date-time) | required | |
completed_at | string (date-time) or null | ||
signers | array of SignerOut | default [] |
DocumentOut
| Field | Type | Notes | |
|---|---|---|---|
id | string | required | |
title | string | required | |
status | string | required | |
original_sha256 | string or null | required | |
signed_sha256 | string or null | ||
page_count | integer or null | required | |
created_at | string (date-time) | required | |
completed_at | string (date-time) or null | ||
original_storage_recorded | boolean | default False | |
corrected_from_document_id | string or null | ||
corrected_by_document_id | string or null | ||
has_correction_lineage | boolean | default False | |
correction_consumed | boolean | default False | |
signers | array of SignerOut | default [] | |
fields | array of FieldOut | default [] |
DocumentStatus
No declared fields.
EndpointIn
A destination request.
There is deliberately no org_id and no secret: the tenant comes from the session context and the secret is minted server-side. A caller supplying its own secret would be choosing the strength of the credential that authenticates us to them, which is not theirs to weaken.
| Field | Type | Notes | |
|---|---|---|---|
url | string | required | |
description | string | default | |
event_types | array of string |
EvidenceEditionOut
One immutable, published edition of the detached evidence bundle.
Editions are additive: a bundle published while a checkpoint was still owed is never rewritten when that checkpoint lands, because a recipient may already hold its bytes. A recipient must therefore be able to obtain the edition they were given, not merely the newest one, which is why this list and the per-edition download exist at all.
| Field | Type | Notes | |
|---|---|---|---|
edition | integer | required | 1-based; the highest edition is the current one. |
sha256 | string | required | SHA-256 of the exact published bytes. |
manifest_schema | string | required | |
supersedes_sha256 | string or null | SHA-256 of the edition this one replaces. ISSUER-AUTHENTICATED: the bundle's publication signature covers it, and no timestamp authority attests it. | |
published_at | string (date-time) or null | ||
current | boolean | required | True for the edition GET /{id}/evidence serves right now. |
EvidenceEditionsOut
| Field | Type | Notes | |
|---|---|---|---|
editions | array of EvidenceEditionOut | default [] | |
lineage_assurance | string | What binds one edition to its predecessor. `issuer-authenticated` — the publication signature over the manifest, which detects alteration by anyone without Drakon's sealing key and is not evidence against Drakon. The succession is NOT independently timestamped: an edition cannot contain the record of its own publication. default issuer-authenticated |
FieldIn
| Field | Type | Notes | |
|---|---|---|---|
page | integer | required | |
x | number | required | |
y | number | required | |
width | number | required | |
height | number | required | |
field_type | FieldType | default signature | |
required | boolean | default True | |
signer_id | string or null |
FieldOut
| Field | Type | Notes | |
|---|---|---|---|
id | string | required | |
page | integer | required | |
x | number | required | |
y | number | required | |
width | number | required | |
height | number | required | |
field_type | string | required | |
required | boolean | required | |
signer_id | string or null | required | |
placement_source | string | required |
FieldPatch
Partial edit of a draft field. Sending signer_id: null unassigns it; combined-rect and page-range validation happens in the service, where the merged result is known.
| Field | Type | Notes | |
|---|---|---|---|
page | integer or null | ||
x | number or null | ||
y | number or null | ||
width | number or null | ||
height | number or null | ||
field_type | FieldType or null | ||
required | boolean or null | ||
signer_id | string or null |
FieldType
No declared fields.
FromCheckoutIn
| Field | Type | Notes | |
|---|---|---|---|
session_id | string | required |
HTTPValidationError
| Field | Type | Notes | |
|---|---|---|---|
detail | array of ValidationError |
InviteIn
| Field | Type | Notes | |
|---|---|---|---|
email | string (email) | required | |
role | string | default member |
MfaVerifyIn
| Field | Type | Notes | |
|---|---|---|---|
code | string | required |
OtpVerifyIn
| Field | Type | Notes | |
|---|---|---|---|
code | string | required |
ReceiptSessionIn
Explicit user gesture required before completed evidence can be fetched.
| Field | Type | Notes | |
|---|---|---|---|
confirm_download | boolean | required | |
artifact | signed | certificate | evidence | required |
ReceiptSessionOut
| Field | Type | Notes | |
|---|---|---|---|
session_token | string | required | |
expires_in_seconds | integer | required |
RegenerateCodesIn
| Field | Type | Notes | |
|---|---|---|---|
code | string | required |
ReminderOut
Durable reminder-queue and email-provider truth for one request.
| Field | Type | Notes | |
|---|---|---|---|
reminded | integer | required | Compatibility alias for provider_accepted; never a delivery or read receipt. |
failed | integer | required | Authoritative permanent payload/content rejections. |
queued | integer | required | Durable reminder rows committed with freshly rotated links. |
provider_accepted | integer | required | Requests accepted by the email provider, not proven delivered. |
pending | integer | required | Queued, leased, submitting or ambiguous provider outcomes. |
manual_review | integer | required | Requests requiring controlled support reconciliation. |
skipped | integer | required | Rows cancelled before provider submission because the link became stale. |
request_id | string or null |
RequestLinkIn
| Field | Type | Notes | |
|---|---|---|---|
email | string (email) | required |
RiskOut
| Field | Type | Notes | |
|---|---|---|---|
severity | string | required | |
title | string | required | |
detail | string | required |
RoleChangeIn
| Field | Type | Notes | |
|---|---|---|---|
role | string | required |
SaveAsTemplateIn
| Field | Type | Notes | |
|---|---|---|---|
name | string | required |
SessionOut
| Field | Type | Notes | |
|---|---|---|---|
session_token | string | required |
SignDocumentView
Post-OTP view: the document metadata + this signer's own fields.
| Field | Type | Notes | |
|---|---|---|---|
document_title | string | required | |
page_count | integer or null | required | |
fields | array of FieldOut | required | |
ai_summary | string or null | ||
consent_text_version | string | required | |
consent_text | string | required |
SignIn
| Field | Type | Notes | |
|---|---|---|---|
field_values | object of string | default {} | |
consent | boolean | default False | |
consent_text_version | string | required |
SignResultOut
Committed ceremony state after a successful signing submission.
| Field | Type | Notes | |
|---|---|---|---|
status | DocumentStatus | required |
SignerIn
| Field | Type | Notes | |
|---|---|---|---|
name | string | required | |
email | string (email) | required | |
role | SignerRole | default signer | |
routing_index | integer | default 0 |
SignerOut
| Field | Type | Notes | |
|---|---|---|---|
id | string | required | |
name | string | required | |
email | string | required | |
role | string | required | |
routing_index | integer | required | |
status | string | required |
SignerRole
No declared fields.
SignerViewOut
Pre-OTP metadata shown when a signer opens their link (no PDF, no fields yet).
| Field | Type | Notes | |
|---|---|---|---|
document_title | string | required | |
signer_name | string | required | |
organization | string | required | |
page_count | integer or null | required | |
status | string | required |
StartCheckoutIn
What the browser is allowed to choose: a tier and a billing interval.
Deliberately nothing else. The organisation the checkout is for comes from the session cookie and is stamped on the Stripe session server-side, so no request body can point a purchase at a workspace the caller is not in.
| Field | Type | Notes | |
|---|---|---|---|
tier | string | required | |
interval | string | default monthly |
TemplateOut
| Field | Type | Notes | |
|---|---|---|---|
id | string | required | |
name | string | required | |
page_count | integer or null | required | |
created_at | string (date-time) | required | |
signers | array of TemplatePlaceholderOut | default [] | |
field_count | integer | default 0 | |
damaged | boolean | default False |
TemplatePlaceholderOut
| Field | Type | Notes | |
|---|---|---|---|
name | string | required | |
email | string | required | |
role | string | required | |
routing_index | integer | required |
UseTemplateIn
| Field | Type | Notes | |
|---|---|---|---|
title | string | required | |
signers | array of UseTemplateSignerIn | default [] |
UseTemplateSignerIn
| Field | Type | Notes | |
|---|---|---|---|
name | string | required | |
email | string (email) | required |
ValidationError
| Field | Type | Notes | |
|---|---|---|---|
loc | array of string | integer | required | |
msg | string | required | |
type | string | required | |
input | any | ||
ctx | object |
VerifyIn
| Field | Type | Notes | |
|---|---|---|---|
token | string | required |
VoidIn
| Field | Type | Notes | |
|---|---|---|---|
reason | string or null |
VoidOut
A void plus durable cancellation-notice delivery truth.
| Field | Type | Notes | |
|---|---|---|---|
id | string | required | |
title | string | required | |
status | string | required | |
original_sha256 | string or null | required | |
signed_sha256 | string or null | ||
page_count | integer or null | required | |
created_at | string (date-time) | required | |
completed_at | string (date-time) or null | ||
original_storage_recorded | boolean | default False | |
corrected_from_document_id | string or null | ||
corrected_by_document_id | string or null | ||
has_correction_lineage | boolean | default False | |
correction_consumed | boolean | default False | |
signers | array of SignerOut | default [] | |
fields | array of FieldOut | default [] | |
voided_now | boolean | default True | |
provider_accepted | integer | default 0 | |
notify_permanent_failed | integer | default 0 | |
notify_pending | integer | default 0 | |
notify_manual_review | integer | default 0 | |
notify_outcome_unknown | integer | default 0 | |
warning | string or null |