{"openapi":"3.1.0","info":{"title":"DrakonSign API","description":"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.\n\nDrakonSign 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.","version":"0.18.0"},"paths":{"/api/health":{"get":{"tags":["Health"],"summary":"Health Check","operationId":"health_check_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/health/ready":{"get":{"tags":["Health"],"summary":"Readiness","operationId":"readiness_api_health_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/ping":{"get":{"tags":["Health"],"summary":"Ping","operationId":"ping_api_ping_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/auth/request-link":{"post":{"tags":["Auth"],"summary":"Request Link","description":"Email a sign-in link if the address maps to an account. Always 200.\n\nOnly the same indexed lookup runs before the response for either branch.\nFor a candidate account, fenced minting, the atomic JTI/frozen-request\ncommit and delivery all begin after the body is flushed. Within that\nbackground phase the provider still cannot run before the commit, and\naccount closure is rechecked under the shared organization fence.","operationId":"request_link_api_auth_request_link_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestLinkIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/verify":{"post":{"tags":["Auth"],"summary":"Verify","description":"Exchange a one-time login token for a session cookie.\n\nWhen the account has a confirmed second factor this returns\n``{\"mfa_required\": true}`` and an ``ds_mfa`` challenge cookie INSTEAD of a\nsession: possession of the mailbox is one factor, and on its own it is\nexactly the thing MFA exists to stop being sufficient.","operationId":"verify_api_auth_verify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/mfa":{"post":{"tags":["Auth"],"summary":"Verify Mfa","description":"Complete sign-in by presenting the second factor.\n\nFailure is a single generic 401 for every cause — no challenge, expired\nchallenge, attempts exhausted, wrong code, replayed timestep, spent\nrecovery code, locked enrolment. The client cannot tell them apart, which\nis what keeps this from being an oracle about the account's state.","operationId":"verify_mfa_api_auth_mfa_post","parameters":[{"name":"ds_mfa","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Mfa"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/from-checkout":{"post":{"tags":["Auth"],"summary":"From Checkout","description":"Instant entry after Stripe checkout: turn a paid session_id into a session.\n\n401 if the checkout isn't a genuine paid session; 409 if it's paid but the\nprovisioning webhook hasn't landed yet (the client should retry shortly).","operationId":"from_checkout_api_auth_from_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FromCheckoutIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/accept-invite":{"post":{"tags":["Auth"],"summary":"Accept Invite","description":"Accept an org invitation and sign in. POST only, token in the body.\n\nNever a GET: SameSite=Lax permits a top-level cross-site GET, so a GET\naccept would be CSRF-able from a link in any email — someone could be\njoined to a workspace by clicking a link that looked like something else.\nThe token arrives in the URL fragment, which the static /invite page reads\nclient-side and POSTs here, so it never reaches an access log.\n\nOn success this mints an ordinary session through the same\n`_start_session` the magic-link flow uses — there is no second session\nmechanism, and the new member's device gets the same new-sign-in alert\nevery other sign-in produces. MFA cannot apply: the account is being\ncreated by this request, so it has no second factor to present.","operationId":"accept_invite_api_auth_accept_invite_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptInviteIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/me":{"get":{"tags":["Auth"],"summary":"Me","operationId":"me_api_auth_me_get","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/logout":{"post":{"tags":["Auth"],"summary":"Logout","description":"Sign out THIS device, and only this device.\n\nBefore M11 this bumped a version number and signed every device out — a\nsurprise for anyone who closed a session on a borrowed laptop and found\ntheir phone logged out too. \"Sign out everywhere\" is now a separate,\ndeliberate action on the security page.","operationId":"logout_api_auth_logout_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/security":{"get":{"tags":["Account security"],"summary":"Get Security","description":"The security page's whole state: MFA status, code count, live devices.","operationId":"get_security_api_account_security_get","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/security/mfa/begin":{"post":{"tags":["Account security"],"summary":"Begin Mfa","description":"Stage a pending TOTP secret and return it with a locally rendered QR.\n\nReturning the secret in plaintext here is unavoidable and correct — it is\nthe one moment it has to reach the authenticator app — and it is why this\nroute needs fresh reauthentication: the response IS the enrolment material.\n\nRefuses outright when MFA is already on. Re-enrolling is a \"replace my\nfactor\" operation and must go through disable (which requires the current\nfactor) rather than quietly overwriting an active secret.","operationId":"begin_mfa_api_account_security_mfa_begin_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"403":{"description":"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":{"description":"Two-step sign-in is already on; disable it first."},"503":{"description":"Enrolment is closed while MFA_ENCRYPTION_KEY is rotated. Existing sign-ins, existing second factors and every other route are unaffected; retry shortly."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/security/mfa/confirm":{"post":{"tags":["Account security"],"summary":"Confirm Mfa","description":"Activate the pending secret and issue recovery codes, shown once.\n\nTurning MFA on also signs every OTHER device out. If the reason someone is\nenrolling is \"I think somebody else has access\", leaving that somebody's\nsession alive would defeat the entire exercise. This device stays signed in\n— it has just proved the new factor — but on a NEW cookie value, so a copy\nof the credential it was using dies with the change instead of surviving it.","operationId":"confirm_mfa_api_account_security_mfa_confirm_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmMfaIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"403":{"description":"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":{"description":"The code did not verify against the pending secret, or that secret has expired. Start enrolment again."},"503":{"description":"Enrolment is closed while MFA_ENCRYPTION_KEY is rotated. Existing sign-ins, existing second factors and every other route are unaffected; retry shortly."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/security/mfa/disable":{"post":{"tags":["Account security"],"summary":"Disable Mfa","description":"Turn the second factor off. Requires fresh sign-in AND a current code.\n\nEvery other device is signed out and this one's cookie value is replaced,\nfor the same reason as enabling: the account's authentication just got\nweaker, and a copy of the credential in use must not be one of the things\nthat survives it.","operationId":"disable_mfa_api_account_security_mfa_disable_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisableMfaIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"403":{"description":"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":{"description":"The submitted second factor was not accepted. One generic message covers every cause, deliberately."},"409":{"description":"Two-step sign-in is not on for this account."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/security/mfa/recovery-codes":{"post":{"tags":["Account security"],"summary":"Regenerate Recovery Codes","description":"Replace the whole recovery-code set. The old codes stop working at once.\n\nOther sessions are deliberately NOT revoked here: this is a routine\nhygiene action (\"I used one, give me a fresh sheet\"), not a response to\ncompromise, and signing someone's other devices out for it would train\nthem to avoid doing it.","operationId":"regenerate_recovery_codes_api_account_security_mfa_recovery_codes_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegenerateCodesIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"403":{"description":"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":{"description":"The submitted second factor was not accepted. One generic message covers every cause, deliberately."},"409":{"description":"Two-step sign-in is not on for this account."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/security/sessions":{"get":{"tags":["Account security"],"summary":"List Sessions","operationId":"list_sessions_api_account_security_sessions_get","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/security/sessions/{session_id}":{"delete":{"tags":["Account security"],"summary":"Revoke Session","description":"End one named session.\n\nOwnership is checked against the CALLER'S user id, and a row belonging to\nanyone else is a flat 404 — not a 403. A distinguishable \"exists but isn't\nyours\" would turn session ids into a cross-tenant existence oracle, and the\nid itself tells an attacker nothing they can use.","operationId":"revoke_session_api_account_security_sessions__session_id__delete","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"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":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/security/sessions/revoke-all":{"post":{"tags":["Account security"],"summary":"Revoke Other Sessions","description":"Sign out every OTHER device, keeping this one.\n\nKeeping the current session is the whole usability of the control: the\nperson doing it is reacting to something and should not be thrown out of\nthe page they are reacting on. \"Sign out of this one too\" is the ordinary\nSign out button, one click away.\n\nDeliberately NOT behind the reauthentication gate. Every gated action\nWEAKENS the account (bind a new factor, remove the existing one, reprint\nthe codes); this one strengthens it. Someone who has just seen a sign-in\nthey do not recognise must be able to act in one click, not go and fetch a\nfresh link from the mailbox that may itself be the problem.","operationId":"revoke_other_sessions_api_account_security_sessions_revoke_all_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents":{"post":{"tags":["Documents"],"summary":"Upload Document","description":"Sync on purpose: hashing, the capacity check and the blob write all\nblock, so FastAPI must dispatch this to a threadpool (see\nsend_for_signing). The body is read from UploadFile's spooled file with\na hard cap so an oversized body can't exhaust the VM's memory.","operationId":"upload_document_api_documents_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_document_api_documents_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentOut"}}}},"400":{"description":"The upload is not a valid supported PDF."},"409":{"description":"The organization is closed."},"413":{"description":"The upload exceeds the configured cap."},"500":{"description":"Upload failed and storage artifact cleanup remains pending."},"507":{"description":"The organization storage allowance or volume capacity is exhausted."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Documents"],"summary":"List Documents","description":"Org-scoped document list, newest first (the dashboard's home query).\n\nSync on purpose, like every handler here that touches the database: the\nSession is synchronous, so the connection-pool checkout and the query\nround-trip block the calling thread — that wait belongs in the\nthreadpool, not on the event loop (see send_for_signing).","operationId":"list_documents_api_documents_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DocumentListOut"},"title":"Response List Documents Api Documents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/cancellation-notices/manual-review":{"get":{"tags":["Documents"],"summary":"List Cancellation Manual Review","description":"Return the authenticated org's sanitized intervention queue.\n\nRecipient addresses, document titles, frozen provider HTML and raw\nprovider errors are deliberately absent. The queue is owner-session-only;\nno API caller and no admin/member session may adjudicate an unknowable\nprovider outcome.","operationId":"list_cancellation_manual_review_api_documents_cancellation_notices_manual_review_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":100,"title":"Limit"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CancellationManualReviewOut"},"title":"Response List Cancellation Manual Review Api Documents Cancellation Notices Manual Review Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}":{"get":{"tags":["Documents"],"summary":"Read Document","operationId":"read_document_api_documents__document_id__get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Documents"],"summary":"Delete Document","description":"Attempt to erase an envelope in this request — irreversible on success.\n\nBlobs and database rows are destroyed, not flagged. Two outcomes:\n\n- `{\"status\": \"deleted\"}` — no signature was ever issued; the envelope and\n  its entire record are gone.\n- `{\"status\": \"erased\"}` — a signature had been issued; the tamper-evident\n  audit record is retained so the signature stays integrity-verifiable. The\n  files and field values are destroyed; what remains is the hash-chained\n  event log — event types, timestamps, network metadata, the envelope title\n  and the SHA-256 fingerprints of the destroyed artifacts (DPA §9.2).\n\nSession or API key: the DPA promises deletion \"through the dashboard or\nAPI\". API keys additionally need the can_delete scope, granted when the\nkey is minted — a leaked everyday integration key must not be able to\nraze the org's envelopes. Idempotent on the residue — repeating the\nDELETE returns \"erased\" without touching the retained record.","operationId":"delete_document_api_documents__document_id__delete","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Database erasure committed. If verified post-commit blob removal fails or raises, cleanup_pending=true and a recovery warning are returned.","content":{"application/json":{"schema":{}}}},"403":{"description":"An API key without delete scope cannot erase documents."},"404":{"description":"Tenant-scoped document not found."},"409":{"description":"Erasure is blocked by provider-crossed lifecycle email or AI submission uncertainty that requires controlled support reconciliation."},"503":{"description":"Lifecycle delivery coordination is busy; retry safely."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/signers":{"post":{"tags":["Documents"],"summary":"Create Signer","operationId":"create_signer_api_documents__document_id__signers_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignerIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignerOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/signers/me":{"post":{"tags":["Documents"],"summary":"Create Signer Me","description":"Add the signed-in dashboard user as an ordinary signing recipient (\"Add me\").\n\nSync on purpose — it waits on the document row lock (see create_signer).\n\nSession-only and body-less on purpose: the identity is derived server-side\nfrom the ACCEPTED session's user row, never from anything the client sends —\na stale /me cache or a cross-tab sign-in can't put the wrong person on the\nenvelope, and an API key (whose bearer is a program, not the person at the\nkeyboard) is refused by the dependency with a 401. This creates a normal\nrecipient and nothing more: they still get the signing email, magic link and\none-time code like everyone else — never a shortcut past the OTP ceremony.\n\nRouting lands in the NEXT signing group after the current maximum (an empty\ndraft starts at 0): the sender reviewing their own pack signs after the\nrecipients they are adding it for, and never silently jumps the queue.","operationId":"create_signer_me_api_documents__document_id__signers_me_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignerOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/fields":{"post":{"tags":["Documents"],"summary":"Create Field","operationId":"create_field_api_documents__document_id__fields_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/fields/{field_id}":{"patch":{"tags":["Documents"],"summary":"Edit Field","description":"Move/resize/retype/reassign a field on a draft (the visual composer's edit surface).\n\nSync on purpose — it waits on the document row lock (see create_signer).","operationId":"edit_field_api_documents__document_id__fields__field_id__patch","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"field_id","in":"path","required":true,"schema":{"type":"string","title":"Field Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Documents"],"summary":"Delete Field","operationId":"delete_field_api_documents__document_id__fields__field_id__delete","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"field_id","in":"path","required":true,"schema":{"type":"string","title":"Field Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/signers/{signer_id}":{"delete":{"tags":["Documents"],"summary":"Delete Signer","description":"Remove a recipient from a draft, along with any fields assigned to them.\n\nSync on purpose — it waits on the document row lock (see create_signer).","operationId":"delete_signer_api_documents__document_id__signers__signer_id__delete","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"signer_id","in":"path","required":true,"schema":{"type":"string","title":"Signer Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/save-as-template":{"post":{"tags":["Documents"],"summary":"Save As Template","description":"Snapshot this DRAFT's PDF + recipients + fields into a reusable template.\n\nThe snapshot copies the blob — templates outlive their source envelope, so\nthey must not share its storage (erasing the envelope destroys its files).\nDrafts only: once sent, an envelope is (becoming) evidence, and its content\nmust not be copyable out from under the erasure rules. The service holds\nthe org + document row locks, so a concurrent erasure serialises with the\ncopy instead of racing it. Sync on purpose — file copy work belongs in the\nthreadpool, not on the event loop (see send_for_signing).","operationId":"save_as_template_api_documents__document_id__save_as_template_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveAsTemplateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateOut"}}}},"404":{"description":"Tenant-scoped source document not found."},"409":{"description":"The source is not a live draft or its original PDF is unavailable."},"500":{"description":"Template copy failed and storage cleanup remains pending."},"507":{"description":"The organization storage allowance or volume capacity is exhausted."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/original":{"get":{"tags":["Documents"],"summary":"Download Original Pdf","description":"The original as uploaded — the composer renders placement on it.\n\nNo audit event: the sender previewing their own working copy is not part\nof the signing ceremony. DOWNLOADED is reserved for the signed evidence.\n\nSync on purpose (see send_for_signing): storage.get is a blocking read of\na PDF up to the upload cap, and template-created drafts hit this route\nevery time the composer opens — it must run in the threadpool, not stall\nthe event loop.","operationId":"download_original_pdf_api_documents__document_id__original_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Exact original bytes after SHA-256 verification under the organization-scoped document lock; private/no-store and nosniff.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"409":{"description":"Original storage or its recorded SHA-256 is missing, or the stored bytes fail integrity verification; private/no-store and nosniff."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/send":{"post":{"tags":["Documents"],"summary":"Send For Signing","description":"Finalize a draft and queue durable invitations for the first signer tier.\n\nSending is the metered action: it consumes one envelope from the org's plan\nallowance (402 when spent). Consumption, token hashes, frozen provider\nrequests and SENT audit events commit together. Provider acceptance is a\nseparate recoverable phase, so a pending or ambiguous email does not roll the\nenvelope back or refund its allowance.\n\nDeclared sync on purpose: this handler blocks (email, and optionally an\nAnthropic call for the signer summary), so FastAPI must dispatch it to a\nthreadpool. As an `async def` it ran on the event loop, where one slow AI call\nstalled every other request in the process — including a signer submitting a\nsignature. Do not make this `async` without moving the blocking work off-loop.","operationId":"send_for_signing_api_documents__document_id__send_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentOut"}}}},"400":{"description":"Draft composition is incomplete or invalid."},"402":{"description":"The subscription is lapsed, its allowance is exhausted, or authoritative trial reconciliation is required."},"404":{"description":"The organization or tenant-scoped document does not exist."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/remind":{"post":{"tags":["Documents"],"summary":"Remind Signers","description":"Queue and attempt a reminder for everyone whose turn it currently is.\n\nMinting is how re-sending works (only the token's hash is stored), so each\nreminder supersedes the link already in the recipient's inbox. Link rotation,\nfrozen provider requests and audit truth commit atomically; the response\nreports provider acceptance/pending/manual-review truth, never inbox delivery.\nDeclared sync on purpose — the bounded outbox drain can call the provider.","operationId":"remind_signers_api_documents__document_id__remind_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReminderOut"}}}},"409":{"description":"The document has no currently eligible recipients to remind."},"503":{"description":"Fresh links and durable reminder rows committed, but provider processing could not acquire its bounded lifecycle fence."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/void":{"post":{"tags":["Documents"],"summary":"Void Document","description":"Cancel an in-flight envelope. Terminal and irreversible, like decline.\n\nEvery outstanding signing link stops working the moment this commits; every\nrecipient already contacted (including someone who signed earlier in a\npartially signed ceremony) gets a durable cancellation notice. Draft\nenvelopes can't be voided (nothing was ever sent — delete them instead),\nand settled ones (completed, declined, expired) are history, not\ncancellable. Repeating a successful void drains any still-undelivered\ndurable notices without adding another audit event. Sync on purpose — it\nmay run a bounded post-commit outbox delivery phase.","operationId":"void_document_api_documents__document_id__void_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/VoidIn"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"The envelope is voided. Repeated calls are idempotent and only process due durable cancellation notices.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidOut"}}}},"409":{"description":"The envelope is not voidable in its current state."},"404":{"description":"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":{"description":"Lifecycle coordination is busy, or the void committed and only durable cancellation-notification processing remains pending; retry safely to drain due work."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/correct":{"post":{"tags":["Documents"],"summary":"Correct Document Route","description":"Void this envelope and create a corrected DRAFT copy of it.\n\nA sent envelope is evidence: its bytes, fields, recipients and signatures\nare never edited in place. This is the sanctioned way to fix a mistake —\nthe original is cancelled and a new draft carries forward the exact\noriginal PDF, the recipient list and the field placements, with fresh ids,\nno signature values and no tokens. Both audit chains record the link and\nthe reason, so the pair reads as one story from either end. The new draft\nis never auto-sent: review and send it like any other. Creating it reserves\nand consumes one new envelope immediately; that atomic reservation keeps\nthe exact successor sendable if the allowance fills or subscription lapses\nlater.\n\n`201` a corrected draft was created · `200` its previously created,\nstill-editable draft is returned (idempotent — a double-click voids\nnothing twice, while undelivered durable notices may be retried) · `409`\nthe envelope cannot be corrected (a draft is still editable, a completed\nenvelope is never voided, an erased one has nothing to copy, the recorded\nsuccessor is missing/erased/no longer a draft, or the stored original is\nmissing or no longer matches its fingerprint) ·\n`402` no new envelope is available (the source remains live and untouched) ·\n`507` the copy would exceed the org's storage allowance.\n\nSession or API key, and deliberately no extra key scope: this composes\n`/void` and a blob clone, both of which any org-scoped key may already do,\nso requiring the delete scope here would only push callers to the\nlower-level route. It destroys nothing — the source and every artifact and\naudit event it owns are preserved exactly as they were.\n\nSync on purpose (see send_for_signing): it blocks on a PDF read, a blob\nwrite, the document row lock and — after the commit — outbound email.","operationId":"correct_document_route_api_documents__document_id__correct_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CorrectIn"}}}},"responses":{"201":{"description":"A new corrected draft was created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CorrectionOut"}}}},"200":{"description":"Idempotent retry returning the existing corrected draft.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CorrectionOut"}}}},"400":{"description":"The correction reason or source PDF cannot be processed."},"402":{"description":"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":{"description":"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":{"description":"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":{"description":"Lifecycle coordination is busy, or the correction committed and only durable cancellation-notification processing remains pending; retry safely to drain due work."},"500":{"description":"Correction failed after publishing storage and immediate artifact cleanup could not be confirmed; cleanup_pending is returned."},"507":{"description":"The corrected PDF copy cannot be stored within the organization's storage allowance; the source remains unchanged."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/ai/suggest-fields":{"post":{"tags":["Documents"],"summary":"Suggest Document Fields","description":"Auto-detect signing fields in the stored PDF and persist them.\n\nDetection is deterministic text-anchor matching, so these land with\nplacement_source=\"anchor\". The \"/ai/\" in the path is a legacy name that\nintegrations depend on — it does not mean a model ran.\n\nSync on purpose (see send_for_signing): the blocking storage read plus\nCPU-bound PDF text extraction must run in the threadpool, not on the\nevent loop.","operationId":"suggest_document_fields_api_documents__document_id__ai_suggest_fields_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FieldOut"},"title":"Response Suggest Document Fields Api Documents  Document Id  Ai Suggest Fields Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/ai/analysis":{"post":{"tags":["Documents"],"summary":"Analyse Document","description":"Key clauses + risk flags for the *sender*, before they send.\n\nAdvisory only: never stored on the audit chain, the seal or the certificate.\nReturns `available: false` rather than an error whenever we cannot produce\nsomething useful — the org hasn't opted in, no key is configured, the PDF has\nno extractable text, or the model gave us nothing. AI must not be able to turn\na working page into an error.\n\nSync on purpose (see send_for_signing): this blocks on Anthropic and must run\nin a threadpool, not on the event loop.\n\nThe result is cached against the original's SHA-256, so repeat views cost\nnothing and a client calling this in a loop cannot run up our Anthropic bill.\nDraft-only for the same reason — it is a pre-send aid, and a sent envelope's\ntext does not change.","operationId":"analyse_document_api_documents__document_id__ai_analysis_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClauseAnalysisOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/download":{"get":{"tags":["Documents"],"summary":"Download Signed Pdf","description":"The signed artifact. Every successful download is recorded on the audit chain.\n\nThe exact stored bytes must match the recorded SHA-256\n(``signed_sha256``) before a DOWNLOADED event is appended or any content is\nserved. A missing fingerprint, missing blob or mismatch returns 409.\nSuccessful responses are private/no-store and carry ``nosniff``.\n\nSync (threadpool) and holding the same document-first lock the writers\ntake: without it a concurrent DELETE can commit its erasure between our\nstatus check and our audit append, and this route would then hand out\nbytes the API says are gone and append DOWNLOADED to a sealed chain\n(or 500 on a duplicated chain sequence). Locked, the two serialise:\nwhichever wins, the other sees committed truth.","operationId":"download_signed_pdf_api_documents__document_id__download_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"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.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"409":{"description":"Document is not completed or the signed artifact is missing/corrupt."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/certificate":{"get":{"tags":["Documents"],"summary":"Download Certificate","description":"The certificate of completion (PDF).\n\nThe exact stored bytes must match the certificate's recorded SHA-256 before\na DOWNLOADED event is appended or any content is served. A missing\nfingerprint, missing blob or mismatch returns 409. Successful responses are\nprivate/no-store and carry ``nosniff``.\n\nSync + document-first lock, same as the signed download: this read also\nappends to the audit chain, so it must serialise against a concurrent\nerasure rather than race it.","operationId":"download_certificate_api_documents__document_id__certificate_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"SHA-256-verified completion certificate; private/no-store and nosniff.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"409":{"description":"Document is not completed or certificate evidence is missing/corrupt."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/audit":{"get":{"tags":["Documents"],"summary":"Read Audit Trail","operationId":"read_audit_trail_api_documents__document_id__audit_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AuditEventOut"},"title":"Response Read Audit Trail Api Documents  Document Id  Audit Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/evidence":{"get":{"tags":["Documents"],"summary":"Download Evidence Bundle","description":"The detached, write-once evidence bundle for a completed envelope.\n\nThis is the artifact that makes the evidence independently verifiable: the\ncanonical audit records the hash chain covers, every RFC 3161 token in DER\nform, the authority's trust anchors as PEM, the artifact hashes and a\ndeterministic verification procedure — none of which the human-readable\ncertificate can carry, and none of which should require an authenticated\ncall to Drakon to obtain.\n\nPublished once, after the completion checkpoint resolves; 409 until then,\nwith the reason, because freezing a write-once artifact around \"no\ncompletion checkpoint exists\" while one is queued would be untrue.\n\nSync + document-first lock, same as the certificate download: it appends to\nthe audit chain and must serialise against a concurrent erasure.","operationId":"download_evidence_bundle_api_documents__document_id__evidence_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"SHA-256-verified evidence bundle: a PDF carrying the canonical evidence manifest as an embedded evidence.json attachment; private/no-store and nosniff.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"409":{"description":"The envelope is not completed, its completion checkpoint is still outstanding, or the stored bundle failed its integrity check."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/evidence/editions":{"get":{"tags":["Documents"],"summary":"List Evidence Editions","description":"Every published edition of this envelope's evidence bundle.\n\nPublication is write-once per edition and additive across them, so a\nrecipient can be holding edition 1 while edition 2 is current. Before this\nexisted, `editions()` could enumerate the rows internally but nothing in the\nproduct could reach an earlier one — a superseded edition was, in practice,\nunobtainable the moment it was superseded. Read-only: it publishes nothing\nand appends nothing.","operationId":"list_evidence_editions_api_documents__document_id__evidence_editions_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvidenceEditionsOut"}}}},"404":{"description":"Unknown envelope for this tenant."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/evidence/editions/{edition}":{"get":{"tags":["Documents"],"summary":"Download Evidence Edition","description":"One historical edition, byte for byte, verified against its own SHA-256.\n\nDeliberately NOT a publish path: asking for edition 1 must never mint\nedition 3. It serves what was published or 404s, which is what makes the\nword \"immutable\" mean something a caller can rely on.","operationId":"download_evidence_edition_api_documents__document_id__evidence_editions__edition__get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"edition","in":"path","required":true,"schema":{"type":"integer","title":"Edition"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"SHA-256-verified bytes of ONE immutable edition, exactly as published.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Unknown envelope for this tenant, or no such edition."},"409":{"description":"The stored bytes failed their integrity check."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/audit/verify":{"get":{"tags":["Documents"],"summary":"Verify Audit Trail","description":"Recompute the full hash chain and validate every typed checkpoint.\n\nAn RFC 3161 checkpoint covers only the event prefix present at its target\nevent. The response reports each checkpoint's covered prefix separately from\nthe later internally chained events outside that timestamp, and the\ntop-level `anchor_*` fields describe the envelope's primary TERMINAL\ncheckpoint — completion for a completed envelope, the declined/voided/\nexpired checkpoint otherwise.\n\nSync on purpose (see send_for_signing): rehashing the full chain and the\nanchors' CMS signature validation are CPU-bound work that blocks the\ncalling thread — it belongs in the threadpool, not on the event loop.","operationId":"verify_audit_trail_api_documents__document_id__audit_verify_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditVerifyOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/anchor":{"get":{"tags":["Documents"],"summary":"Read Anchor","description":"One RFC 3161 checkpoint token + metadata, for independent verification.\n\nWithout `kind`, returns the envelope's primary TERMINAL checkpoint — which\nfor a completed envelope is the completion anchor this endpoint has always\nreturned. Use `GET /anchors` for the whole set.\n\nSync on purpose — synchronous database work (see list_documents).","operationId":"read_anchor_api_documents__document_id__anchor_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChainAnchorOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Documents"],"summary":"Create Anchor","description":"Anchor a terminal prefix, or return an existing *validated* checkpoint.\n\nWithout `kind` this targets the envelope's primary TERMINAL checkpoint —\nfor a completed envelope, the COMPLETED-event prefix this endpoint has\nalways anchored. `kind=signatures` recovers the checkpoint the completion\ncertificate reports; `kind=declined|voided|expired` recovers a terminal\noutcome whose checkpoint the TSA was not available for.\n\n201 new checkpoint · 200 already checkpointed and valid · 409 no valid\ntarget prefix or stored token invalid · 502 TSA down/invalid new token ·\n503 trusted-root configuration missing. A corrupt/forged stored token is\nevidence and is never silently replaced. Retry applies only while no row\nexists for that kind.\n\nSync on purpose (see send_for_signing): anchoring blocks on the TSA\nround-trip (sealing._run joins its worker thread for up to the TSA\ntimeout), so this must run in the threadpool, not on the event loop.","operationId":"create_anchor_api_documents__document_id__anchor_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Existing checkpoint over the target prefix was cryptographically validated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChainAnchorOut"}}}},"201":{"description":"A new RFC 3161 checkpoint was created over the target event's prefix."},"400":{"description":"An unknown checkpoint kind was requested."},"409":{"description":"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":{"description":"No anchor exists and the timestamp authority did not answer, or it returned a new token that failed immediate validation."},"503":{"description":"Trusted TSA roots are absent or malformed, so neither an existing token nor a newly requested token can be independently validated."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{document_id}/anchors":{"get":{"tags":["Documents"],"summary":"Read Anchors","description":"Every stored checkpoint for the envelope, tokens included.\n\nThis is the endpoint the completion certificate points a reader at: the\ncertificate reproduces the `signatures` checkpoint it could describe when\nits bytes were frozen, and the `completion` checkpoint that covers the\ncertificate's own SHA-256 is fetched from here.\n\nSync on purpose — synchronous database work (see list_documents).","operationId":"read_anchors_api_documents__document_id__anchors_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChainAnchorListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/templates":{"get":{"tags":["Templates"],"summary":"List Templates","description":"Org-scoped template list, newest first (the 'New from template' picker).\n\nSync on purpose — the Session is synchronous, so the pool checkout and\nquery block the calling thread; that wait belongs in the threadpool, not\non the event loop (see documents.list_documents).","operationId":"list_templates_api_templates_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TemplateOut"},"title":"Response List Templates Api Templates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/templates/{template_id}":{"delete":{"tags":["Templates"],"summary":"Delete Template","description":"Delete a template — row and PDF blob.\n\nSame credential rule as delete_document: sessions always may, API keys\nonly with the can_delete scope — a leaked everyday integration key must\nnot be able to strip the org's templates. (Templates are working material,\nnot evidence, so nothing is retained.)","operationId":"delete_template_api_templates__template_id__delete","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Template row deletion committed. If verified post-commit blob removal fails or raises, cleanup_pending=true and a recovery warning are returned.","content":{"application/json":{"schema":{}}}},"403":{"description":"An API key without delete scope cannot delete templates."},"404":{"description":"Tenant-scoped template not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/templates/{template_id}/use":{"post":{"tags":["Templates"],"summary":"Use Template Route","description":"Create a fresh DRAFT envelope from a template. Never auto-sends —\nthe caller reviews (and can adjust) in the composer before sending.\nSync on purpose: it blocks on file copy work and must run in FastAPI's\nthreadpool, not on the event loop (see send_for_signing).","operationId":"use_template_route_api_templates__template_id__use_post","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UseTemplateIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentOut"}}}},"400":{"description":"Recipient input is invalid."},"404":{"description":"Tenant-scoped template not found."},"409":{"description":"The template PDF/data is unavailable or the organization is closed."},"500":{"description":"Draft copy failed and storage cleanup remains pending."},"507":{"description":"The organization storage allowance or volume capacity is exhausted."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/session/document":{"get":{"tags":["Signing"],"summary":"Session Document","operationId":"session_document_api_sign_session_document_get","parameters":[{"name":"X-Signing-Session","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Signing-Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignDocumentView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/session/pdf":{"get":{"tags":["Signing"],"summary":"Session Pdf","operationId":"session_pdf_api_sign_session_pdf_get","parameters":[{"name":"X-Signing-Session","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Signing-Session"}}],"responses":{"200":{"description":"Exact original bytes after SHA-256 verification under the ceremony locks; private/no-store and nosniff.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"The signing session is missing, expired or revoked."},"409":{"description":"Original storage or its recorded SHA-256 is missing, or the stored bytes fail integrity verification; private/no-store and nosniff."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/session/sign":{"post":{"tags":["Signing"],"summary":"Session Sign","operationId":"session_sign_api_sign_session_sign_post","parameters":[{"name":"X-Signing-Session","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Signing-Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignResultOut"}}}},"400":{"description":"Required field values or signing consent are invalid."},"401":{"description":"The signing session is missing, expired or revoked."},"409":{"description":"The document or audit/original integrity state prevents signing."},"500":{"description":"A completion-artifact write failed and storage cleanup remains pending; the signature transition was not committed."},"503":{"description":"Lifecycle-email delivery coordination is busy; no signature transition was committed and the request can be retried."},"507":{"description":"Completion artifacts could not be stored; the signature transition was rolled back and can be retried after capacity is restored."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/session/decline":{"post":{"tags":["Signing"],"summary":"Session Decline","operationId":"session_decline_api_sign_session_decline_post","parameters":[{"name":"X-Signing-Session","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Signing-Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeclineIn"}}}},"responses":{"204":{"description":"Successful Response"},"401":{"description":"The signing session is missing, expired or revoked."},"409":{"description":"The envelope reached a state that can no longer be declined."},"503":{"description":"Lifecycle-email delivery coordination is busy; no decline transition was committed and the request can be retried."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/receipt/{token}/session":{"post":{"tags":["Signing"],"summary":"Create Receipt Session","description":"Exchange an explicit browser gesture for a short-lived header token.\n\nCompletion mail links only to the inert frontend receipt page. Automated\nGET/HEAD prefetch therefore cannot reach this POST, cannot obtain evidence,\nand cannot append a signer DOWNLOADED event.","operationId":"create_receipt_session_api_sign_receipt__token__session_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptSessionIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptSessionOut"}}}},"404":{"description":"Unknown, revoked or erased receipt credential."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/receipt/{token}/signed":{"get":{"tags":["Signing"],"summary":"Receipt Signed Pdf","operationId":"receipt_signed_pdf_api_sign_receipt__token__signed_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}},{"name":"X-Receipt-Session","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Receipt-Session"}}],"responses":{"200":{"description":"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).","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Unknown, revoked or erased receipt credential."},"401":{"description":"Explicit receipt-session confirmation is required."},"409":{"description":"Completed evidence missing or integrity check failed."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/receipt/{token}/certificate":{"get":{"tags":["Signing"],"summary":"Receipt Certificate","operationId":"receipt_certificate_api_sign_receipt__token__certificate_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}},{"name":"X-Receipt-Session","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Receipt-Session"}}],"responses":{"200":{"description":"SHA-256-verified completion certificate for this signer.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Unknown, revoked or erased receipt credential."},"401":{"description":"Explicit receipt-session confirmation is required."},"409":{"description":"Completed evidence missing or integrity check failed."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/receipt/{token}/evidence":{"get":{"tags":["Signing"],"summary":"Receipt Evidence Bundle","operationId":"receipt_evidence_bundle_api_sign_receipt__token__evidence_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}},{"name":"edition","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"A specific immutable edition. Omit for the current one — which is what every existing caller does, and its behaviour is unchanged.","title":"Edition"},"description":"A specific immutable edition. Omit for the current one — which is what every existing caller does, and its behaviour is unchanged."},{"name":"X-Receipt-Session","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Receipt-Session"}}],"responses":{"200":{"description":"SHA-256-verified evidence bundle for this signer: a PDF carrying the canonical evidence manifest as an embedded evidence.json attachment.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Unknown, revoked or erased receipt credential."},"401":{"description":"Explicit receipt-session confirmation is required."},"409":{"description":"The completion checkpoint is still outstanding, or the stored bundle failed its integrity check."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/receipt/{token}/evidence/editions":{"get":{"tags":["Signing"],"summary":"Receipt Evidence Editions","description":"Which editions this signer's own envelope has published.\n\nBehind the same explicit `artifact: \"evidence\"` confirmation session as the\ndownload, and scoped to the one document the receipt resolves to. Read-only:\nit publishes nothing and appends no audit event, because listing what exists\nis not a download.","operationId":"receipt_evidence_editions_api_sign_receipt__token__evidence_editions_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}},{"name":"X-Receipt-Session","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Receipt-Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvidenceEditionsOut"}}}},"404":{"description":"Unknown, revoked or erased receipt credential."},"401":{"description":"Explicit receipt-session confirmation is required."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/{token}":{"get":{"tags":["Signing"],"summary":"View Link","operationId":"view_link_api_sign__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignerViewOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/{token}/otp":{"post":{"tags":["Signing"],"summary":"Send Otp","operationId":"send_otp_api_sign__token__otp_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"204":{"description":"Successful Response"},"202":{"description":"The OTP request and candidate hash are committed; provider acceptance remains pending or ambiguous."},"404":{"description":"The signing ceremony or presented link is no longer live."},"429":{"description":"OTP request budget exhausted or rate limited."},"502":{"description":"The provider authoritatively rejected the staged OTP; any previous valid challenge remains usable."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/{token}/verify-otp":{"post":{"tags":["Signing"],"summary":"Verify Otp","operationId":"verify_otp_api_sign__token__verify_otp_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpVerifyIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sign/{token}/decline":{"post":{"tags":["Signing"],"summary":"Decline","description":"A magic link alone is not authority to void the legal ceremony.\n\nKept as an explicit compatibility refusal rather than silently removing the\nroute: old clients receive actionable, non-terminal truth. The dependency\nstill prevents token enumeration, but neither the reason nor the envelope\nis mutated until OTP verification created a signing session.","operationId":"decline_api_sign__token__decline_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeclineIn"}}}},"responses":{"403":{"description":"A magic link is not sufficient identity proof for a terminal decline; verify by OTP and use the signing-session route.","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/webhook":{"post":{"tags":["Billing"],"summary":"Stripe Webhook","operationId":"stripe_webhook_api_billing_webhook_post","parameters":[{"name":"Stripe-Signature","in":"header","required":false,"schema":{"type":"string","title":"Stripe-Signature"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/checkout":{"post":{"tags":["Billing"],"summary":"Start Checkout","description":"Start a Stripe Checkout for the caller's own organisation.\n\nThis is the path a returning customer takes instead of a public payment link.\nThe public links provision *new* workspaces and cannot safely attach to an\nexisting one (the checkout email is unverified), so an existing account that\ngoes through one ends up paid-but-unattached. Here the workspace is known\nbefore Stripe is ever called.","operationId":"start_checkout_api_billing_checkout_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartCheckoutIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/portal":{"post":{"tags":["Billing"],"summary":"Billing Portal","description":"Open a Stripe Customer Portal session for the caller's org (upgrade/cancel).","operationId":"billing_portal_api_billing_portal_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account":{"get":{"tags":["Account"],"summary":"Get Account","operationId":"get_account_api_account_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/ai":{"patch":{"tags":["Account"],"summary":"Set Ai Enabled","description":"Turn the optional AI features on or off for the caller's organisation.","operationId":"set_ai_enabled_api_account_ai_patch","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiSettingIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/api-keys":{"get":{"tags":["Account"],"summary":"List Api Keys","description":"The org's API keys. Never the secrets — prefixes are display only.","operationId":"list_api_keys_api_account_api_keys_get","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Account"],"summary":"Create Api Key","description":"Mint an API key for the org. The plaintext is returned once and never again.","operationId":"create_api_key_api_account_api_keys_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeyIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/close":{"post":{"tags":["Account"],"summary":"Close Account","description":"Close the account and begin its destructive live-data purge in this request.\n\nThe DPA (§9.2) promises purge within 30 days of closure; doing it inside the\nclosure itself satisfies that with nothing left to schedule or forget. Every\nenvelope is erased under the standard criteria (signed ones leave their\ntamper-evident audit residue — see DELETE /api/documents/{id} for exactly\nwhat that retains), users and API keys are deleted, the organization row is\nredacted, and any Stripe subscription is cancelled best-effort. Filesystem\ndeletion is verified; a storage or Stripe failure is returned explicitly and\nlogged for recovery rather than silently described as complete. Irreversible.","operationId":"close_account_api_account_close_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloseAccountIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"409":{"description":"Closure is blocked by provider-crossed lifecycle email or AI submission uncertainty that requires controlled support reconciliation."},"503":{"description":"Organization lifecycle delivery coordination is busy; no closure is reported, so retry safely."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/api-keys/{key_id}":{"delete":{"tags":["Account"],"summary":"Revoke Api Key","operationId":"revoke_api_key_api_account_api_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/members":{"get":{"tags":["Account"],"summary":"List Members","description":"The workspace's members. Admin+, session-only.","operationId":"list_members_api_account_members_get","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/members/{user_id}/api-keys":{"get":{"tags":["Account"],"summary":"Member Api Keys","description":"The live keys a member minted — what removing them would stop working.\n\nSurfaced so the removal warning states facts. If the departing admin minted\nthe key an integration runs on, that integration stops the instant they are\nremoved, and the person clicking Remove should see which.","operationId":"member_api_keys_api_account_members__user_id__api_keys_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/members/{user_id}":{"patch":{"tags":["Account"],"summary":"Change Member Role","description":"Change a member's role. Never your own; never above your own rank.","operationId":"change_member_role_api_account_members__user_id__patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleChangeIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Account"],"summary":"Remove Member","description":"Remove a member. Their sessions, magic link and API keys all stop working.","operationId":"remove_member_api_account_members__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/invites":{"get":{"tags":["Account"],"summary":"List Invites","description":"Pending invitations for the caller's org. Never the tokens.","operationId":"list_invites_api_account_invites_get","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Account"],"summary":"Create Invite","description":"Invite an address into the caller's workspace.\n\n202, and the email is dispatched after the response, copying the shape of\n`request-link` so timing carries nothing. \"Already a member here\" and\n\"already invited here\" ARE distinguished — that is data the caller already\nowns. \"This address belongs to another DrakonSign workspace\" is NOT, and\nis never disclosed before the invitee accepts, or a cheap subscription\nbecomes an enumeration oracle over the whole customer base.","operationId":"create_invite_api_account_invites_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteIn"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/invites/{invite_id}/resend":{"post":{"tags":["Account"],"summary":"Resend Invite","description":"Re-issue an invitation. This ROTATES the token — the old link dies.","operationId":"resend_invite_api_account_invites__invite_id__resend_post","parameters":[{"name":"invite_id","in":"path","required":true,"schema":{"type":"string","title":"Invite Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/invites/{invite_id}":{"delete":{"tags":["Account"],"summary":"Revoke Invite","description":"Withdraw a pending invitation. The emailed link stops working at once.","operationId":"revoke_invite_api_account_invites__invite_id__delete","parameters":[{"name":"invite_id","in":"path","required":true,"schema":{"type":"string","title":"Invite Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/activity":{"get":{"tags":["Account"],"summary":"List Org Activity","description":"Append-only member and API-key activity for the caller's org.\n\nAn activity log, NOT evidence: it is not hash-chained and makes no\ntamper-evidence claim. It is deliberately a separate table from the\ndocument audit trail — widening `audit_events` to host org events would\nhave changed `hash_payload()` and invalidated every existing document\nchain, anchor and certificate.","operationId":"list_org_activity_api_account_activity_get","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/webhooks":{"get":{"tags":["Webhooks"],"summary":"List Webhooks","description":"The workspace's destinations, plus what this deployment can do.\n\n``configured`` is what the dashboard branches on. A deployment with no\n``WEBHOOK_ENCRYPTION_KEY`` reports false and the UI explains that instead\nof offering a Create button that would 503 — the same shape as\n``ai_configured``.","operationId":"list_webhooks_api_account_webhooks_get","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Webhooks"],"summary":"Create Webhook","description":"Register a destination. The signing secret is returned ONCE, here.\n\nA rejected destination (not HTTPS, an IP literal, a private DNS answer, a\nport that is not allowlisted) is a 400 carrying the specific reason, so the\ncustomer can fix it now rather than discovering it when their first\nenvelope completes and nothing arrives.","operationId":"create_webhook_api_account_webhooks_post","parameters":[{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EndpointIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/webhooks/{endpoint_id}/rotate-secret":{"post":{"tags":["Webhooks"],"summary":"Rotate Webhook Secret","description":"Mint a new signing secret. **The old one stops verifying immediately.**\n\nThere is no overlap window, deliberately — see\n``webhooks.rotate_secret`` for why a grace period is the wrong answer to\n\"I think my secret leaked\". The safe procedure is the customer's to run:\naccept both values in your receiver, rotate here, then drop the old one.","operationId":"rotate_webhook_secret_api_account_webhooks__endpoint_id__rotate_secret_post","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","title":"Endpoint Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/webhooks/{endpoint_id}/disable":{"post":{"tags":["Webhooks"],"summary":"Disable Webhook","description":"Stop this destination: no new events, and the pending queue is cancelled.\n\nCancelling the backlog rather than holding it is deliberate — a customer\nwho switches a misbehaving destination off does not want a stampede of\nstale events the moment they switch it back on. Anything they still want is\nan explicit replay from the delivery log.","operationId":"disable_webhook_api_account_webhooks__endpoint_id__disable_post","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","title":"Endpoint Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/webhooks/{endpoint_id}/enable":{"post":{"tags":["Webhooks"],"summary":"Enable Webhook","description":"Re-enable a destination. FUTURE events only.\n\nThe destination is re-validated here, not trusted from the row: it may have\nbeen re-pointed at an internal address while it sat disabled, and enabling\nis exactly the moment to find that out.","operationId":"enable_webhook_api_account_webhooks__endpoint_id__enable_post","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","title":"Endpoint Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/webhooks/{endpoint_id}":{"delete":{"tags":["Webhooks"],"summary":"Delete Webhook","description":"Remove a destination. The delivery history survives; the secret does not.\n\nSoft delete: the endpoint row stays (deliveries hold a non-nullable FK onto\nit, and \"we sent you the completion at 14:02 and you answered 200\" is the\nrecord a support argument turns on) but it disappears from every\ncustomer-facing query and its sealed secret is destroyed in the same\nstatement. What is retained is a record, not a usable credential to a\ndestination the customer has told us to forget.","operationId":"delete_webhook_api_account_webhooks__endpoint_id__delete","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","title":"Endpoint Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/webhooks/deliveries":{"get":{"tags":["Webhooks"],"summary":"List Deliveries","description":"Newest-first delivery log for the caller's own org.\n\n``endpoint_id`` filters; it is NOT a tenancy input. The org filter is\napplied first and always, from the session context, so a foreign endpoint\nid narrows the caller's own rows to none rather than widening them to\nsomebody else's.","operationId":"list_deliveries_api_account_webhooks_deliveries_get","parameters":[{"name":"endpoint_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endpoint Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/webhooks/deliveries/{delivery_id}/replay":{"post":{"tags":["Webhooks"],"summary":"Replay Delivery","description":"Queue the same event again, as a NEW delivery linked to its source.\n\n202, not 200: the row is durable when this returns and the request goes out\nafterwards. The new delivery carries the same ``event_id`` (so the\nreceiver's deduplication still recognises it) and frozen business-event\ndata; only ``delivery_id`` changes, in both body and header, so the replay\ndescribes the envelope as it was without naming the wrong attempt.\n\nThe original row is never mutated. A failed delivery is a fact about what\nhappened; rewriting it to pending would erase the record that we tried,\nexhausted the policy and gave up.","operationId":"replay_delivery_api_account_webhooks_deliveries__delivery_id__replay_post","parameters":[{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string","title":"Delivery Id"}},{"name":"ds_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ds Session"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AcceptInviteIn":{"properties":{"token":{"type":"string","title":"Token"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},"type":"object","required":["token"],"title":"AcceptInviteIn"},"AiSettingIn":{"properties":{"ai_enabled":{"type":"boolean","title":"Ai Enabled"}},"type":"object","required":["ai_enabled"],"title":"AiSettingIn"},"AuditEventOut":{"properties":{"sequence":{"type":"integer","title":"Sequence"},"event_type":{"type":"string","title":"Event Type"},"actor":{"type":"string","title":"Actor"},"signer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signer Id"},"ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip"},"user_agent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent"},"timestamp_utc":{"type":"string","title":"Timestamp Utc"},"payload_json":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payload Json"},"prev_hash":{"type":"string","title":"Prev Hash"},"hash":{"type":"string","title":"Hash"}},"type":"object","required":["sequence","event_type","actor","signer_id","ip","user_agent","timestamp_utc","payload_json","prev_hash","hash"],"title":"AuditEventOut"},"AuditVerifyOut":{"properties":{"intact":{"type":"boolean","title":"Intact"},"first_broken_sequence":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Broken Sequence"},"event_count":{"type":"integer","title":"Event Count"},"anchored":{"type":"boolean","title":"Anchored","default":false},"anchor_expected":{"type":"boolean","title":"Anchor Expected","description":"True when the retained audit history contains a terminal event (completed, declined, voided or expired), including erased residues.","default":false},"anchor_missing_recorded":{"type":"boolean","title":"Anchor Missing Recorded","description":"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":{"type":"boolean","title":"Any Checkpoint Missing Recorded","description":"True when the chain records a fail-open checkpoint failure for ANY kind. Read `checkpoints[]` to see which.","default":false},"state_consistent":{"type":"boolean","title":"State Consistent","description":"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":{"items":{"type":"string"},"type":"array","title":"Anomalies","description":"Plain statements of every inconsistency found. Empty when consistent."},"anchor_event_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Anchor Event Count","description":"Number of audit events in the RFC 3161-timestamped terminal prefix."},"unanchored_suffix_count":{"type":"integer","minimum":0.0,"title":"Unanchored Suffix Count","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anchored At"},"anchor_tsa":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anchor Tsa"},"anchor_valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Anchor Valid"},"anchor_validation_status":{"type":"string","enum":["not_present","valid","invalid","unverifiable_configuration"],"title":"Anchor Validation Status","default":"not_present"},"terminal_checkpoint_kind":{"anyOf":[{"type":"string","enum":["completion","declined","voided","expired"]},{"type":"null"}],"title":"Terminal Checkpoint Kind","description":"Which terminal checkpoint this envelope's outcome calls for, if any."},"checkpoints":{"items":{"$ref":"#/components/schemas/CheckpointOut"},"type":"array","title":"Checkpoints","description":"Every checkpoint kind relevant to this envelope, present or not."}},"type":"object","required":["intact","first_broken_sequence","event_count"],"title":"AuditVerifyOut"},"Body_upload_document_api_documents_post":{"properties":{"title":{"type":"string","maxLength":500,"minLength":1,"title":"Title"},"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["title","file"],"title":"Body_upload_document_api_documents_post"},"CancellationManualReviewOut":{"properties":{"id":{"type":"string","title":"Id"},"document_id":{"type":"string","title":"Document Id"},"notice_kind":{"type":"string","title":"Notice Kind"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"manual_review_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Manual Review At"},"attempt_count":{"type":"integer","title":"Attempt Count"},"provider_boundary_crossed":{"type":"boolean","title":"Provider Boundary Crossed"},"last_attempt_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Attempt At"},"idempotency_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Idempotency Expires At"},"payload_sha256":{"type":"string","title":"Payload Sha256"},"provider_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Message Id"},"reason_code":{"type":"string","title":"Reason Code"},"pii_redacted":{"type":"boolean","title":"Pii Redacted"},"support_required":{"type":"boolean","title":"Support Required","default":true}},"type":"object","required":["id","document_id","notice_kind","status","created_at","attempt_count","provider_boundary_crossed","payload_sha256","reason_code","pii_redacted"],"title":"CancellationManualReviewOut","description":"Sanitized org-scoped delivery view; never includes frozen email HTML."},"ChainAnchorListOut":{"properties":{"document_id":{"type":"string","title":"Document Id"},"anchors":{"items":{"$ref":"#/components/schemas/ChainAnchorOut"},"type":"array","title":"Anchors","default":[]}},"type":"object","required":["document_id"],"title":"ChainAnchorListOut"},"ChainAnchorOut":{"properties":{"document_id":{"type":"string","title":"Document Id"},"head_hash":{"type":"string","title":"Head Hash"},"event_count":{"type":"integer","title":"Event Count"},"tsa_url":{"type":"string","title":"Tsa Url"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"token_b64":{"type":"string","title":"Token B64"},"anchor_kind":{"type":"string","title":"Anchor Kind","default":"completion"},"target_event_type":{"type":"string","title":"Target Event Type","default":"completed"},"target_event_sequence":{"type":"integer","title":"Target Event Sequence","default":0}},"type":"object","required":["document_id","head_hash","event_count","tsa_url","created_at","token_b64"],"title":"ChainAnchorOut"},"CheckpointOut":{"properties":{"kind":{"type":"string","title":"Kind"},"kind_known":{"type":"boolean","title":"Kind Known","description":"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":{"type":"string","title":"Target Event Type","description":"The audit event type this checkpoint's token was taken over."},"present":{"type":"boolean","title":"Present","description":"True when a stored token exists for this kind."},"expected":{"type":"boolean","title":"Expected","description":"True when the retained audit history contains the target event, so a checkpoint of this kind should exist."},"missing_recorded":{"type":"boolean","title":"Missing Recorded","description":"True when the chain immutably records that this checkpoint's fail-open attempt failed.","default":false},"obligation_pending":{"type":"boolean","title":"Obligation Pending","description":"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":{"type":"string","enum":["not_present","valid","invalid","unverifiable_configuration"],"title":"Validation Status","default":"not_present"},"valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Valid"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"target_event_sequence":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Target Event Sequence"},"covered_event_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Covered Event Count","description":"Number of audit events inside this checkpoint's timestamped prefix."},"suffix_event_count":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Suffix Event Count","description":"Events after this checkpoint's prefix — internally chained, outside its timestamp."},"head_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Head Hash"},"tsa_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tsa Url"},"attested_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Attested At","description":"TSA-attested generation time. Present only for a token that validated."}},"type":"object","required":["kind","target_event_type","present","expected"],"title":"CheckpointOut","description":"One typed RFC 3161 checkpoint over the audit trail, described precisely."},"ClauseAnalysisOut":{"properties":{"available":{"type":"boolean","title":"Available"},"clauses":{"items":{"$ref":"#/components/schemas/ClauseOut"},"type":"array","title":"Clauses","default":[]},"risks":{"items":{"$ref":"#/components/schemas/RiskOut"},"type":"array","title":"Risks","default":[]}},"type":"object","required":["available"],"title":"ClauseAnalysisOut","description":"Pre-send reading aid for the sender (M10), never stored on the envelope.\n\n`available` is False whenever the org has not opted into AI, no key is\nconfigured, the PDF has no extractable text, or the model returned nothing\nusable. Callers render the panel only when it is True — AI is never allowed to\nturn a working page into an error."},"ClauseOut":{"properties":{"title":{"type":"string","title":"Title"},"detail":{"type":"string","title":"Detail"}},"type":"object","required":["title","detail"],"title":"ClauseOut"},"CloseAccountIn":{"properties":{"confirm":{"type":"boolean","title":"Confirm","default":false}},"type":"object","title":"CloseAccountIn"},"ConfirmMfaIn":{"properties":{"code":{"type":"string","title":"Code"}},"type":"object","required":["code"],"title":"ConfirmMfaIn"},"CorrectIn":{"properties":{"reason":{"type":"string","maxLength":500,"minLength":1,"title":"Reason"}},"type":"object","required":["reason"],"title":"CorrectIn"},"CorrectionOut":{"properties":{"created":{"type":"boolean","title":"Created"},"source_id":{"type":"string","title":"Source Id"},"source_status":{"type":"string","title":"Source Status"},"voided_now":{"type":"boolean","title":"Voided Now"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"provider_accepted":{"type":"integer","title":"Provider Accepted","default":0},"notify_permanent_failed":{"type":"integer","title":"Notify Permanent Failed","default":0},"notify_pending":{"type":"integer","title":"Notify Pending","default":0},"notify_manual_review":{"type":"integer","title":"Notify Manual Review","default":0},"notify_outcome_unknown":{"type":"integer","title":"Notify Outcome Unknown","default":0},"cleanup_pending":{"type":"boolean","title":"Cleanup Pending","default":false},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning"},"draft":{"$ref":"#/components/schemas/DocumentOut"}},"type":"object","required":["created","source_id","source_status","voided_now","draft"],"title":"CorrectionOut","description":"The result of a void-and-copy correction.\n\nReports what actually happened rather than assuming the happy path:\n`created` is False when an earlier request already produced this draft (the\nidempotent replay), `voided_now` is False when the source was already\nsettled and had nothing left to cancel. Notification counters report only\nprovider acceptance, pending work and terminal/manual-review states; they\nnever imply that a recipient received or opened an email."},"CreateKeyIn":{"properties":{"name":{"type":"string","title":"Name"},"can_delete":{"type":"boolean","title":"Can Delete","default":false}},"type":"object","required":["name"],"title":"CreateKeyIn"},"DeclineIn":{"properties":{"reason":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Reason"}},"type":"object","title":"DeclineIn"},"DisableMfaIn":{"properties":{"code":{"type":"string","title":"Code"}},"type":"object","required":["code"],"title":"DisableMfaIn"},"DocumentListOut":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"status":{"type":"string","title":"Status"},"page_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page Count"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"signers":{"items":{"$ref":"#/components/schemas/SignerOut"},"type":"array","title":"Signers","default":[]}},"type":"object","required":["id","title","status","page_count","created_at"],"title":"DocumentListOut","description":"Summary row for the dashboard list — field rectangles stay on the detail endpoint."},"DocumentOut":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"status":{"type":"string","title":"Status"},"original_sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Original Sha256"},"signed_sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed Sha256"},"page_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page Count"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"original_storage_recorded":{"type":"boolean","title":"Original Storage Recorded","default":false},"corrected_from_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Corrected From Document Id"},"corrected_by_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Corrected By Document Id"},"has_correction_lineage":{"type":"boolean","title":"Has Correction Lineage","default":false},"correction_consumed":{"type":"boolean","title":"Correction Consumed","default":false},"signers":{"items":{"$ref":"#/components/schemas/SignerOut"},"type":"array","title":"Signers","default":[]},"fields":{"items":{"$ref":"#/components/schemas/FieldOut"},"type":"array","title":"Fields","default":[]}},"type":"object","required":["id","title","status","original_sha256","page_count","created_at"],"title":"DocumentOut"},"DocumentStatus":{"type":"string","enum":["draft","sent","viewed","partially_signed","completed","declined","voided","expired","erased"],"title":"DocumentStatus"},"EndpointIn":{"properties":{"url":{"type":"string","maxLength":2000,"title":"Url"},"description":{"type":"string","maxLength":120,"title":"Description","default":""},"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types"}},"type":"object","required":["url"],"title":"EndpointIn","description":"A destination request.\n\nThere is deliberately no ``org_id`` and no ``secret``: the tenant comes\nfrom the session context and the secret is minted server-side. A caller\nsupplying its own secret would be choosing the strength of the credential\nthat authenticates us to them, which is not theirs to weaken."},"EvidenceEditionOut":{"properties":{"edition":{"type":"integer","title":"Edition","description":"1-based; the highest edition is the current one."},"sha256":{"type":"string","title":"Sha256","description":"SHA-256 of the exact published bytes."},"manifest_schema":{"type":"string","title":"Manifest Schema"},"supersedes_sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Supersedes Sha256","description":"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":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Published At"},"current":{"type":"boolean","title":"Current","description":"True for the edition GET /{id}/evidence serves right now."}},"type":"object","required":["edition","sha256","manifest_schema","current"],"title":"EvidenceEditionOut","description":"One immutable, published edition of the detached evidence bundle.\n\nEditions are additive: a bundle published while a checkpoint was still owed\nis never rewritten when that checkpoint lands, because a recipient may\nalready hold its bytes. A recipient must therefore be able to obtain the\nedition they were given, not merely the newest one, which is why this list\nand the per-edition download exist at all."},"EvidenceEditionsOut":{"properties":{"editions":{"items":{"$ref":"#/components/schemas/EvidenceEditionOut"},"type":"array","title":"Editions","default":[]},"lineage_assurance":{"type":"string","title":"Lineage Assurance","description":"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"}},"type":"object","title":"EvidenceEditionsOut"},"FieldIn":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page"},"x":{"type":"number","maximum":1.0,"minimum":0.0,"title":"X"},"y":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Y"},"width":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"title":"Width"},"height":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"title":"Height"},"field_type":{"$ref":"#/components/schemas/FieldType","default":"signature"},"required":{"type":"boolean","title":"Required","default":true},"signer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signer Id"}},"type":"object","required":["page","x","y","width","height"],"title":"FieldIn"},"FieldOut":{"properties":{"id":{"type":"string","title":"Id"},"page":{"type":"integer","title":"Page"},"x":{"type":"number","title":"X"},"y":{"type":"number","title":"Y"},"width":{"type":"number","title":"Width"},"height":{"type":"number","title":"Height"},"field_type":{"type":"string","title":"Field Type"},"required":{"type":"boolean","title":"Required"},"signer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signer Id"},"placement_source":{"type":"string","title":"Placement Source"}},"type":"object","required":["id","page","x","y","width","height","field_type","required","signer_id","placement_source"],"title":"FieldOut"},"FieldPatch":{"properties":{"page":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Page"},"x":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"X"},"y":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Y"},"width":{"anyOf":[{"type":"number","maximum":1.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Width"},"height":{"anyOf":[{"type":"number","maximum":1.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Height"},"field_type":{"anyOf":[{"$ref":"#/components/schemas/FieldType"},{"type":"null"}]},"required":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Required"},"signer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signer Id"}},"type":"object","title":"FieldPatch","description":"Partial edit of a draft field. Sending signer_id: null unassigns it;\ncombined-rect and page-range validation happens in the service, where the\nmerged result is known."},"FieldType":{"type":"string","enum":["signature","initials","date","text","checkbox"],"title":"FieldType"},"FromCheckoutIn":{"properties":{"session_id":{"type":"string","title":"Session Id"}},"type":"object","required":["session_id"],"title":"FromCheckoutIn"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InviteIn":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"role":{"type":"string","title":"Role","default":"member"}},"type":"object","required":["email"],"title":"InviteIn"},"MfaVerifyIn":{"properties":{"code":{"type":"string","title":"Code"}},"type":"object","required":["code"],"title":"MfaVerifyIn"},"OtpVerifyIn":{"properties":{"code":{"type":"string","title":"Code"}},"type":"object","required":["code"],"title":"OtpVerifyIn"},"ReceiptSessionIn":{"properties":{"confirm_download":{"type":"boolean","const":true,"title":"Confirm Download"},"artifact":{"type":"string","enum":["signed","certificate","evidence"],"title":"Artifact"}},"type":"object","required":["confirm_download","artifact"],"title":"ReceiptSessionIn","description":"Explicit user gesture required before completed evidence can be fetched."},"ReceiptSessionOut":{"properties":{"session_token":{"type":"string","title":"Session Token"},"expires_in_seconds":{"type":"integer","title":"Expires In Seconds"}},"type":"object","required":["session_token","expires_in_seconds"],"title":"ReceiptSessionOut"},"RegenerateCodesIn":{"properties":{"code":{"type":"string","title":"Code"}},"type":"object","required":["code"],"title":"RegenerateCodesIn"},"ReminderOut":{"properties":{"reminded":{"type":"integer","title":"Reminded","description":"Compatibility alias for provider_accepted; never a delivery or read receipt."},"failed":{"type":"integer","title":"Failed","description":"Authoritative permanent payload/content rejections."},"queued":{"type":"integer","title":"Queued","description":"Durable reminder rows committed with freshly rotated links."},"provider_accepted":{"type":"integer","title":"Provider Accepted","description":"Requests accepted by the email provider, not proven delivered."},"pending":{"type":"integer","title":"Pending","description":"Queued, leased, submitting or ambiguous provider outcomes."},"manual_review":{"type":"integer","title":"Manual Review","description":"Requests requiring controlled support reconciliation."},"skipped":{"type":"integer","title":"Skipped","description":"Rows cancelled before provider submission because the link became stale."},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id"}},"type":"object","required":["reminded","failed","queued","provider_accepted","pending","manual_review","skipped"],"title":"ReminderOut","description":"Durable reminder-queue and email-provider truth for one request."},"RequestLinkIn":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"RequestLinkIn"},"RiskOut":{"properties":{"severity":{"type":"string","title":"Severity"},"title":{"type":"string","title":"Title"},"detail":{"type":"string","title":"Detail"}},"type":"object","required":["severity","title","detail"],"title":"RiskOut"},"RoleChangeIn":{"properties":{"role":{"type":"string","title":"Role"}},"type":"object","required":["role"],"title":"RoleChangeIn"},"SaveAsTemplateIn":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"}},"type":"object","required":["name"],"title":"SaveAsTemplateIn"},"SessionOut":{"properties":{"session_token":{"type":"string","title":"Session Token"}},"type":"object","required":["session_token"],"title":"SessionOut"},"SignDocumentView":{"properties":{"document_title":{"type":"string","title":"Document Title"},"page_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page Count"},"fields":{"items":{"$ref":"#/components/schemas/FieldOut"},"type":"array","title":"Fields"},"ai_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Summary"},"consent_text_version":{"type":"string","title":"Consent Text Version"},"consent_text":{"type":"string","title":"Consent Text"}},"type":"object","required":["document_title","page_count","fields","consent_text_version","consent_text"],"title":"SignDocumentView","description":"Post-OTP view: the document metadata + this signer's own fields."},"SignIn":{"properties":{"field_values":{"additionalProperties":{"type":"string"},"type":"object","title":"Field Values","default":{}},"consent":{"type":"boolean","title":"Consent","default":false},"consent_text_version":{"type":"string","title":"Consent Text Version"}},"type":"object","required":["consent_text_version"],"title":"SignIn"},"SignResultOut":{"properties":{"status":{"$ref":"#/components/schemas/DocumentStatus"}},"type":"object","required":["status"],"title":"SignResultOut","description":"Committed ceremony state after a successful signing submission."},"SignerIn":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"email":{"type":"string","maxLength":320,"format":"email","title":"Email"},"role":{"$ref":"#/components/schemas/SignerRole","default":"signer"},"routing_index":{"type":"integer","maximum":2147483647.0,"minimum":0.0,"title":"Routing Index","default":0}},"type":"object","required":["name","email"],"title":"SignerIn"},"SignerOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"email":{"type":"string","title":"Email"},"role":{"type":"string","title":"Role"},"routing_index":{"type":"integer","title":"Routing Index"},"status":{"type":"string","title":"Status"}},"type":"object","required":["id","name","email","role","routing_index","status"],"title":"SignerOut"},"SignerRole":{"type":"string","enum":["signer","witness","approver","cc"],"title":"SignerRole"},"SignerViewOut":{"properties":{"document_title":{"type":"string","title":"Document Title"},"signer_name":{"type":"string","title":"Signer Name"},"organization":{"type":"string","title":"Organization"},"page_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page Count"},"status":{"type":"string","title":"Status"}},"type":"object","required":["document_title","signer_name","organization","page_count","status"],"title":"SignerViewOut","description":"Pre-OTP metadata shown when a signer opens their link (no PDF, no fields yet)."},"StartCheckoutIn":{"properties":{"tier":{"type":"string","title":"Tier"},"interval":{"type":"string","title":"Interval","default":"monthly"}},"type":"object","required":["tier"],"title":"StartCheckoutIn","description":"What the browser is allowed to choose: a tier and a billing interval.\n\nDeliberately nothing else. The organisation the checkout is for comes from\nthe session cookie and is stamped on the Stripe session server-side, so no\nrequest body can point a purchase at a workspace the caller is not in."},"TemplateOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"page_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page Count"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"signers":{"items":{"$ref":"#/components/schemas/TemplatePlaceholderOut"},"type":"array","title":"Signers","default":[]},"field_count":{"type":"integer","title":"Field Count","default":0},"damaged":{"type":"boolean","title":"Damaged","default":false}},"type":"object","required":["id","name","page_count","created_at"],"title":"TemplateOut"},"TemplatePlaceholderOut":{"properties":{"name":{"type":"string","title":"Name"},"email":{"type":"string","title":"Email"},"role":{"type":"string","title":"Role"},"routing_index":{"type":"integer","title":"Routing Index"}},"type":"object","required":["name","email","role","routing_index"],"title":"TemplatePlaceholderOut"},"UseTemplateIn":{"properties":{"title":{"type":"string","maxLength":500,"minLength":1,"title":"Title"},"signers":{"items":{"$ref":"#/components/schemas/UseTemplateSignerIn"},"type":"array","title":"Signers","default":[]}},"type":"object","required":["title"],"title":"UseTemplateIn"},"UseTemplateSignerIn":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"email":{"type":"string","maxLength":320,"format":"email","title":"Email"}},"type":"object","required":["name","email"],"title":"UseTemplateSignerIn"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerifyIn":{"properties":{"token":{"type":"string","title":"Token"}},"type":"object","required":["token"],"title":"VerifyIn"},"VoidIn":{"properties":{"reason":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Reason"}},"type":"object","title":"VoidIn"},"VoidOut":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"status":{"type":"string","title":"Status"},"original_sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Original Sha256"},"signed_sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed Sha256"},"page_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page Count"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"original_storage_recorded":{"type":"boolean","title":"Original Storage Recorded","default":false},"corrected_from_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Corrected From Document Id"},"corrected_by_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Corrected By Document Id"},"has_correction_lineage":{"type":"boolean","title":"Has Correction Lineage","default":false},"correction_consumed":{"type":"boolean","title":"Correction Consumed","default":false},"signers":{"items":{"$ref":"#/components/schemas/SignerOut"},"type":"array","title":"Signers","default":[]},"fields":{"items":{"$ref":"#/components/schemas/FieldOut"},"type":"array","title":"Fields","default":[]},"voided_now":{"type":"boolean","title":"Voided Now","default":true},"provider_accepted":{"type":"integer","title":"Provider Accepted","default":0},"notify_permanent_failed":{"type":"integer","title":"Notify Permanent Failed","default":0},"notify_pending":{"type":"integer","title":"Notify Pending","default":0},"notify_manual_review":{"type":"integer","title":"Notify Manual Review","default":0},"notify_outcome_unknown":{"type":"integer","title":"Notify Outcome Unknown","default":0},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning"}},"type":"object","required":["id","title","status","original_sha256","page_count","created_at"],"title":"VoidOut","description":"A void plus durable cancellation-notice delivery truth."}}}}