openapi: 3.1.0
info:
  title: Abundera Sign API
  version: '1.0'
  description: 'Self-hosted e-signature platform with cryptographic proof. Template-driven document signing

    with hash-chained audit trails, RFC 3161 timestamps, AI contract summaries, and public verification.


    ## Authentication


    Three authentication modes are used depending on the endpoint:


    1. **JWT Bearer** (`Authorization: Bearer <token>`) — For API consumers. JWTs are verified against the Abundera JWKS endpoint
    at `https://abundera.ai/v1/auth/jwks`. Used by envelope management, template, and collaboration endpoints.

    2. **Signing Token** (query parameter `?token=...`) — 256-bit CSPRNG tokens issued per signer. Used by signing pages,
    document download, decline, and OTP endpoints. Tokens are stored as SHA-256 hashes (never raw).

    3. **Public** — No authentication required. Used by document verification and health check endpoints.


    ## Rate Limiting


    All endpoints are rate limited via KV-backed counters. Limits vary by endpoint:

    - Envelope creation: tier-based (Free: 5/mo, Pro: 100/mo, Business: 500/mo)

    - OTP send: 3 per 10 minutes per token

    - General API: 60 requests/minute per IP


    ## Base URL


    All API paths use the `/api/v1/` prefix. Example: `POST https://sign.abundera.ai/api/v1/envelopes`


    ## Webhooks


    When a `callback_url` is provided during envelope creation, events are delivered via POST with HMAC-SHA256 signatures.
    Verify using the `callback_secret` returned in the creation response. Header: `X-Signature: sha256=<hex>`.


    **Webhook event types:**

    - `signer.viewed` — Signer opened the signing page

    - `signer.signed` — Signer completed signing

    - `signer.declined` — Signer declined to sign

    - `signer.delegated` — Signer delegated signing to another person

    - `envelope.completed` — All signers signed, envelope sealed

    - `envelope.voided` — Envelope voided by sender

    - `envelope.expired` — Envelope expired (auto-voided by cron)

    - `payment.completed` — Pay-to-sign Stripe payment completed

    - `clause.flagged` — Signer flagged a clause for negotiation

    - `clause.responded` — Sender responded to a clause flag

    - `clause.accepted` — Signer accepted sender''s proposed changes

    - `clause.counter_proposed` — Signer submitted a counter-proposal


    ## Evidence Package


    Completed envelopes produce a tamper-evident evidence package containing:

    - Hash-chained audit trail (SHA-256, each entry includes previous hash)

    - RFC 3161 timestamp from a trusted TSA

    - GitHub-anchored manifest hash (public commit proof)

    - Certificate of Completion PDF'
  contact:
    name: Abundera Engineering
    email: security@abundera.ai
    url: https://sign.abundera.ai
  termsOfService: https://abundera.ai/terms
  license:
    name: Proprietary
    url: https://abundera.ai/terms
  x-logo:
    url: https://abundera.ai/img/logo.svg
    altText: Abundera logo
    backgroundColor: '#0f172a'
externalDocs:
  description: Developer documentation
  url: https://sign.abundera.ai/docs/
servers:
- url: https://sign.abundera.ai
  description: Production
tags:
- name: Envelopes
  description: Create, list, retrieve, void, and check status of signing envelopes
- name: Signers
  description: Manage signers — send reminders, resend invitations, handle declines
- name: Documents
  description: Download signed PDFs, verify document integrity, get AI summaries
- name: Templates
  description: Manage document templates (JSON-defined, no GUI builder)
- name: Organizations
  description: Create and manage organizations for team collaboration (Business tier)
- name: Collaboration
  description: Comments and hash-chained audit trails on envelopes
- name: Verification
  description: SMS OTP verification for signers with phone numbers on file
- name: System
  description: Health checks and infrastructure status
- name: Authentication
  description: User session and token management
- name: Branding
  description: White-label branding configuration (Business tier)
- name: Clauses
  description: Reusable clause library for templates (Business tier)
- name: Declarations
  description: Court-ready evidence declarations
- name: Identity
  description: Government ID verification and knowledge-based authentication
- name: Negotiation
  description: Clause-level negotiation between signers and senders
- name: Packages
  description: Multi-document signing packages
- name: Payments
  description: Pay-to-sign Stripe checkout
- name: Signing Links
  description: Reusable signing links (Professional+ tier)
- name: Signer Profile
  description: Opt-in auto-fill for returning signers
- name: Notarization
  description: Remote online notarization via BlueNotary (Business tier)
- name: Cron
  description: Internal scheduled tasks (CRON_SECRET protected)
  x-internal: true
x-tagGroups:
- name: Signing Flow
  tags:
  - Envelopes
  - Signers
  - Signing Links
  - Packages
- name: Documents & Templates
  tags:
  - Documents
  - Templates
  - Clauses
- name: Identity & Verification
  tags:
  - Verification
  - Identity
  - Signer Profile
- name: Negotiation & Collaboration
  tags:
  - Negotiation
  - Collaboration
  - Declarations
- name: Team Management
  tags:
  - Organizations
  - Branding
- name: Payments
  tags:
  - Payments
- name: Auth & Infrastructure
  tags:
  - Authentication
  - System
- name: Internal
  tags:
  - Cron
security:
- bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT verified against https://abundera.ai/v1/auth/jwks
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
      required:
      - error
    Signer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string
        role:
          type: string
        status:
          type: string
          enum:
          - pending
          - sent
          - viewed
          - signed
          - declined
          - waiting
          - delegated
        signed_at:
          type: string
          format: date-time
          nullable: true
        signing_order:
          type: integer
        has_phone:
          type: boolean
        phone_verified:
          type: boolean
        reminder_count:
          type: integer
        decline_reason:
          type: string
          nullable: true
    Envelope:
      type: object
      properties:
        id:
          type: string
          format: uuid
        template_id:
          type: string
        template_name:
          type: string
        status:
          type: string
          enum:
          - sent
          - completed
          - voided
        sender_email:
          type: string
          format: email
        sender_name:
          type: string
        created_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
          nullable: true
        expires_at:
          type: string
          format: date-time
        retention_years:
          type: integer
        has_access_code:
          type: boolean
        sign_order:
          type: boolean
        signers:
          type: array
          items:
            $ref: '#/components/schemas/Signer'
        audit_event_count:
          type: integer
        comment_count:
          type: integer
        negotiation_counts:
          type: object
          nullable: true
          description: Clause negotiation counts by status (present when > 0)
          properties:
            open:
              type: integer
            resolved:
              type: integer
            withdrawn:
              type: integer
    SignerInput:
      type: object
      required:
      - email
      - name
      - role
      properties:
        email:
          type: string
          format: email
        name:
          type: string
        role:
          type: string
          description: Must match a role defined in the template
        phone:
          type: string
          description: E.164 format for SMS OTP verification
    Verification:
      type: object
      properties:
        verified:
          type: boolean
        envelope:
          type: object
          properties:
            id:
              type: string
            status:
              type: string
            completed_at:
              type: string
              format: date-time
        signers:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              email:
                type: string
                description: Privacy-masked (e.g., j***@example.com)
              signed_at:
                type: string
        integrity:
          type: object
          properties:
            audit_chain_valid:
              type: boolean
            manifest_hash:
              type: string
              description: SHA-256 hash of the evidence manifest
            github_anchor:
              type: object
              nullable: true
              properties:
                commit_sha:
                  type: string
                url:
                  type: string
                  format: uri
            rfc3161_timestamp:
              type: object
              nullable: true
              properties:
                tsa_url:
                  type: string
                  format: uri
                requested_at:
                  type: string
                  format: date-time
    HealthCheck:
      type: object
      properties:
        status:
          type: string
          enum:
          - healthy
          - degraded
        checks:
          type: object
          properties:
            d1:
              type: string
              enum:
              - ok
              - error
            kv:
              type: string
              enum:
              - ok
              - error
            r2:
              type: string
              enum:
              - ok
              - error
        timestamp:
          type: string
          format: date-time
paths:
  /api/v1/envelopes:
    post:
      summary: Create Envelope
      description: Create a new signing envelope from a template. Sends signing invitation emails to all signers (or first
        signer if sequential signing is enabled). Returns signer IDs and a callback secret for webhook verification.
      operationId: createEnvelope
      tags:
      - Envelopes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - template
              - signers
              properties:
                template:
                  type: string
                  description: Template ID (must exist in template registry)
                signers:
                  type: array
                  items:
                    $ref: '#/components/schemas/SignerInput'
                  minItems: 1
                  description: At least one signer required
                fields:
                  type: object
                  additionalProperties: true
                  description: Pre-filled field values keyed by field name
                callback_url:
                  type: string
                  format: uri
                  description: Webhook URL for envelope events (signed, completed, declined)
                metadata:
                  type: object
                  additionalProperties: true
                  description: Arbitrary metadata stored with the envelope
                watermark:
                  type: string
                  description: Watermark text rendered on each page
                footer:
                  type: object
                  properties:
                    left:
                      type: string
                    center:
                      type: string
                    right:
                      type: string
                    rule:
                      type: boolean
                  description: Custom footer with left/center/right text and optional rule line
                sign_order:
                  type: boolean
                  default: false
                  description: Enable sequential signing (signers sign in order)
                expiry_days:
                  type: integer
                  default: 30
                  maximum: 365
                  description: Days until envelope expires and is auto-voided
                retention_years:
                  type: integer
                  default: 3
                  maximum: 99
                  description: Years to retain the signed document in R2 storage
                reminder_days:
                  type: integer
                  default: 3
                  maximum: 30
                  description: Days between automatic reminders
                max_reminders:
                  type: integer
                  default: 3
                  maximum: 10
                  description: Maximum automatic reminders per signer
                access_code:
                  type: string
                  minLength: 4
                  description: PIN code required to view the document (stored as SHA-256 hash)
                geo_lock:
                  type: array
                  items:
                    type: string
                    pattern: ^[A-Z]{2}$
                  description: ISO 3166-1 alpha-2 country codes. Signers must be in one of these countries.
                  example:
                  - US
                  - CA
                block_vpn:
                  type: boolean
                  default: false
                  description: Block signing from VPN, proxy, or Tor networks
                source:
                  type: string
                  enum:
                  - self_service
                  - abundera
                  - api
                  default: self_service
                  description: Source channel for analytics
                org_id:
                  type: string
                  format: uuid
                  description: Organization ID to scope this envelope to a shared workspace (Business tier)
      responses:
        '200':
          description: Envelope created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  status:
                    type: string
                  template_id:
                    type: string
                  expires_at:
                    type: string
                    format: date-time
                  signers:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        email:
                          type: string
                        name:
                          type: string
                        role:
                          type: string
                        status:
                          type: string
                  callback_secret:
                    type: string
                    description: HMAC secret for verifying webhook payloads
        '400':
          description: Validation error (missing fields, invalid template, role mismatch)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Tier envelope limit exceeded
    get:
      summary: List Envelopes
      description: List envelopes for the authenticated user with pagination and optional filters.
      operationId: listEnvelopes
      tags:
      - Envelopes
      parameters:
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: limit
        in: query
        schema:
          type: integer
          default: 20
          maximum: 100
      - name: status
        in: query
        schema:
          type: string
          enum:
          - sent
          - completed
          - voided
      - name: template
        in: query
        schema:
          type: string
        description: Filter by template ID
      - name: org_id
        in: query
        schema:
          type: string
          format: uuid
        description: Filter by organization (returns org envelopes instead of personal)
      responses:
        '200':
          description: Paginated envelope list
  /api/v1/envelopes/bulk:
    post:
      summary: Bulk Create Envelopes
      description: Send the same template to multiple recipients in one call. Each recipient gets their own envelope. Max
        100 recipients per request. Only markdown-based templates are supported.
      operationId: bulkCreateEnvelopes
      tags:
      - Envelopes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - template
              - recipients
              properties:
                template:
                  type: string
                  description: Template ID (must be a markdown template)
                recipients:
                  type: array
                  maxItems: 100
                  items:
                    type: object
                    required:
                    - email
                    - name
                    - role
                    properties:
                      email:
                        type: string
                        format: email
                      name:
                        type: string
                      role:
                        type: string
                      phone:
                        type: string
                      fields:
                        type: object
                        additionalProperties: true
                        description: Recipient-specific field overrides
                sender_role:
                  type: string
                  default: party_a
                  description: Sender's role in the template
                shared_fields:
                  type: object
                  additionalProperties: true
                  description: Fields shared across all envelopes
                sign_order:
                  type: boolean
                  default: false
                watermark:
                  type: string
                footer:
                  type: object
                  properties:
                    left:
                      type: string
                    center:
                      type: string
                    right:
                      type: string
                    rule:
                      type: boolean
                expiry_days:
                  type: integer
                  default: 30
                  maximum: 365
                retention_years:
                  type: integer
                  default: 3
                  maximum: 99
                reminder_days:
                  type: integer
                  default: 3
                  maximum: 30
                max_reminders:
                  type: integer
                  default: 3
                  maximum: 10
                callback_url:
                  type: string
                  format: uri
                access_code:
                  type: string
                  minLength: 4
                geo_lock:
                  type: array
                  items:
                    type: string
                    pattern: ^[A-Z]{2}$
                  description: ISO 3166-1 alpha-2 country codes for signing location restriction
                block_vpn:
                  type: boolean
                  default: false
                  description: Block signing from VPN/proxy/Tor
                org_id:
                  type: string
                  format: uuid
                  description: Organization ID for shared workspace
      responses:
        '201':
          description: Bulk creation results
          content:
            application/json:
              schema:
                type: object
                properties:
                  created:
                    type: integer
                  failed:
                    type: integer
                  envelopes:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        recipient:
                          type: string
                          format: email
                        status:
                          type: string
                          enum:
                          - sent
                          - failed
                        error:
                          type: string
        '400':
          description: Validation error or recipients exceed 100
  /api/v1/envelopes/{id}:
    get:
      summary: Get Envelope
      description: Retrieve full envelope details including all signers, audit event count, and comment count.
      operationId: getEnvelope
      tags:
      - Envelopes
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Full envelope details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Envelope'
        '404':
          description: Envelope not found
  /api/v1/envelope-status:
    get:
      summary: Envelope Status
      description: Check envelope status. Supports JWT auth, signing token, or demo mode. Used by the signing page to show
        real-time status.
      operationId: getEnvelopeStatus
      tags:
      - Envelopes
      security: []
      parameters:
      - name: id
        in: query
        schema:
          type: string
      - name: token
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Envelope status with signer details
  /api/v1/void-envelope:
    post:
      summary: Void Envelope
      description: Void an active envelope. All pending signers are notified via email. Cannot void already-completed envelopes.
      operationId: voidEnvelope
      tags:
      - Envelopes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - envelope_id
              properties:
                envelope_id:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Envelope voided, signers notified
        '400':
          description: Cannot void a completed envelope
  /api/v1/remind:
    post:
      summary: Send Reminder
      description: Send a reminder email to pending signers. Can target a specific signer or all pending signers on the envelope.
      operationId: sendReminder
      tags:
      - Signers
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - envelope_id
              properties:
                envelope_id:
                  type: string
                  format: uuid
                signer_id:
                  type: string
                  format: uuid
                  description: Optional — omit to remind all pending signers
      responses:
        '200':
          description: Reminder(s) sent with fresh signing tokens
  /api/v1/resend-signer:
    post:
      summary: Resend Invitation
      description: Resend the signing invitation to a specific signer. Optionally update their email address (generates a
        new token for the new address).
      operationId: resendSigner
      tags:
      - Signers
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - envelope_id
              - signer_id
              properties:
                envelope_id:
                  type: string
                  format: uuid
                signer_id:
                  type: string
                  format: uuid
                new_email:
                  type: string
                  format: email
                  description: New email address (replaces existing)
      responses:
        '200':
          description: Invitation resent
  /api/v1/decline:
    post:
      summary: Decline to Sign
      description: Decline to sign an envelope. Requires the signing token and a reason (max 1000 chars). The sender is notified
        via email with the decline reason.
      operationId: declineToSign
      tags:
      - Signers
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - reason
              properties:
                token:
                  type: string
                  description: Signing token from the invitation URL
                reason:
                  type: string
                  maxLength: 1000
                  description: Reason for declining
      responses:
        '200':
          description: Declined successfully, sender notified
        '409':
          description: Signer has already signed or declined
  /api/v1/download:
    get:
      summary: Download Signed PDF
      description: Download the completed signed PDF. Requires either JWT auth (sender) or a download token (signer). Returns
        the binary PDF from R2 storage.
      operationId: downloadPdf
      tags:
      - Documents
      security: []
      parameters:
      - name: envelope
        in: query
        required: true
        schema:
          type: string
          format: uuid
      - name: token
        in: query
        schema:
          type: string
        description: Download token (for signer access without JWT)
      responses:
        '200':
          description: Signed PDF binary
          content:
            application/pdf: {}
        '403':
          description: Unauthorized — invalid token or not the sender
  /api/v1/verify:
    get:
      summary: Verify Document
      description: Publicly verify a completed document's integrity. Checks the hash-chained audit trail, manifest hash, GitHub
        anchor, and RFC 3161 timestamp. No authentication required.
      operationId: verifyDocument
      tags:
      - Documents
      security: []
      parameters:
      - name: id
        in: query
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Verification result with integrity details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Verification'
  /api/v1/document-data:
    get:
      summary: Get Document Data
      description: Retrieve the rendered document HTML and field definitions for the signing page. Enforces access code and
        OTP gates if configured. Returns 403 if phone verification is required, 409 if already signed, 410 if voided/expired.
      operationId: getDocumentData
      tags:
      - Documents
      security: []
      parameters:
      - name: token
        in: query
        required: true
        schema:
          type: string
          minLength: 32
        description: Signing token
      - name: access_code
        in: query
        schema:
          type: string
        description: PIN code if envelope requires one
      responses:
        '200':
          description: Document HTML, fields, and signer context
          content:
            application/json:
              schema:
                type: object
                properties:
                  html:
                    type: string
                    description: Rendered document HTML
                  fields:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        type:
                          type: string
                          enum:
                          - text
                          - textarea
                          - select
                          - signature
                          - autodate
                          - date
                          - number
                          - email
                          - checkbox
                        required:
                          type: boolean
                        label:
                          type: string
                        options:
                          type: array
                          items:
                            type: string
                  signer_role:
                    type: string
                  signer_name:
                    type: string
                  template_name:
                    type: string
                  envelope_id:
                    type: string
                  features:
                    type: object
                    properties:
                      ai_summary:
                        type: boolean
        '403':
          description: Access code required, invalid access code, or phone verification required
        '409':
          description: Already signed
        '410':
          description: Document voided or link expired
  /api/v1/document-summary:
    get:
      summary: AI Contract Summary
      description: Get an AI-generated plain-language summary of the document. Powered by Workers AI — data never leaves our infrastructure. Results are cached in KV
        for subsequent requests.
      operationId: getDocumentSummary
      tags:
      - Documents
      security: []
      parameters:
      - name: token
        in: query
        required: true
        schema:
          type: string
          minLength: 32
        description: Signing token
      responses:
        '200':
          description: AI-generated contract summary
  /api/v1/templates:
    get:
      summary: List Templates
      description: List all available document templates. Supports filtering by category, tag, or search term. Returns 311+
        built-in templates.
      operationId: listTemplates
      tags:
      - Templates
      parameters:
      - name: category
        in: query
        schema:
          type: string
        description: Filter by category (e.g. "Real Estate", "Employment & HR")
      - name: tag
        in: query
        schema:
          type: string
        description: Filter by tag (e.g. "single-signer", "healthcare", "agreement")
      - name: search
        in: query
        schema:
          type: string
        description: Search template names and descriptions
      - name: org_id
        in: query
        schema:
          type: string
          format: uuid
        description: Include org-specific templates alongside global ones
      responses:
        '200':
          description: Array of templates with IDs, names, categories, and tags
    post:
      summary: Create Template
      description: Create a new document template. Templates are JSON-defined with markdown content, roles, and field definitions.
      operationId: createTemplate
      tags:
      - Templates
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - id
              - name
              properties:
                id:
                  type: string
                  description: Unique template identifier (slug format)
                name:
                  type: string
                  description: Human-readable template name
                description:
                  type: string
                roles:
                  type: array
                  items:
                    type: string
                  description: Signer roles this template expects
                markdown:
                  type: string
                  description: Template body in markdown with field placeholders
                metadata:
                  type: object
                  additionalProperties: true
                org_id:
                  type: string
                  format: uuid
                  description: Create as an org-specific template (requires admin+ role)
      responses:
        '201':
          description: Template created
        '409':
          description: Template ID already exists
  /api/v1/templates/{id}:
    get:
      summary: Get Template
      description: Retrieve a single template by ID, including full markdown content and metadata.
      operationId: getTemplate
      tags:
      - Templates
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Template details
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  description:
                    type: string
                  version:
                    type: integer
                  fields:
                    type: array
                    items:
                      type: object
                  markdown:
                    type: string
                  metadata:
                    type: object
                  created_at:
                    type: string
                    format: date-time
                  updated_at:
                    type: string
                    format: date-time
        '404':
          description: Template not found
    put:
      summary: Update Template
      description: Update an existing template's name, description, fields, markdown, or metadata.
      operationId: updateTemplate
      tags:
      - Templates
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                fields:
                  type: array
                  items:
                    type: object
                markdown:
                  type: string
                metadata:
                  type: object
                  additionalProperties: true
      responses:
        '200':
          description: Template updated
        '404':
          description: Template not found
    delete:
      summary: Delete Template
      description: Delete a template. Fails if the template has active (non-voided) envelopes.
      operationId: deleteTemplate
      tags:
      - Templates
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Template deleted
        '404':
          description: Template not found
        '409':
          description: Template has active envelopes
  /api/v1/demo-envelope:
    get:
      summary: List Demo Templates
      description: List available templates for demo envelope creation. No authentication required.
      operationId: listDemoTemplates
      tags:
      - Envelopes
      security: []
      responses:
        '200':
          description: Available demo templates
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  templates:
                    type: array
                    items:
                      type: string
    post:
      summary: Create Demo Envelope
      description: Create a demo envelope for testing. No authentication required. Returns signing URLs for immediate use.
        Limited to 7-day expiry (max 90 days).
      operationId: createDemoEnvelope
      tags:
      - Envelopes
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                template:
                  type: string
                  default: demo
                name:
                  type: string
                  default: Test Signer
                email:
                  type: string
                  format: email
                  default: test@example.com
                fields:
                  type: object
                  additionalProperties: true
                signers:
                  type: array
                  items:
                    type: object
                    properties:
                      name:
                        type: string
                      email:
                        type: string
                        format: email
                      role:
                        type: string
                sign_order:
                  type: boolean
                sender_name:
                  type: string
                expiry_days:
                  type: integer
                  default: 7
                  maximum: 90
      responses:
        '201':
          description: Demo envelope created with signing URLs
          content:
            application/json:
              schema:
                type: object
                properties:
                  envelope_id:
                    type: string
                    format: uuid
                  template:
                    type: string
                  template_name:
                    type: string
                  signers:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        name:
                          type: string
                        email:
                          type: string
                        role:
                          type: string
                        status:
                          type: string
                        signing_url:
                          type: string
                          format: uri
                  expires_at:
                    type: string
                    format: date-time
        '404':
          description: Template not found
  /api/v1/webhooks:
    post:
      summary: Inbound Webhook
      description: Receives webhook events from email providers (delivery, bounce, failure). Verified via HMAC-SHA256 signature
        in the X-Signature-256 header using WEBHOOK_SECRET.
      operationId: receiveWebhook
      tags:
      - System
      security: []
      parameters:
      - name: X-Signature-256
        in: header
        required: true
        schema:
          type: string
        description: HMAC-SHA256 signature of request body
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - event
              - data
              properties:
                event:
                  type: string
                  enum:
                  - email.delivered
                  - email.bounced
                  - email.failed
                data:
                  type: object
                  additionalProperties: true
      responses:
        '200':
          description: Webhook received
        '401':
          description: Invalid or missing signature
  /api/v1/envelopes/{id}/comments:
    get:
      summary: List Comments
      description: List all comments on an envelope. Requires JWT auth (sender only).
      operationId: listComments
      tags:
      - Collaboration
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Array of comments with author, text, and timestamp
    post:
      summary: Add Comment
      description: Add a comment to an envelope. Comments are visible to the sender only (not signers).
      operationId: addComment
      tags:
      - Collaboration
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - comment
              properties:
                comment:
                  type: string
                  maxLength: 2000
      responses:
        '201':
          description: Comment added
  /api/v1/envelopes/{id}/audit:
    get:
      summary: Audit Trail
      description: Retrieve the hash-chained audit trail for an envelope. Each event includes a SHA-256 hash that incorporates
        the previous event's hash, creating a tamper-evident chain.
      operationId: getAuditTrail
      tags:
      - Collaboration
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Ordered array of audit events with hash chain
  /api/v1/otp/send:
    post:
      summary: Send OTP
      description: Send a 6-digit SMS OTP to the signer's phone number. Rate limited to 3 attempts per 10 minutes.
      operationId: sendOtp
      tags:
      - Verification
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              properties:
                token:
                  type: string
                  description: Signing token
      responses:
        '200':
          description: OTP sent to signer's phone
        '429':
          description: Rate limited — too many OTP requests
  /api/v1/otp/verify:
    post:
      summary: Verify OTP
      description: Verify the 6-digit OTP code. On success, the signer's phone_verified flag is set. After 5 failed attempts,
        the OTP is invalidated.
      operationId: verifyOtp
      tags:
      - Verification
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - code
              properties:
                token:
                  type: string
                  description: Signing token
                code:
                  type: string
                  pattern: ^\d{6}$
                  description: 6-digit OTP code
      responses:
        '200':
          description: Verification result (success or failure)
        '429':
          description: Too many failed attempts — OTP invalidated
  /api/v1/orgs:
    post:
      summary: Create Organization
      description: Create a new organization. The creator becomes the owner. Requires Business tier.
      operationId: createOrg
      tags:
      - Organizations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              properties:
                name:
                  type: string
                  maxLength: 100
                  description: Organization name
      responses:
        '201':
          description: Organization created
        '403':
          description: Business tier required
    get:
      summary: List Organizations
      description: List all organizations the authenticated user belongs to.
      operationId: listOrgs
      tags:
      - Organizations
      responses:
        '200':
          description: Array of organizations with user's role in each
  /api/v1/orgs/{id}:
    get:
      summary: Get Organization
      description: Get organization details including member count. Requires viewer+ role.
      operationId: getOrg
      tags:
      - Organizations
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Organization details
        '403':
          description: Not a member
    put:
      summary: Update Organization
      description: Update organization name. Requires admin+ role.
      operationId: updateOrg
      tags:
      - Organizations
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              properties:
                name:
                  type: string
                  maxLength: 100
      responses:
        '200':
          description: Organization updated
        '403':
          description: Admin role required
    delete:
      summary: Delete Organization
      description: Delete an organization. Requires owner role. Fails if org has active envelopes.
      operationId: deleteOrg
      tags:
      - Organizations
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Organization deleted
        '403':
          description: Owner role required
        '409':
          description: Organization has active envelopes
  /api/v1/orgs/{id}/members:
    get:
      summary: List Members
      description: List all members of an organization. Requires viewer+ role.
      operationId: listOrgMembers
      tags:
      - Organizations
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Array of members with roles
  /api/v1/orgs/{id}/members/{uid}:
    put:
      summary: Change Member Role
      description: Change a member's role. Requires admin+ role. Cannot change owner's role.
      operationId: updateOrgMember
      tags:
      - Organizations
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: uid
        in: path
        required: true
        schema:
          type: string
        description: Target user's ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - role
              properties:
                role:
                  type: string
                  enum:
                  - viewer
                  - member
                  - admin
      responses:
        '200':
          description: Role updated
        '403':
          description: Insufficient permissions
    delete:
      summary: Remove Member
      description: Remove a member from the organization. Requires admin+ role. Cannot remove the owner.
      operationId: removeOrgMember
      tags:
      - Organizations
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: uid
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Member removed
        '403':
          description: Cannot remove owner or insufficient permissions
  /api/v1/orgs/{id}/invitations:
    post:
      summary: Invite Member
      description: Send an email invitation to join the organization. Requires admin+ role. Invitation expires in 7 days.
      operationId: createOrgInvitation
      tags:
      - Organizations
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              properties:
                email:
                  type: string
                  format: email
                role:
                  type: string
                  enum:
                  - viewer
                  - member
                  - admin
                  default: member
      responses:
        '201':
          description: Invitation sent
        '409':
          description: User already a member or invitation already pending
    get:
      summary: List Invitations
      description: List all invitations for the organization. Requires admin+ role.
      operationId: listOrgInvitations
      tags:
      - Organizations
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Array of invitations with status
  /api/v1/orgs/{id}/invitations/{iid}:
    delete:
      summary: Revoke Invitation
      description: Revoke a pending invitation. Requires admin+ role.
      operationId: revokeOrgInvitation
      tags:
      - Organizations
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: iid
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Invitation revoked
        '400':
          description: Invitation is not pending
  /api/v1/orgs/accept-invite:
    post:
      summary: Accept Invitation
      description: Accept an organization invitation using the token from the invitation email. Email must match the invitation.
      operationId: acceptOrgInvitation
      tags:
      - Organizations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              properties:
                token:
                  type: string
                  description: Invitation token from the email link
      responses:
        '200':
          description: Invitation accepted, user added as member
        '403':
          description: Email mismatch
        '410':
          description: Invitation expired
  /api/v1/health:
    get:
      summary: Health Check
      description: Check platform health. Verifies D1 database, KV store, and R2 bucket connectivity. Returns 503 if any check
        fails.
      operationId: healthCheck
      tags:
      - System
      security: []
      responses:
        '200':
          description: All systems healthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthCheck'
        '503':
          description: One or more systems degraded
  /api/v1/auth/me:
    get:
      summary: Current User Info
      description: 'Returns the authenticated user''s info, sign tier, and current month usage. Reads JWT from cookie or Authorization
        header. Returns { authenticated: false } if not logged in.'
      operationId: getCurrentUser
      tags:
      - Authentication
      security: []
      responses:
        '200':
          description: User info with sign tier and usage
          content:
            application/json:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  id:
                    type: string
                  email:
                    type: string
                    format: email
                  name:
                    type: string
                  role:
                    type: string
                  sign_tier:
                    type: string
                    enum:
                    - starter
                    - professional
                    - business
                  envelope_limit:
                    type: integer
                  envelopes_used:
                    type: integer
                  year_month:
                    type: string
                    description: Current billing period (YYYY-MM)
                  language:
                    type: string
  /api/v1/auth/refresh:
    post:
      summary: Refresh Token
      description: Proxy token refresh to Abundera auth service. Accepts a refresh_token and returns new access/refresh tokens.
      operationId: refreshToken
      tags:
      - Authentication
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - refresh_token
              properties:
                refresh_token:
                  type: string
      responses:
        '200':
          description: New tokens
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token:
                    type: string
                  refresh_token:
                    type: string
                  expires_in:
                    type: integer
        '400':
          description: Missing or invalid refresh token
        '504':
          description: Auth service timeout
  /api/v1/archive-envelope:
    post:
      summary: Archive Envelope
      description: Archive or unarchive a completed or voided envelope. Only the sender or org member with member+ role can
        archive.
      operationId: archiveEnvelope
      tags:
      - Envelopes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - envelope_id
              - action
              properties:
                envelope_id:
                  type: string
                  format: uuid
                action:
                  type: string
                  enum:
                  - archive
                  - unarchive
      responses:
        '200':
          description: Envelope archived or unarchived
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  status:
                    type: string
                  archived_at:
                    type: string
                    format: date-time
                    nullable: true
        '400':
          description: Cannot archive envelope with this status or already archived/not archived
        '404':
          description: Envelope not found
  /api/v1/delegate:
    post:
      summary: Delegate Signing
      description: Delegate signing responsibility to another person. Token-based auth. Chain limit of 2 delegations per signer
        position. The original signer is marked as delegated and the delegate receives a signing invitation email.
      operationId: delegateSigning
      tags:
      - Signers
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - delegate_email
              - delegate_name
              properties:
                token:
                  type: string
                  description: Signing token
                delegate_email:
                  type: string
                  format: email
                delegate_name:
                  type: string
                  maxLength: 200
                reason:
                  type: string
                  maxLength: 1000
                  description: Optional reason for delegation
      responses:
        '200':
          description: Signing delegated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                    - delegated
                  delegate_email:
                    type: string
                  delegate_name:
                    type: string
                  envelope_id:
                    type: string
                    format: uuid
        '400':
          description: Validation error (self-delegation, chain limit, existing signer, etc.)
        '403':
          description: Delegation not allowed for this document
        '409':
          description: Already signed, declined, or delegated
        '410':
          description: Document voided, completed, or expired
  /api/v1/envelopes/{id}/declaration:
    get:
      summary: Court-Ready Declaration
      description: Generate a court-ready evidence declaration PDF for a completed envelope. Professional+ tier required.
        Returns a binary PDF.
      operationId: getDeclaration
      tags:
      - Declarations
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Declaration PDF
          content:
            application/pdf: {}
        '400':
          description: Envelope not completed
        '404':
          description: Envelope not found
  /api/v1/sign:
    post:
      summary: Submit Signature
      description: Submit a signature for a document. Token-based auth. Accepts field values, signature data (draw/type/upload),
        consent flag, and optional viewing metrics, identity photos, audio/video recordings, and file attachments.
      operationId: submitSignature
      tags:
      - Signers
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - field_values
              - signature_type
              - consent
              properties:
                token:
                  type: string
                  description: Signing token
                field_values:
                  type: object
                  additionalProperties: true
                  description: Field name to value mapping
                signature_data:
                  type: string
                  description: Base64-encoded signature image (draw/upload) or typed name
                signature_type:
                  type: string
                  enum:
                  - draw
                  - type
                  - typed
                  - upload
                  - none
                consent:
                  type: boolean
                  description: ESIGN Act consent acknowledgment
                viewing_duration:
                  type: number
                  description: Time spent viewing document (ms)
                scroll_depth:
                  type: number
                  description: Maximum scroll depth percentage (0-100)
                identity_photo:
                  type: string
                  description: Base64-encoded identity photo
                audio_recording:
                  type: string
                  description: Base64-encoded audio statement (WebM/MP4)
                video_recording:
                  type: string
                  description: Base64-encoded video statement (WebM/MP4)
                attachments:
                  type: array
                  maxItems: 5
                  items:
                    type: object
                    properties:
                      filename:
                        type: string
                      mime_type:
                        type: string
                      data:
                        type: string
                        description: Base64-encoded file data
                passkey_verified:
                  type: boolean
                  description: Whether the signer verified via passkey before signing
                passkey_permission:
                  type: string
                  enum:
                  - sign
                  - view
                  description: Permission level granted by passkey verification
                has_client_signature:
                  type: boolean
                  description: Whether client-side cryptographic signature is included
                client_signature:
                  type: string
                  description: Base64-encoded client-side ECDSA signature over field values hash
      responses:
        '200':
          description: Signature submitted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  envelope_status:
                    type: string
                    description: Overall envelope status (may be 'completed' if last signer)
        '400':
          description: Validation error (missing consent, invalid signature, etc.)
        '409':
          description: Already signed or declined
        '410':
          description: Document voided or expired
  /api/v1/branding:
    get:
      summary: Get Branding
      description: Get the authenticated user's white-label branding configuration. Business tier required.
      operationId: getBranding
      tags:
      - Branding
      responses:
        '200':
          description: Branding configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  company_name:
                    type: string
                  logo_url:
                    type: string
                    format: uri
                  primary_color:
                    type: string
                    pattern: ^#[0-9a-fA-F]{6}$
                  accent_color:
                    type: string
                    pattern: ^#[0-9a-fA-F]{6}$
                  custom_domain:
                    type: string
                  email_from_name:
                    type: string
                  footer_text:
                    type: string
                  configured:
                    type: boolean
                    description: false if no branding configured yet
    put:
      summary: Update Branding
      description: Create or update white-label branding configuration. Business tier required.
      operationId: updateBranding
      tags:
      - Branding
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                company_name:
                  type: string
                  maxLength: 100
                logo_url:
                  type: string
                  maxLength: 2048
                primary_color:
                  type: string
                  pattern: ^#[0-9a-fA-F]{6}$
                accent_color:
                  type: string
                  pattern: ^#[0-9a-fA-F]{6}$
                custom_domain:
                  type: string
                  maxLength: 253
                email_from_name:
                  type: string
                footer_text:
                  type: string
                  maxLength: 500
      responses:
        '200':
          description: Branding updated
        '400':
          description: Validation error
  /api/v1/branding/verify-domain:
    post:
      summary: Verify Custom Domain
      description: Verify custom domain DNS configuration for white-label signing URLs. Business tier required. Checks CNAME
        and TXT records.
      operationId: verifyCustomDomain
      tags:
      - Branding
      responses:
        '200':
          description: Domain verification result
          content:
            application/json:
              schema:
                type: object
                properties:
                  domain:
                    type: string
                  status:
                    type: string
                    enum:
                    - active
                    - pending
                    - failed
                  message:
                    type: string
        '400':
          description: No custom domain configured
        '403':
          description: Business tier required
  /api/v1/branding/logo:
    get:
      summary: Serve Branding Logo
      description: Serve a branding logo from R2 storage. Public endpoint for displaying logos on signing pages. Requires
        a valid R2 key starting with "branding/".
      operationId: getBrandingLogo
      tags:
      - Branding
      security: []
      parameters:
      - name: key
        in: query
        required: true
        schema:
          type: string
        description: R2 object key (must start with "branding/")
      responses:
        '200':
          description: Logo image binary
          content:
            image/png: {}
            image/jpeg: {}
            image/svg+xml: {}
            image/webp: {}
        '404':
          description: Logo not found
    put:
      summary: Upload Branding Logo
      description: Upload a branding logo to R2. Business tier required. Accepts PNG, JPG, SVG, or WebP up to 1MB as base64.
      operationId: uploadBrandingLogo
      tags:
      - Branding
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - mime_type
              - data
              properties:
                mime_type:
                  type: string
                  enum:
                  - image/png
                  - image/jpeg
                  - image/svg+xml
                  - image/webp
                data:
                  type: string
                  description: Base64-encoded image data (max 1MB decoded)
      responses:
        '200':
          description: Logo uploaded successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  logo_url:
                    type: string
                    format: uri
        '400':
          description: Invalid file type or data
  /api/v1/branding/verify-email-domain:
    post:
      summary: Verify Email Domain
      description: Verify email sending domain (DKIM/SPF) for branded email delivery. Business tier required.
      operationId: verifyEmailDomain
      tags:
      - Branding
      responses:
        '200':
          description: Email domain verification result
          content:
            application/json:
              schema:
                type: object
                properties:
                  domain:
                    type: string
                  status:
                    type: string
                    enum:
                    - verified
                    - pending
                    - failed
                  message:
                    type: string
        '400':
          description: No custom domain configured
        '403':
          description: Business tier required
  /api/v1/clauses:
    get:
      summary: List Clauses
      description: List reusable content clauses for templates. Supports category and search filters. Business+ tier.
      operationId: listClauses
      tags:
      - Clauses
      parameters:
      - name: category
        in: query
        schema:
          type: string
      - name: search
        in: query
        schema:
          type: string
      - name: org_id
        in: query
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Array of clauses
          content:
            application/json:
              schema:
                type: object
                properties:
                  clauses:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        name:
                          type: string
                        description:
                          type: string
                        category:
                          type: string
                        content:
                          type: string
                        tags:
                          type: string
    post:
      summary: Create Clause
      description: Create a new reusable clause. Business+ tier.
      operationId: createClause
      tags:
      - Clauses
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              - content
              properties:
                name:
                  type: string
                  maxLength: 200
                content:
                  type: string
                  maxLength: 10000
                description:
                  type: string
                  maxLength: 500
                category:
                  type: string
                tags:
                  type: string
                org_id:
                  type: string
                  format: uuid
      responses:
        '201':
          description: Clause created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  name:
                    type: string
                  created_at:
                    type: string
                    format: date-time
        '400':
          description: Validation error
  /api/v1/clauses/{id}:
    get:
      summary: Get Clause
      description: Retrieve a single clause by ID.
      operationId: getClause
      tags:
      - Clauses
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Clause details
          content:
            application/json:
              schema:
                type: object
                properties:
                  clause:
                    type: object
        '404':
          description: Clause not found
    put:
      summary: Update Clause
      description: Update an existing clause's name, content, description, category, or tags.
      operationId: updateClause
      tags:
      - Clauses
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 200
                content:
                  type: string
                  maxLength: 10000
                description:
                  type: string
                category:
                  type: string
                tags:
                  type: string
      responses:
        '200':
          description: Clause updated
        '404':
          description: Clause not found
    delete:
      summary: Delete Clause
      description: Delete a clause by ID.
      operationId: deleteClause
      tags:
      - Clauses
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Clause deleted
        '404':
          description: Clause not found
  /api/v1/contact:
    post:
      summary: Contact Form
      description: Submit a contact form message. No authentication required. Honeypot protected.
      operationId: submitContact
      tags:
      - System
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              - message
              properties:
                email:
                  type: string
                  format: email
                name:
                  type: string
                  maxLength: 100
                message:
                  type: string
                  minLength: 10
                  maxLength: 5000
                website:
                  type: string
                  description: Honeypot field — leave empty
      responses:
        '200':
          description: Message sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '400':
          description: Validation error
  /api/v1/contact-sender:
    post:
      summary: Contact Sender
      description: Signer sends a message to the envelope sender. Token-based auth. In demo mode, routes to signer's own email.
      operationId: contactSender
      tags:
      - Signers
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - message
              properties:
                token:
                  type: string
                  description: Signing token
                message:
                  type: string
                  minLength: 5
                  maxLength: 2000
      responses:
        '200':
          description: Message sent to sender
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '404':
          description: Invalid token or recipient not available
  /api/v1/cron/email-retry:
    get:
      summary: Retry Failed Emails
      description: Process the failed email retry queue. Resends emails with up to 3 attempts. Runs every 15 minutes.
      operationId: cronEmailRetry
      tags:
      - Cron
      x-internal: true
      security: []
      parameters:
      - name: secret
        in: query
        required: true
        schema:
          type: string
        description: CRON_SECRET
      responses:
        '200':
          description: Retry results
          content:
            application/json:
              schema:
                type: object
                properties:
                  processed:
                    type: integer
                  succeeded:
                    type: integer
                  failed:
                    type: integer
        '401':
          description: Invalid or missing secret
  /api/v1/cron/expire:
    get:
      summary: Expire Envelopes
      description: Auto-void envelopes past their expires_at date. Notifies senders. Runs hourly.
      operationId: cronExpire
      tags:
      - Cron
      x-internal: true
      security: []
      parameters:
      - name: secret
        in: query
        required: true
        schema:
          type: string
        description: CRON_SECRET
      responses:
        '200':
          description: Expiration results
          content:
            application/json:
              schema:
                type: object
                properties:
                  expired:
                    type: integer
        '401':
          description: Invalid or missing secret
  /api/v1/cron/ots-upgrade:
    get:
      summary: Upgrade OTS Proofs
      description: Upgrade pending OpenTimestamps proofs to Bitcoin-confirmed status. Processes up to 50 per run. Runs hourly.
      operationId: cronOtsUpgrade
      tags:
      - Cron
      x-internal: true
      security: []
      parameters:
      - name: secret
        in: query
        required: true
        schema:
          type: string
        description: CRON_SECRET
      responses:
        '200':
          description: Upgrade results
          content:
            application/json:
              schema:
                type: object
                properties:
                  upgraded:
                    type: integer
                  pending:
                    type: integer
        '401':
          description: Invalid or missing secret
  /api/v1/cron/reminders:
    get:
      summary: Send Reminders
      description: Send automated signing reminders to pending signers. Respects reminder_days and max_reminders settings.
        Runs hourly.
      operationId: cronReminders
      tags:
      - Cron
      x-internal: true
      security: []
      parameters:
      - name: secret
        in: query
        required: true
        schema:
          type: string
        description: CRON_SECRET
      responses:
        '200':
          description: Reminder results
          content:
            application/json:
              schema:
                type: object
                properties:
                  sent:
                    type: integer
        '401':
          description: Invalid or missing secret
  /api/v1/cron/re-seal:
    get:
      summary: Retry Failed Seals
      description: Retry incomplete envelope seals (failed or stuck sealing state). Max 3 seal attempts per envelope. Runs
        every 5-15 minutes.
      operationId: cronReSeal
      tags:
      - Cron
      x-internal: true
      security: []
      parameters:
      - name: secret
        in: query
        required: true
        schema:
          type: string
        description: CRON_SECRET
      responses:
        '200':
          description: Re-seal results
          content:
            application/json:
              schema:
                type: object
                properties:
                  retried:
                    type: integer
                  skipped:
                    type: integer
                  errors:
                    type: array
                    items:
                      type: string
        '401':
          description: Invalid or missing secret
  /api/v1/cron/retention:
    get:
      summary: Retention Cleanup
      description: Purge expired evidence packages. Deletes R2 objects for envelopes past their retention period. Runs daily.
      operationId: cronRetention
      tags:
      - Cron
      x-internal: true
      security: []
      parameters:
      - name: secret
        in: query
        required: true
        schema:
          type: string
        description: CRON_SECRET
      responses:
        '200':
          description: Retention cleanup results
          content:
            application/json:
              schema:
                type: object
                properties:
                  purged:
                    type: integer
        '401':
          description: Invalid or missing secret
  /api/v1/cron/test-cleanup:
    get:
      summary: Test Envelope Cleanup
      description: Auto-void test mode envelopes older than 24 hours. Deletes associated R2 objects. Runs hourly.
      operationId: cronTestCleanup
      tags:
      - Cron
      x-internal: true
      security: []
      parameters:
      - name: secret
        in: query
        required: true
        schema:
          type: string
        description: CRON_SECRET
      responses:
        '200':
          description: Cleanup results
          content:
            application/json:
              schema:
                type: object
                properties:
                  voided:
                    type: integer
        '401':
          description: Invalid or missing secret
  /api/v1/cron/domain-check:
    get:
      summary: Check Custom Domains
      description: Poll pending custom domain and email domain validations. Activates domains when SSL is live, fails domains
        after 7 days. Runs hourly.
      operationId: cronDomainCheck
      tags:
      - Cron
      x-internal: true
      security: []
      parameters:
      - name: secret
        in: query
        required: true
        schema:
          type: string
        description: CRON_SECRET
      responses:
        '200':
          description: Domain check results
          content:
            application/json:
              schema:
                type: object
                properties:
                  checked:
                    type: integer
                  activated:
                    type: integer
                  failed:
                    type: integer
                  email_checked:
                    type: integer
                  email_activated:
                    type: integer
                  email_failed:
                    type: integer
        '401':
          description: Invalid or missing secret
  /api/v1/cron/notarization-check:
    get:
      summary: Poll Notarization Sessions
      description: Poll stale BlueNotary notarization sessions. Updates status for pending/in_progress sessions older than
        48h. Expires sessions older than 72h. Runs hourly.
      operationId: cronNotarizationCheck
      tags:
      - Cron
      x-internal: true
      security: []
      parameters:
      - name: secret
        in: query
        required: true
        schema:
          type: string
        description: CRON_SECRET
      responses:
        '200':
          description: Notarization check results
          content:
            application/json:
              schema:
                type: object
                properties:
                  checked:
                    type: integer
                  updated:
                    type: integer
                  expired:
                    type: integer
                  errors:
                    type: integer
        '401':
          description: Invalid or missing secret
        '503':
          description: BlueNotary API not configured
  /api/v1/declaration-download:
    get:
      summary: Download Declaration
      description: One-time burn-on-use declaration download. Token is destroyed after first successful download. Expires
        after 72 hours.
      operationId: downloadDeclaration
      tags:
      - Declarations
      security: []
      parameters:
      - name: token
        in: query
        required: true
        schema:
          type: string
        description: One-time download token from email
      responses:
        '200':
          description: Declaration PDF
          content:
            application/pdf: {}
        '404':
          description: Token expired or already used
  /api/v1/declaration-request:
    post:
      summary: Request Declaration
      description: Request a court-ready declaration via email OTP verification. Step 1 sends OTP, step 2 verifies and processes
        request. Signers are auto-approved; third-party requests are queued for admin review.
      operationId: requestDeclaration
      tags:
      - Declarations
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                action:
                  type: string
                  enum:
                  - verify
                  description: Set to "verify" for step 2 (OTP verification)
                envelope_id:
                  type: string
                  format: uuid
                email:
                  type: string
                  format: email
                name:
                  type: string
                relationship:
                  type: string
                  enum:
                  - signer
                  - attorney
                  - court_officer
                  - law_enforcement
                  - other
                otp:
                  type: string
                  description: 6-digit OTP code (step 2 only)
      responses:
        '200':
          description: OTP sent (step 1) or request processed (step 2)
        '400':
          description: Validation error
        '429':
          description: OTP rate limited
  /api/v1/declaration-request/approve:
    post:
      summary: Approve Declaration Request
      description: Admin approves or denies a third-party declaration request. Generates download token and emails requester
        on approval.
      operationId: approveDeclarationRequest
      tags:
      - Declarations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - request_id
              properties:
                request_id:
                  type: string
                  format: uuid
                denial_reason:
                  type: string
                  description: Reason for denial (omit to approve)
      responses:
        '200':
          description: Request approved or denied
        '403':
          description: Admin access required
        '404':
          description: Request not found
  /api/v1/document-compare:
    post:
      summary: AI Document Comparison
      description: AI-powered comparison of the contract text with an uploaded document. Returns a plain-language summary
        of differences. Token-based auth.
      operationId: compareDocuments
      tags:
      - Documents
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - contract_text
              - uploaded_text
              properties:
                token:
                  type: string
                  minLength: 32
                  description: Signing token
                contract_text:
                  type: string
                  maxLength: 6000
                  description: Original contract text
                uploaded_text:
                  type: string
                  maxLength: 6000
                  description: Uploaded document text to compare
                lang:
                  type: string
                  description: Response language (ISO 639-1)
      responses:
        '200':
          description: AI comparison summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  summary:
                    type: string
                  lang:
                    type: string
        '400':
          description: Invalid token or missing text
        '503':
          description: AI comparison not available
  /api/v1/document-pdf:
    post:
      summary: Download Unsigned PDF Preview
      description: Generate and download an unsigned PDF preview of the document with page break hints. Token-based auth.
      operationId: downloadDocumentPdf
      tags:
      - Documents
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              properties:
                token:
                  type: string
                  description: Signing token
                break_before_lines:
                  type: array
                  items:
                    type: integer
                  description: Line numbers to insert page breaks before
      responses:
        '200':
          description: PDF binary
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '400':
          description: Missing token
        '404':
          description: Invalid token or envelope not found
  /api/v1/idv/session:
    post:
      summary: Create IDV Session
      description: Create a Veriff government ID verification session. Token-based auth. Requires envelope to have require_id_verification
        enabled. Max 3 sessions per signer.
      operationId: createIdvSession
      tags:
      - Identity
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              properties:
                token:
                  type: string
                  description: Signing token
      responses:
        '200':
          description: Veriff session created
          content:
            application/json:
              schema:
                type: object
                properties:
                  session_url:
                    type: string
                    format: uri
                  session_id:
                    type: string
        '404':
          description: Invalid token
        '409':
          description: Already signed
        '410':
          description: Document voided
        '429':
          description: Max sessions exceeded
  /api/v1/idv/verify:
    post:
      summary: Verify IDV Result
      description: Verify that government ID verification was completed successfully. Fetches the decision from Veriff and
        stores the result. Token-based auth.
      operationId: verifyIdv
      tags:
      - Identity
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              properties:
                token:
                  type: string
                  description: Signing token
      responses:
        '200':
          description: IDV verification result
          content:
            application/json:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                  already_verified:
                    type: boolean
        '404':
          description: Invalid token
  /api/v1/kba/start:
    post:
      summary: Start KBA
      description: Start a knowledge-based authentication session. Returns quiz questions from LexisNexis or instant verification
        via Persona. Token-based auth.
      operationId: startKba
      tags:
      - Identity
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - name
              - dob
              - address_line1
              - address_city
              - address_state
              - address_zip
              - ssn_last4
              properties:
                token:
                  type: string
                  description: Signing token
                name:
                  type: string
                dob:
                  type: string
                  description: Date of birth (YYYY-MM-DD)
                address_line1:
                  type: string
                address_city:
                  type: string
                address_state:
                  type: string
                  description: US state code (e.g. CA, NY)
                address_zip:
                  type: string
                ssn_last4:
                  type: string
                  pattern: ^\d{4}$
      responses:
        '200':
          description: KBA questions or instant verification result
          content:
            application/json:
              schema:
                type: object
                properties:
                  questions:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        text:
                          type: string
                        choices:
                          type: array
                          items:
                            type: string
                  expires_in:
                    type: integer
                    description: Seconds until questions expire
                  passed:
                    type: boolean
                    description: Instant pass (Persona provider only)
        '400':
          description: Validation error
        '404':
          description: Invalid token
        '429':
          description: Max KBA attempts exceeded
  /api/v1/kba/verify:
    post:
      summary: Verify KBA Answers
      description: Verify KBA quiz answers. LexisNexis provider only (Persona uses instant verification in start). Token-based
        auth.
      operationId: verifyKba
      tags:
      - Identity
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - answers
              properties:
                token:
                  type: string
                  description: Signing token
                answers:
                  type: array
                  items:
                    type: object
                    required:
                    - question_id
                    - answer
                    properties:
                      question_id:
                        type: string
                      answer:
                        type: string
      responses:
        '200':
          description: KBA verification result
          content:
            application/json:
              schema:
                type: object
                properties:
                  passed:
                    type: boolean
                  correct_count:
                    type: integer
                  attempts_remaining:
                    type: integer
        '400':
          description: Invalid answers format
        '404':
          description: Invalid token
  /api/v1/negotiate:
    post:
      summary: Flag Clause
      description: Signer flags a clause for negotiation. Token-based auth. Max 20 flags per signer. Envelope must have allow_negotiation
        enabled.
      operationId: flagClause
      tags:
      - Negotiation
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - clause_ref
              - objection
              properties:
                token:
                  type: string
                  minLength: 32
                clause_ref:
                  type: string
                  description: Clause identifier/reference
                clause_text:
                  type: string
                  maxLength: 2000
                  description: Text of the flagged clause
                objection:
                  type: string
                  maxLength: 2000
                  description: Signer's objection or concern
      responses:
        '201':
          description: Clause flagged
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  status:
                    type: string
                    enum:
                    - open
                  created_at:
                    type: string
                    format: date-time
        '400':
          description: Validation error or cannot negotiate
        '403':
          description: Negotiation not enabled
        '429':
          description: Max flags per signer exceeded
    get:
      summary: Get Negotiations (Signer)
      description: Get all clause negotiations for the envelope. Token-based auth.
      operationId: getSignerNegotiations
      tags:
      - Negotiation
      security: []
      parameters:
      - name: token
        in: query
        required: true
        schema:
          type: string
          minLength: 32
        description: Signing token
      responses:
        '200':
          description: List of negotiations
          content:
            application/json:
              schema:
                type: object
                properties:
                  negotiations:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        clause_ref:
                          type: string
                        objection:
                          type: string
                        status:
                          type: string
                          enum:
                          - open
                          - resolved
                          - withdrawn
                        sender_response:
                          type: string
                          nullable: true
                        resolved_action:
                          type: string
                          nullable: true
                          enum:
                          - modified
                          - noted
                          - dismissed
        '404':
          description: Invalid token
    delete:
      summary: Withdraw Flag
      description: Signer withdraws a clause flag. Token-based auth. Only open flags can be withdrawn.
      operationId: withdrawFlag
      tags:
      - Negotiation
      security: []
      parameters:
      - name: token
        in: query
        required: true
        schema:
          type: string
          minLength: 32
      - name: id
        in: query
        required: true
        schema:
          type: string
          format: uuid
        description: Negotiation ID to withdraw
      responses:
        '200':
          description: Flag withdrawn
        '400':
          description: Cannot withdraw resolved negotiation
        '403':
          description: Can only withdraw own flags
        '404':
          description: Negotiation not found
  /api/v1/negotiate/list:
    get:
      summary: List Negotiations (Sender)
      description: List clause negotiations across the sender's envelopes. JWT auth. Supports filtering by status and envelope_id.
      operationId: listSenderNegotiations
      tags:
      - Negotiation
      parameters:
      - name: status
        in: query
        schema:
          type: string
          enum:
          - open
          - resolved
      - name: envelope_id
        in: query
        schema:
          type: string
          format: uuid
      - name: limit
        in: query
        schema:
          type: integer
          default: 50
          maximum: 100
      - name: offset
        in: query
        schema:
          type: integer
          default: 0
      responses:
        '200':
          description: Negotiations with signer and template details
  /api/v1/negotiate/respond:
    post:
      summary: Respond to Negotiation
      description: Sender responds to a clause negotiation flag. JWT auth. Actions are modified, noted, or dismissed.
      operationId: respondToNegotiation
      tags:
      - Negotiation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - negotiation_id
              - response
              - action
              properties:
                negotiation_id:
                  type: string
                  format: uuid
                response:
                  type: string
                  maxLength: 2000
                  description: Sender's response text
                action:
                  type: string
                  enum:
                  - modified
                  - noted
                  - dismissed
                  description: Resolution action
                proposed_text:
                  type: string
                  maxLength: 2000
                  description: Proposed replacement text (required when action is 'modified')
      responses:
        '200':
          description: Negotiation resolved
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  status:
                    type: string
                    enum:
                    - resolved
                  resolved_action:
                    type: string
                    enum:
                    - modified
                    - noted
                    - dismissed
                  proposed_text:
                    type: string
                    nullable: true
                  resolved_at:
                    type: string
                    format: date-time
        '400':
          description: Validation error or already resolved
        '403':
          description: Only envelope sender can respond
        '404':
          description: Negotiation not found
  /api/v1/negotiate/counter:
    post:
      summary: Signer Counter-Response
      description: 'Signer counter-responds to a resolved negotiation. Token-based auth.

        Actions: "accepted" (closes permanently) or "counter_proposed" (reopens, increments round).

        Only available for negotiations where resolved_action is "modified". Max 3 rounds.'
      operationId: counterNegotiation
      tags:
      - Negotiation
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - negotiation_id
              - counter_status
              properties:
                token:
                  type: string
                  minLength: 4
                  description: Signing token
                negotiation_id:
                  type: string
                  format: uuid
                counter_status:
                  type: string
                  enum:
                  - accepted
                  - counter_proposed
                  description: Accept proposed changes or submit counter-proposal
                counter_text:
                  type: string
                  maxLength: 2000
                  description: Counter-proposal text (required when counter_status is 'counter_proposed')
      responses:
        '200':
          description: Counter-response recorded
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  status:
                    type: string
                    enum:
                    - open
                    - resolved
                  counter_status:
                    type: string
                    enum:
                    - accepted
                    - counter_proposed
                  round:
                    type: integer
        '400':
          description: Validation error, wrong state, or max rounds reached
        '403':
          description: Can only respond to own negotiations
        '404':
          description: Negotiation not found
  /api/v1/events:
    get:
      summary: Server-Sent Events
      description: 'Streams real-time events via SSE for a given channel (e.g., seal progress).

        Token-based auth for signer-scoped channels. Auto-closes at ~25s (CF limit).

        Returns `text/event-stream` response. Sends keepalive comments every 15s.'
      operationId: streamEvents
      tags:
      - System
      security: []
      parameters:
      - name: channel
        in: query
        required: true
        schema:
          type: string
        description: Event channel (e.g., 'seal:{envelope_id}')
      - name: token
        in: query
        schema:
          type: string
        description: Signing token (required for signer-scoped channels)
      - name: since
        in: query
        schema:
          type: integer
        description: Timestamp in ms to start from (default now)
      responses:
        '200':
          description: SSE stream
          content:
            text/event-stream:
              schema:
                type: string
        '400':
          description: Channel required
        '401':
          description: Token required for signer-scoped channels
        '403':
          description: Token does not match channel scope
  /api/v1/packages:
    get:
      summary: List Packages
      description: List multi-document signing packages. JWT auth. Supports filtering by status and pagination. Pass ?id=
        to get a single package with envelope details.
      operationId: listPackages
      tags:
      - Packages
      parameters:
      - name: id
        in: query
        schema:
          type: string
          format: uuid
        description: Get single package by ID
      - name: status
        in: query
        schema:
          type: string
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: limit
        in: query
        schema:
          type: integer
          default: 20
          maximum: 50
      responses:
        '200':
          description: Package list or single package details
    post:
      summary: Create Package
      description: Create a multi-document signing package linking 2-10 existing envelopes. JWT auth.
      operationId: createPackage
      tags:
      - Packages
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              - envelopes
              properties:
                name:
                  type: string
                  description: Package name
                envelopes:
                  type: array
                  minItems: 2
                  maxItems: 10
                  items:
                    type: object
                    required:
                    - envelope_id
                    properties:
                      envelope_id:
                        type: string
                        format: uuid
                      sequence:
                        type: integer
                      depends_on:
                        type: string
                        format: uuid
                      label:
                        type: string
                metadata:
                  type: object
                  additionalProperties: true
      responses:
        '201':
          description: Package created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  name:
                    type: string
                  envelope_count:
                    type: integer
                  created_at:
                    type: string
                    format: date-time
        '400':
          description: Validation error or envelopes already in a package
        '403':
          description: All envelopes must belong to you
        '404':
          description: One or more envelopes not found
  /api/v1/payments/checkout:
    post:
      summary: Create Checkout Session
      description: Create a Stripe Checkout session for pay-to-sign. Accepts either a signing token or JWT auth (envelope
        owner).
      operationId: createCheckout
      tags:
      - Payments
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - envelope_id
              properties:
                envelope_id:
                  type: string
                  format: uuid
                token:
                  type: string
                  description: Signing token (alternative to JWT auth)
      responses:
        '200':
          description: Stripe checkout URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  checkout_url:
                    type: string
                    format: uri
        '400':
          description: Missing envelope_id
        '403':
          description: Invalid token or not envelope owner
        '503':
          description: Payment processing not configured
  /api/v1/signer-profile:
    get:
      summary: Get Signer Profile
      description: Retrieve auto-fill suggestions for the signer. Profile data is encrypted at rest (AES-256-GCM). Token-based
        auth.
      operationId: getSignerProfile
      tags:
      - Signer Profile
      security: []
      parameters:
      - name: token
        in: query
        required: true
        schema:
          type: string
          minLength: 32
        description: Signing token
      responses:
        '200':
          description: Profile data or null if none saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  profile:
                    type: object
                    nullable: true
                    properties:
                      fields:
                        type: object
                        additionalProperties:
                          type: string
                      consent_at:
                        type: string
                        format: date-time
                      field_count:
                        type: integer
        '404':
          description: Invalid token
    post:
      summary: Save Signer Profile
      description: Create or update an auto-fill profile. Requires explicit consent. Max 20 fields, 500 chars each. Token-based
        auth.
      operationId: saveSignerProfile
      tags:
      - Signer Profile
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - token
              - fields
              - consent
              properties:
                token:
                  type: string
                  minLength: 32
                fields:
                  type: object
                  additionalProperties:
                    type: string
                  description: Field name to value mapping (max 20 fields)
                consent:
                  type: boolean
                  description: Must be true
      responses:
        '200':
          description: Profile saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  saved:
                    type: boolean
                  field_count:
                    type: integer
        '400':
          description: Missing consent or fields
        '404':
          description: Invalid token
    delete:
      summary: Delete Signer Profile
      description: Delete signer profile (GDPR right to erasure). Token-based auth.
      operationId: deleteSignerProfile
      tags:
      - Signer Profile
      security: []
      parameters:
      - name: token
        in: query
        required: true
        schema:
          type: string
          minLength: 32
        description: Signing token
      responses:
        '200':
          description: Profile deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
        '404':
          description: Invalid token
  /api/v1/signing-links:
    get:
      summary: List Signing Links
      description: List the authenticated user's reusable signing links. JWT auth.
      operationId: listSigningLinks
      tags:
      - Signing Links
      parameters:
      - name: page
        in: query
        schema:
          type: integer
          default: 1
        description: Page number (1-based)
      - name: limit
        in: query
        schema:
          type: integer
          default: 20
          maximum: 100
        description: Results per page
      responses:
        '200':
          description: Array of signing links
    put:
      summary: Deactivate Signing Link
      description: Deactivate a reusable signing link. Only the creator can deactivate. JWT auth.
      operationId: deactivateSigningLink
      tags:
      - Signing Links
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - link_id
              properties:
                link_id:
                  type: string
                  format: uuid
                  description: ID of the signing link to deactivate
      responses:
        '200':
          description: Signing link deactivated
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  status:
                    type: string
                    enum:
                    - deactivated
        '403':
          description: Not the link creator
        '404':
          description: Signing link not found
        '409':
          description: Link already deactivated
    post:
      summary: Create Signing Link
      description: Create a reusable signing link for a template. Professional+ tier required. JWT auth.
      operationId: createSigningLink
      tags:
      - Signing Links
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - template
              properties:
                template:
                  type: string
                  description: Template ID
                fields:
                  type: object
                  additionalProperties: true
                  description: Pre-filled field values
                max_uses:
                  type: integer
                  maximum: 100000
                  description: Maximum number of times this link can be used
                expiry_days:
                  type: integer
                  default: 90
                  maximum: 365
                metadata:
                  type: object
                  additionalProperties: true
                org_id:
                  type: string
                  format: uuid
      responses:
        '201':
          description: Signing link created
        '403':
          description: Professional+ tier required
        '404':
          description: Template not found
  /api/v1/signing-links/info:
    get:
      summary: Get Signing Link Info
      description: Get signing link details for the claim form. Public endpoint, no auth. Token-based.
      operationId: getSigningLinkInfo
      tags:
      - Signing Links
      security: []
      parameters:
      - name: token
        in: query
        required: true
        schema:
          type: string
        description: Signing link token
      responses:
        '200':
          description: Link info
          content:
            application/json:
              schema:
                type: object
                properties:
                  template_name:
                    type: string
                  sender_name:
                    type: string
                  status:
                    type: string
                    enum:
                    - active
        '404':
          description: Invalid or expired link
        '410':
          description: Link expired
  /api/v1/signing-links/claim:
    post:
      summary: Claim Signing Link
      description: Claim a reusable signing link. Creates an envelope and sends the signing invitation. Public endpoint, no
        JWT.
      operationId: claimSigningLink
      tags:
      - Signing Links
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - link_token
              - name
              - email
              properties:
                link_token:
                  type: string
                  description: Signing link token
                name:
                  type: string
                email:
                  type: string
                  format: email
      responses:
        '201':
          description: Envelope created from signing link
        '400':
          description: Validation error
        '404':
          description: Invalid link
        '410':
          description: Link expired or max uses reached
  /api/v1/notarize:
    post:
      summary: Create Notarization Session
      description: Create a remote online notarization (RON) session via BlueNotary. Business tier required. The envelope
        must be in completed status.
      operationId: createNotarizationSession
      tags:
      - Notarization
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - envelope_id
              - notarization_type
              properties:
                envelope_id:
                  type: string
                  format: uuid
                  description: Envelope to notarize
                notarization_type:
                  type: string
                  enum:
                  - acknowledgment
                  - jurat
                  - copy_certification
                  description: Type of notarization
      responses:
        '201':
          description: Notarization session created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  session_url:
                    type: string
                    format: uri
                    description: BlueNotary session URL for the signer
                  status:
                    type: string
                    enum:
                    - pending
                    - scheduled
        '400':
          description: Envelope not in completed status
        '403':
          description: Business tier required or not envelope owner
        '404':
          description: Envelope not found
        '503':
          description: Notarization service not configured
    get:
      summary: Get Notarization Status
      description: Get the status of a notarization session for an envelope. JWT auth.
      operationId: getNotarizationStatus
      tags:
      - Notarization
      parameters:
      - name: envelope_id
        in: query
        required: true
        schema:
          type: string
          format: uuid
        description: Envelope ID to check notarization status
      responses:
        '200':
          description: Notarization session details
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  status:
                    type: string
                    enum:
                    - pending
                    - scheduled
                    - in_progress
                    - completed
                    - failed
                    - cancelled
                  session_url:
                    type: string
                    format: uri
                    nullable: true
                  notary_name:
                    type: string
                    nullable: true
                  completed_at:
                    type: string
                    format: date-time
                    nullable: true
                  certificate_download_url:
                    type: string
                    format: uri
                    nullable: true
        '404':
          description: No notarization found for this envelope
  /api/v1/webhooks/notarization:
    post:
      summary: BlueNotary Notarization Webhook
      description: Handles BlueNotary webhook events for notarization session updates. Verified via HMAC (BLUENOTARY_WEBHOOK_SECRET).
      operationId: blueNotaryWebhook
      tags:
      - Notarization
      security: []
      parameters:
      - name: X-BlueNotary-Signature
        in: header
        required: true
        schema:
          type: string
        description: HMAC signature for webhook verification
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - event
              - session_id
              properties:
                event:
                  type: string
                  description: Webhook event type
                session_id:
                  type: string
                  description: BlueNotary session ID
      responses:
        '200':
          description: Webhook received
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
        '400':
          description: Invalid signature or payload
        '503':
          description: Webhook secret not configured
  /api/v1/waitlist:
    post:
      summary: Join Waitlist
      description: Join the waitlist. No authentication required. Honeypot protected. One signup per email.
      operationId: joinWaitlist
      tags:
      - System
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              properties:
                email:
                  type: string
                  format: email
                website:
                  type: string
                  description: Honeypot field — leave empty
      responses:
        '200':
          description: Waitlist confirmation
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
  /api/v1/webhooks/payments:
    post:
      summary: Stripe Payment Webhook
      description: Handles Stripe webhook events (checkout.session.completed). Verified via Stripe-Signature header.
      operationId: stripePaymentWebhook
      tags:
      - Payments
      security: []
      parameters:
      - name: Stripe-Signature
        in: header
        required: true
        schema:
          type: string
        description: Stripe webhook signature
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Webhook received
        '400':
          description: Missing or invalid signature
        '503':
          description: Webhook not configured
  /api/v1/declaration-requests:
    get:
      summary: List Declaration Requests
      description: List court-ready declaration requests for admin review. Returns requests stored in R2. Admins see all;
        non-admins see only requests for their envelopes.
      operationId: listDeclarationRequests
      tags:
      - Declarations
      parameters:
      - name: status
        in: query
        schema:
          type: string
          enum:
          - pending
          - approved
          - denied
        description: Filter by request status
      responses:
        '200':
          description: Declaration requests list
          content:
            application/json:
              schema:
                type: object
                properties:
                  requests:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        envelope_id:
                          type: string
                          format: uuid
                        email:
                          type: string
                        name:
                          type: string
                        status:
                          type: string
                          enum:
                          - pending
                          - approved
                          - denied
                        created_at:
                          type: string
                          format: date-time
                  total:
                    type: integer
        '401':
          description: Authentication required
  /api/v1/declaration-request/checkout:
    post:
      summary: Declaration Checkout
      description: Create a Stripe checkout session for a $49 third-party court-ready declaration. Signers and senders are
        exempt (no payment required). Public endpoint.
      operationId: declarationCheckout
      tags:
      - Declarations
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - envelope_id
              - email
              properties:
                envelope_id:
                  type: string
                  format: uuid
                email:
                  type: string
                  format: email
                name:
                  type: string
      responses:
        '200':
          description: Payment status and checkout URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  payment_required:
                    type: boolean
                  checkout_url:
                    type: string
                    format: uri
                    nullable: true
                  session_id:
                    type: string
                    nullable: true
                  reason:
                    type: string
                    nullable: true
                    description: '''signer'' or ''sender'' if payment not required'
        '400':
          description: Missing required fields
        '404':
          description: Envelope not found or not completed
        '503':
          description: Payment processing not configured
  /api/v1/usage:
    get:
      summary: Get Usage Metrics
      description: Detailed usage data for the current user including current month usage, breakdown by source, overage info,
        days remaining, and 12-month history.
      operationId: getUsage
      tags:
      - System
      responses:
        '200':
          description: Usage metrics with history
          content:
            application/json:
              schema:
                type: object
                properties:
                  tier:
                    type: string
                  limit:
                    type: integer
                  used:
                    type: integer
                  self_service:
                    type: integer
                  bundled:
                    type: integer
                  remaining:
                    type: integer
                  overage:
                    type: integer
                  overage_cost:
                    type: number
                  overage_rate:
                    type: number
                  year_month:
                    type: string
                  days_in_month:
                    type: integer
                  days_remaining:
                    type: integer
                  history:
                    type: array
                    items:
                      type: object
                      properties:
                        year_month:
                          type: string
                        total:
                          type: integer
                        self_service:
                          type: integer
                        bundled:
                          type: integer
  /api/v1/founding-members:
    get:
      summary: Founding Member Availability
      description: Get founding member spot availability. Public endpoint, no auth required. Rate limited to 30/min, cached
        for 60 seconds.
      operationId: getFoundingMembers
      tags:
      - System
      security: []
      responses:
        '200':
          description: Founding member spot counts
          content:
            application/json:
              schema:
                type: object
                properties:
                  total:
                    type: integer
                    description: Total founding member spots
                  claimed:
                    type: integer
                    description: Number of spots claimed
                  remaining:
                    type: integer
                    description: Number of spots still available
                  available:
                    type: boolean
                    description: Whether spots are still available
                required:
                - total
                - claimed
                - remaining
                - available
  /api/v1/webhooks/founding:
    post:
      summary: Stripe Founding Member Webhook
      description: 'Stripe webhook for founding member subscription events. Verified via stripe-signature header.

        Events: checkout.session.completed, customer.subscription.deleted, customer.subscription.updated.'
      operationId: stripeFoundingWebhook
      tags:
      - System
      security: []
      parameters:
      - name: stripe-signature
        in: header
        required: true
        schema:
          type: string
        description: Stripe webhook signature
      responses:
        '200':
          description: Webhook received
          content:
            application/json:
              schema:
                type: object
                properties:
                  received:
                    type: boolean
                required:
                - received
        '401':
          description: Missing or invalid signature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/notifications:
    get:
      summary: Get User Notifications
      description: Fetch notifications for the authenticated user. Returns recent notifications (unread first) and marks them
        as read.
      operationId: getNotifications
      tags:
      - System
      parameters:
      - name: limit
        in: query
        schema:
          type: integer
          default: 20
      - name: offset
        in: query
        schema:
          type: integer
          default: 0
      responses:
        '200':
          description: User notifications
          content:
            application/json:
              schema:
                type: object
                properties:
                  notifications:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        type:
                          type: string
                        title:
                          type: string
                        body:
                          type: string
                        data:
                          type: object
                          nullable: true
                        read:
                          type: boolean
                        created_at:
                          type: string
                          format: date-time
                required:
                - notifications
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/envelopes/{id}/evidence:
    get:
      summary: Evidence Package Status
      description: Get evidence package completeness checklist for an envelope. Returns artifact presence, audit trail integrity,
        timestamp status, and per-signer identity evidence.
      operationId: getEvidenceStatus
      tags:
      - Envelopes
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Envelope ID
      responses:
        '200':
          description: Evidence completeness report
          content:
            application/json:
              schema:
                type: object
                properties:
                  complete:
                    type: boolean
                    description: Whether all evidence artifacts are present and envelope is sealed
                  checklist:
                    type: object
                    properties:
                      envelope_status:
                        type: string
                      seal_status:
                        type: string
                      markdown_preserved:
                        type: boolean
                      field_values_captured:
                        type: boolean
                      signatures_stored:
                        type: boolean
                      audit_trail:
                        type: object
                        properties:
                          intact:
                            type: boolean
                          event_count:
                            type: integer
                      timestamps:
                        type: object
                        properties:
                          primary:
                            type: boolean
                          secondary:
                            type: boolean
                      digital_signature:
                        type: boolean
                      retention_years:
                        type: integer
                      hash_algorithms:
                        type: array
                        items:
                          type: string
                        nullable: true
                      evidence_version:
                        type: string
                        nullable: true
                      identity_evidence:
                        type: array
                        items:
                          type: object
                          properties:
                            signer_id:
                              type: string
                            role:
                              type: string
                            status:
                              type: string
                            phone_verified:
                              type: boolean
                            has_signature:
                              type: boolean
                            has_audio:
                              type: boolean
                            has_video:
                              type: boolean
                            signed_at:
                              type: string
                              format: date-time
                              nullable: true
                      warnings:
                        type: array
                        items:
                          type: string
                required:
                - complete
                - checklist
        '404':
          description: Envelope not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/email-log:
    get:
      summary: Email Delivery Log
      description: Query email delivery log. JWT auth required. Supports filtering by recipient, envelope_id, and status.
      operationId: getEmailLog
      tags:
      - System
      parameters:
      - name: recipient
        in: query
        schema:
          type: string
          format: email
      - name: envelope_id
        in: query
        schema:
          type: string
          format: uuid
      - name: status
        in: query
        schema:
          type: string
          enum:
          - delivered
          - failed
          - queued
      - name: limit
        in: query
        schema:
          type: integer
          default: 50
          maximum: 200
      - name: offset
        in: query
        schema:
          type: integer
          default: 0
      responses:
        '200':
          description: Email log entries
          content:
            application/json:
              schema:
                type: object
                properties:
                  emails:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        envelope_id:
                          type: string
                        recipient:
                          type: string
                        subject:
                          type: string
                        template_alias:
                          type: string
                        provider:
                          type: string
                        status:
                          type: string
                        error:
                          type: string
                          nullable: true
                        created_at:
                          type: string
                          format: date-time
                        delivered_at:
                          type: string
                          format: date-time
                          nullable: true
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
