openapi: 3.0.3
info:
  title: eContract API
  version: 1.0.0
  description: |-
    REST API of eContract, the e-signature platform: create and send contracts, follow signing,
    download signed PDFs and audit trails. AI agents can also use the remote MCP server at
    `https://api.econtract.online/mcp`.

    ## Authentication
    Send `Authorization: Bearer <credential>` with an API key (`cl_live_…`, created under
    Dashboard → API Keys) or an OAuth 2.1 access token (`eco_at_…`). Each operation lists the scope
    it needs (`x-econtract-scope`). This reference lists the public endpoints and everything API keys
    and OAuth tokens can call; the web app uses further session-only endpoints that are not part of the
    public API. Signing and declining are always done by a person.

    ## Rate limits
    Per API key or OAuth grant: 60 requests/minute, 1,000/hour, 10,000/day. Responses carry
    `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (seconds until the minute
    window resets); `429` adds `Retry-After`.

    Guides: https://developers.econtract.online
  contact:
    name: eContract
    url: https://econtract.online
    email: contact@econtract.online
servers:
  - url: https://api.econtract.online
    description: Production
externalDocs:
  description: eContract developer guides
  url: https://developers.econtract.online
security:
  - bearerAuth: []
  - oauth2: []
tags:
  - name: activities
    description: Contract activity log.
  - name: ai
    description: AI text extraction and contract analysis (`ai:use`).
  - name: auth
    description: Login and the current principal (`GET /api/v1/auth/me` works with API keys and OAuth tokens).
  - name: contracts
    description: Create, send, track and void contracts; signing links, signed PDF and audit trail.
  - name: files
    description: File upload and download.
  - name: guides
    description: Public guides.
  - name: otp
    description: One-time passwords for signer identity checks.
  - name: Public Verify
    description: Public verification of signed PDFs.
  - name: signers
    description: Signers of a contract and the external signer address book.
  - name: signing
    description: Signer-facing signing flow (token based, used by the signing page).
  - name: system-templates
    description: Public system template catalogue.
  - name: templates
    description: Contract templates and generating contracts from them.
  - name: tokens
    description: Signing token validation.
  - name: verify
    description: Public verification of signed PDFs and contracts.
  - name: webhooks
    description: Webhook endpoints, delivery log and retries (workspace admin or owner).
  - name: workflows
    description: Signing workflow state and events.
paths:
  /api/v1/ai/analyze-contract:
    post:
      operationId: AnalysisController_analyzeContract
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AnalyzeContractRequest'
      responses:
        '200':
          description: ''
      tags:
        - ai
      x-econtract-auth: machine
      x-econtract-scope: ai:use
      security:
        - bearerAuth: []
        - oauth2:
            - ai:use
  /api/v1/ai/contract-prefill:
    post:
      operationId: PrefillController_contractPrefill
      summary: Extract contract prefill data from an uploaded file
      responses:
        '200':
          description: Prefill data extracted successfully
        '400':
          description: Invalid request or unsupported file type
        '403':
          description: Rate limit or cost limit exceeded
        '404':
          description: File not found in storage
      tags:
        - ai
      x-econtract-auth: machine
      x-econtract-scope: ai:use
      security:
        - bearerAuth: []
        - oauth2:
            - ai:use
  /api/v1/ai/extract-text:
    post:
      operationId: ExtractionController_extractText
      summary: Extract text from a PDF file
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: PDF file to extract text from (max 50MB)
              required:
                - file
      responses:
        '200':
          description: Text extracted successfully
        '400':
          description: Invalid file or extraction error
        '413':
          description: File too large
      tags:
        - ai
      x-econtract-auth: machine
      x-econtract-scope: ai:use
      security:
        - bearerAuth: []
        - oauth2:
            - ai:use
  /api/v1/auth/accept-invite:
    post:
      operationId: AuthController_acceptInvite
      summary: Accept workspace invitation
      responses:
        '200':
          description: ''
      tags:
        - auth
      x-econtract-auth: public
      security: []
  /api/v1/auth/forgot-password:
    post:
      operationId: AuthController_forgotPassword
      summary: Request password reset
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ForgotPasswordDto'
      responses:
        '200':
          description: ''
      tags:
        - auth
      x-econtract-auth: public
      security: []
  /api/v1/auth/login:
    post:
      operationId: AuthController_login
      summary: User login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginDto'
      responses:
        '200':
          description: Login successful
      tags:
        - auth
      x-econtract-auth: public
      security: []
  /api/v1/auth/me:
    get:
      operationId: AuthController_me
      summary: Get current user profile
      responses:
        '200':
          description: ''
      tags:
        - auth
      x-econtract-auth: machine
      security:
        - bearerAuth: []
        - oauth2: []
  /api/v1/auth/register:
    post:
      operationId: AuthController_register
      summary: Register a new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterDto'
      responses:
        '201':
          description: User registered
        '429':
          description: 'USAGE_LIMIT_REACHED: too many signups from this IP today'
      tags:
        - auth
      x-econtract-auth: public
      security: []
  /api/v1/auth/register/google:
    post:
      operationId: AuthController_registerGoogle
      responses:
        '201':
          description: ''
      tags:
        - auth
      x-econtract-auth: public
      security: []
  /api/v1/auth/register/microsoft:
    post:
      operationId: AuthController_registerMicrosoft
      responses:
        '201':
          description: ''
      tags:
        - auth
      x-econtract-auth: public
      security: []
  /api/v1/auth/resend-verification:
    post:
      operationId: AuthController_resendVerification
      summary: Resend verification email
      responses:
        '200':
          description: ''
      tags:
        - auth
      x-econtract-auth: public
      security: []
  /api/v1/auth/reset-password:
    post:
      operationId: AuthController_resetPassword
      summary: Reset password with token
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResetPasswordDto'
      responses:
        '200':
          description: ''
      tags:
        - auth
      x-econtract-auth: public
      security: []
  /api/v1/auth/verify-email:
    post:
      operationId: AuthController_verifyEmail
      summary: Verify email address
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyEmailDto'
      responses:
        '200':
          description: ''
      tags:
        - auth
      x-econtract-auth: public
      security: []
  /api/v1/contract-templates:
    post:
      operationId: TemplateController_create
      summary: Create template
      responses:
        '201':
          description: Template created successfully
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:write
      security:
        - bearerAuth: []
        - oauth2:
            - templates:write
    get:
      operationId: TemplateController_findAll
      summary: List templates
      parameters:
        - name: page
          required: true
          in: query
          schema:
            type: string
        - name: limit
          required: true
          in: query
          schema:
            type: string
        - name: type
          required: true
          in: query
          schema:
            type: string
        - name: category
          required: true
          in: query
          schema:
            type: string
        - name: search
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Templates retrieved successfully
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:read
      security:
        - bearerAuth: []
        - oauth2:
            - templates:read
  /api/v1/contract-templates/{id}:
    get:
      operationId: TemplateController_findById
      summary: Get template by ID
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Template retrieved successfully
        '404':
          description: Template not found
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:read
      security:
        - bearerAuth: []
        - oauth2:
            - templates:read
    put:
      operationId: TemplateController_update
      summary: Update template
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Template updated successfully
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:write
      security:
        - bearerAuth: []
        - oauth2:
            - templates:write
    delete:
      operationId: TemplateController_archive
      summary: Archive template
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Template archived successfully
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:delete
      security:
        - bearerAuth: []
        - oauth2:
            - templates:delete
  /api/v1/contract-templates/{id}/extract-variables:
    post:
      operationId: TemplateController_extractVariables
      summary: Extract variables from template file
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:write
      security:
        - bearerAuth: []
        - oauth2:
            - templates:write
  /api/v1/contract-templates/{id}/generate:
    post:
      operationId: TemplateController_generate
      summary: Generate contract from template
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GenerateFromTemplateDto'
      responses:
        '201':
          description: Contract created from template
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contract-templates/{id}/preview:
    post:
      operationId: TemplateController_preview
      summary: Preview template with values
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:read
      security:
        - bearerAuth: []
        - oauth2:
            - templates:read
  /api/v1/contract-templates/categories:
    get:
      operationId: TemplateController_getCategories
      summary: List template categories
      responses:
        '200':
          description: ''
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:read
      security:
        - bearerAuth: []
        - oauth2:
            - templates:read
  /api/v1/contracts:
    post:
      operationId: ContractsController_create
      summary: Create contract
      parameters:
        - name: Idempotency-Key
          in: header
          description: >-
            Up to 255 characters. Retries with the same key and body replay the first result (header
            Idempotent-Replayed: true) for 24 h.
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateContractDto'
      responses:
        '201':
          description: Contract created successfully
        '400':
          description: Invalid request
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
    get:
      operationId: ContractsController_findAll
      summary: List contracts
      parameters:
        - name: page
          required: false
          in: query
          schema:
            minimum: 1
            default: 1
            type: number
        - name: limit
          required: false
          in: query
          description: Values above 100 are capped
          schema:
            minimum: 1
            maximum: 100
            default: 20
            type: number
        - name: status
          required: false
          in: query
          schema:
            enum:
              - draft
              - pending
              - in_signing
              - completed
              - voided
              - expired
            type: string
        - name: search
          required: false
          in: query
          description: Matches code, title or description
          schema:
            type: string
        - name: createdBy
          required: false
          in: query
          description: Creator user id
          schema:
            type: string
        - name: fromDate
          required: false
          in: query
          description: Created at or after (ISO 8601)
          schema:
            type: string
        - name: toDate
          required: false
          in: query
          description: Created at or before (ISO 8601)
          schema:
            type: string
      responses:
        '200':
          description: Contracts retrieved successfully
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
  /api/v1/contracts/{contractId}/activities:
    get:
      operationId: ActivitiesController_findByContractId
      summary: List activities for a contract
      parameters:
        - name: contractId
          required: true
          in: path
          schema:
            type: string
        - name: page
          required: true
          in: query
          schema:
            type: string
        - name: limit
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - activities
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
  /api/v1/contracts/{contractId}/activities/verify-chain:
    get:
      operationId: ActivitiesController_verifyChain
      summary: Verify activity chain integrity
      parameters:
        - name: contractId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - activities
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
  /api/v1/contracts/{contractId}/signers:
    post:
      operationId: SignersController_addSigner
      summary: Add signer to contract
      parameters:
        - name: contractId
          required: true
          in: path
          schema:
            type: string
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddSignerDto'
      responses:
        '201':
          description: Signer added successfully
        '400':
          description: Invalid request or duplicate signer
      tags:
        - signers
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
    get:
      operationId: SignersController_findByContractId
      summary: List signers for contract
      parameters:
        - name: contractId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Signers retrieved successfully
      tags:
        - signers
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
  /api/v1/contracts/{contractId}/signers/{id}:
    get:
      operationId: SignersController_findById
      summary: Get signer by ID
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Signer retrieved successfully
        '404':
          description: Signer not found
      tags:
        - signers
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
    delete:
      operationId: SignersController_removeSigner
      summary: Remove signer from contract
      parameters:
        - name: contractId
          required: true
          in: path
          schema:
            type: string
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Signer removed successfully
        '400':
          description: Cannot remove signed signer
        '404':
          description: Signer not found
      tags:
        - signers
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/{id}:
    get:
      operationId: ContractsController_findById
      summary: Get contract by ID
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: includeSigners
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Contract retrieved successfully
        '404':
          description: Contract not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
    put:
      operationId: ContractsController_update
      summary: Update contract (draft only)
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateContractDto'
      responses:
        '200':
          description: Contract updated successfully
        '400':
          description: Cannot update non-draft contract
        '404':
          description: Contract not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
    delete:
      operationId: ContractsController_delete
      summary: Delete contract (draft only)
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Contract deleted successfully
        '400':
          description: Cannot delete non-draft contract
        '404':
          description: Contract not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:delete
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:delete
  /api/v1/contracts/{id}/accept-ai-suggestions:
    post:
      operationId: ContractsController_acceptAISuggestions
      summary: Accept AI suggestions and update contract title/description
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AcceptAISuggestionsDto'
      responses:
        '200':
          description: AI suggestions accepted
        '400':
          description: Contract is not in draft state
        '404':
          description: Contract not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/{id}/ai-suggestions:
    post:
      operationId: ContractsController_getAISuggestions
      summary: '[Deprecated] AI suggestions — delegates to file-driven prefill'
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AISuggestRequestDto'
      responses:
        '200':
          description: Prefill data extracted from source file
        '400':
          description: Prefill failed or not available
        '404':
          description: Contract or file not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/{id}/audit-trail:
    get:
      operationId: ContractsController_auditTrail
      summary: 'Audit trail: signers, events, hash-chain check and document hash'
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditTrailResponseDto'
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
  /api/v1/contracts/{id}/document:
    get:
      operationId: ContractsController_document
      summary: Download the contract PDF
      description: >-
        version=signed (409 DOCUMENT_NOT_SIGNED until completed), version=original (404 ORIGINAL_NOT_AVAILABLE when not
        retained), default: signed when completed else the current file. Headers X-Document-Version and
        X-Document-Sha256.
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: version
          required: false
          in: query
          description: 'Default: signed when completed, else the current file'
          schema:
            enum:
              - signed
              - original
            type: string
        - name: fileId
          required: false
          in: query
          description: 'Contract file id (multi-file contracts; default: the primary file)'
          schema:
            type: string
      responses:
        '200':
          description: PDF bytes
        '404':
          description: Contract / file not found, or ORIGINAL_NOT_AVAILABLE
        '409':
          description: DOCUMENT_NOT_SIGNED or DOCUMENT_NOT_READY
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
  /api/v1/contracts/{id}/files:
    get:
      operationId: ContractsController_listFiles
      summary: List contract files
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Files retrieved successfully
        '404':
          description: Contract not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
    post:
      operationId: ContractsController_addFile
      summary: Add file to contract (draft only)
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddContractFileDto'
      responses:
        '201':
          description: File added successfully
        '400':
          description: Cannot add file to non-draft contract
        '404':
          description: Contract not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/{id}/files/{fileId}:
    delete:
      operationId: ContractsController_removeFile
      summary: Remove file from contract (draft only)
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: fileId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: File removed successfully
        '400':
          description: Cannot remove file from non-draft contract
        '404':
          description: Contract or file not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/{id}/files/{fileId}/retry-conversion:
    post:
      operationId: ContractsController_retryConversion
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: fileId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/{id}/prefill:
    post:
      operationId: ContractsController_getContractPrefill
      summary: File-driven contract prefill — extract title, summary, signers from source document
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContractPrefillRequestDto'
      responses:
        '200':
          description: Prefill data extracted from source file
        '400':
          description: Prefill failed or not available
        '404':
          description: Contract or file not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/{id}/resend-signing-request:
    post:
      operationId: ContractsController_resendSigningRequest
      summary: Resend signing request notifications to pending signers
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: Idempotency-Key
          in: header
          description: >-
            Up to 255 characters. Retries with the same key and body replay the first result (header
            Idempotent-Replayed: true) for 24 h.
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResendSigningRequestDto'
      responses:
        '200':
          description: Signing requests resent successfully
        '400':
          description: Contract is not in signing state
        '404':
          description: Contract not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/{id}/resolve-candidates:
    post:
      operationId: ContractsController_resolveCandidates
      summary: Resolve signer candidates against internal users and external signers
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResolveSignerCandidatesRequestDto'
      responses:
        '200':
          description: Candidates resolved successfully
        '400':
          description: Invalid request
        '404':
          description: Contract not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/{id}/signing-links:
    post:
      operationId: ContractsController_signingLinks
      summary: Get signing links for the signers (no email is sent)
      description: >-
        Mints a fresh token for every external signer who has not signed or declined (older links stay valid). Internal
        signers get the dashboard link. 409 unless the contract is pending or in signing.
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: Idempotency-Key
          in: header
          description: >-
            Up to 255 characters. Retries with the same key and body replay the first result (header
            Idempotent-Replayed: true) for 24 h.
          required: false
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SigningLinksResponseDto'
        '409':
          description: CONTRACT_NOT_IN_SIGNING
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/{id}/submit:
    post:
      operationId: ContractsController_submit
      summary: Submit contract for signing
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: Idempotency-Key
          in: header
          description: >-
            Up to 255 characters. Retries with the same key and body replay the first result (header
            Idempotent-Replayed: true) for 24 h.
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitContractDto'
      responses:
        '200':
          description: Contract submitted successfully
        '400':
          description: Invalid contract state
        '404':
          description: Contract not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/{id}/void:
    post:
      operationId: ContractsController_void
      summary: Void contract
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: Idempotency-Key
          in: header
          description: >-
            Up to 255 characters. Retries with the same key and body replay the first result (header
            Idempotent-Replayed: true) for 24 h.
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VoidContractDto'
      responses:
        '200':
          description: Contract voided successfully
        '400':
          description: Cannot void contract
        '404':
          description: Contract not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/ai/feature-flags:
    get:
      operationId: ContractsController_getAIFeatureFlags
      summary: Get AI feature flags for the current tenant/plan
      responses:
        '200':
          description: Feature flags retrieved
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
  /api/v1/contracts/code/{code}:
    get:
      operationId: ContractsController_findByCode
      summary: Get contract by code
      parameters:
        - name: code
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Contract retrieved successfully
        '404':
          description: Contract not found
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
  /api/v1/contracts/send:
    post:
      operationId: ContractsController_send
      summary: Create and send contract in one call (JSON with base64/URL files, or multipart upload)
      parameters:
        - name: Idempotency-Key
          in: header
          description: >-
            Up to 255 characters. Retries with the same key and body replay the first result (header
            Idempotent-Replayed: true) for 24 h.
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/SendContractDto'
                - type: object
                  properties:
                    files:
                      type: array
                      items:
                        type: string
                        format: binary
                      description: PDF or DOCX files (max 10, max 50MB each)
                    metadata:
                      type: string
                      description: Stringified JSON SendContractDto (without files)
          multipart/form-data:
            schema:
              oneOf:
                - $ref: '#/components/schemas/SendContractDto'
                - type: object
                  properties:
                    files:
                      type: array
                      items:
                        type: string
                        format: binary
                      description: PDF or DOCX files (max 10, max 50MB each)
                    metadata:
                      type: string
                      description: Stringified JSON SendContractDto (without files)
      responses:
        '202':
          description: Contract created and queued for processing
        '400':
          description: Invalid request (missing file, signers, or invalid email)
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:write
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:write
  /api/v1/contracts/stats:
    get:
      operationId: ContractsController_getStats
      summary: Get contract statistics
      parameters:
        - name: userId
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Statistics retrieved successfully
      tags:
        - contracts
      x-econtract-auth: machine
      x-econtract-scope: contracts:read
      security:
        - bearerAuth: []
        - oauth2:
            - contracts:read
  /api/v1/external-signers:
    post:
      operationId: SignersController_create
      summary: Create external signer
      responses:
        '201':
          description: External signer created successfully
        '409':
          description: Email already exists
      tags:
        - signers
      x-econtract-auth: machine
      x-econtract-scope: signing:write
      security:
        - bearerAuth: []
        - oauth2:
            - signing:write
    get:
      operationId: SignersController_findAll
      summary: List external signers
      responses:
        '200':
          description: External signers retrieved successfully
      tags:
        - signers
      x-econtract-auth: machine
      x-econtract-scope: signing:read
      security:
        - bearerAuth: []
        - oauth2:
            - signing:read
  /api/v1/external-signers/{id}:
    get:
      operationId: SignersController_findById
      summary: Get external signer by ID
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: External signer retrieved successfully
        '404':
          description: External signer not found
      tags:
        - signers
      x-econtract-auth: machine
      x-econtract-scope: signing:read
      security:
        - bearerAuth: []
        - oauth2:
            - signing:read
    put:
      operationId: SignersController_update
      summary: Update external signer
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: External signer updated successfully
        '404':
          description: External signer not found
      tags:
        - signers
      x-econtract-auth: machine
      x-econtract-scope: signing:write
      security:
        - bearerAuth: []
        - oauth2:
            - signing:write
    delete:
      operationId: SignersController_delete
      summary: Delete external signer
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: External signer deleted successfully
        '404':
          description: External signer not found
      tags:
        - signers
      x-econtract-auth: machine
      x-econtract-scope: signing:write
      security:
        - bearerAuth: []
        - oauth2:
            - signing:write
  /api/v1/external-signers/audit-trail:
    get:
      operationId: SigningController_getAuditTrail
      summary: Get contract audit trail via signing token
      parameters:
        - name: token
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - signing
      x-econtract-auth: public
      security: []
  /api/v1/external-signers/contract-file:
    get:
      operationId: SigningController_getContractFile
      summary: Download contract file via signing token
      parameters:
        - name: token
          required: true
          in: query
          schema:
            type: string
        - name: fileId
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: File stream returned
        '404':
          description: Token or file not found
      tags:
        - signing
      x-econtract-auth: public
      security: []
  /api/v1/external-signers/decline:
    post:
      operationId: SigningController_declineContract
      summary: Decline contract as external signer
      responses:
        '200':
          description: Contract declined successfully
        '404':
          description: Token not found
      tags:
        - signing
      x-econtract-auth: public
      security: []
  /api/v1/external-signers/sign:
    post:
      operationId: SigningController_signContract
      summary: Sign contract as external signer
      responses:
        '200':
          description: Contract signed successfully
        '400':
          description: OTP not verified or invalid state
        '404':
          description: Token not found
      tags:
        - signing
      x-econtract-auth: public
      security: []
  /api/v1/external-signers/verify:
    post:
      operationId: SigningController_verifyToken
      summary: Verify signing token and get contract info
      responses:
        '200':
          description: Token verified, contract info returned
        '404':
          description: Token not found or invalid
      tags:
        - signing
      x-econtract-auth: public
      security: []
  /api/v1/external-signers/verify-chain:
    get:
      operationId: SigningController_verifyChain
      summary: Verify contract integrity chain via signing token
      parameters:
        - name: token
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - signing
      x-econtract-auth: public
      security: []
  /api/v1/files/{fileKey}:
    get:
      operationId: FilesController_downloadFile
      summary: Download file (streaming)
      parameters:
        - name: fileKey
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: File stream
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:read
      security:
        - bearerAuth: []
        - oauth2:
            - files:read
    delete:
      operationId: FilesController_deleteFile
      summary: Delete file
      parameters:
        - name: fileKey
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: File deleted
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:delete
      security:
        - bearerAuth: []
        - oauth2:
            - files:delete
  /api/v1/files/{fileKey}/metadata:
    get:
      operationId: FilesController_getFileMetadata
      summary: Get file metadata
      parameters:
        - name: fileKey
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: File metadata
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:read
      security:
        - bearerAuth: []
        - oauth2:
            - files:read
  /api/v1/files/{fileKey}/url:
    get:
      operationId: FilesController_getDownloadUrl
      summary: Get presigned download URL
      parameters:
        - name: fileKey
          required: true
          in: path
          schema:
            type: string
        - name: expiryMinutes
          required: true
          in: query
          schema:
            type: number
      responses:
        '200':
          description: Download URL generated
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:read
      security:
        - bearerAuth: []
        - oauth2:
            - files:read
  /api/v1/files/download:
    get:
      operationId: FilesController_downloadFileByKey
      summary: Download file by key query param (streaming)
      parameters:
        - name: key
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: File stream
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:read
      security:
        - bearerAuth: []
        - oauth2:
            - files:read
  /api/v1/files/metadata:
    get:
      operationId: FilesController_getFileMetadataByKey
      summary: Get file metadata by query param
      parameters:
        - name: key
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: File metadata
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:read
      security:
        - bearerAuth: []
        - oauth2:
            - files:read
  /api/v1/files/upload:
    post:
      operationId: FilesController_uploadFile
      summary: Upload file directly (max 50MB)
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                fileName:
                  type: string
                fileType:
                  type: string
                entityType:
                  type: string
                entityId:
                  type: string
      responses:
        '201':
          description: ''
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:write
      security:
        - bearerAuth: []
        - oauth2:
            - files:write
  /api/v1/files/upload/multipart/{uploadId}:
    delete:
      operationId: FilesController_abortMultipartUpload
      summary: Abort multipart upload
      parameters:
        - name: uploadId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Multipart upload aborted
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:write
      security:
        - bearerAuth: []
        - oauth2:
            - files:write
  /api/v1/files/upload/multipart/complete:
    post:
      operationId: FilesController_completeMultipartUpload
      summary: Complete multipart upload
      responses:
        '200':
          description: Multipart upload completed
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:write
      security:
        - bearerAuth: []
        - oauth2:
            - files:write
  /api/v1/files/upload/multipart/initiate:
    post:
      operationId: FilesController_initiateMultipartUpload
      summary: Initiate multipart upload for large files
      responses:
        '200':
          description: Multipart upload initiated
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:write
      security:
        - bearerAuth: []
        - oauth2:
            - files:write
  /api/v1/files/upload/multipart/part-url:
    post:
      operationId: FilesController_getPartUploadUrl
      summary: Get presigned URL for uploading a part
      responses:
        '200':
          description: Part upload URL generated
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:write
      security:
        - bearerAuth: []
        - oauth2:
            - files:write
  /api/v1/files/upload/url:
    post:
      operationId: FilesController_getUploadUrl
      summary: Get presigned upload URL for client-side upload
      responses:
        '200':
          description: Upload URL generated
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:write
      security:
        - bearerAuth: []
        - oauth2:
            - files:write
  /api/v1/files/url:
    get:
      operationId: FilesController_getDownloadUrlByKey
      summary: Get presigned download URL by query param
      parameters:
        - name: key
          required: true
          in: query
          schema:
            type: string
        - name: expiryMinutes
          required: true
          in: query
          schema:
            type: number
      responses:
        '200':
          description: Download URL generated
      tags:
        - files
      x-econtract-auth: machine
      x-econtract-scope: files:read
      security:
        - bearerAuth: []
        - oauth2:
            - files:read
  /api/v1/guides:
    get:
      operationId: GuidesController_findAll
      summary: List guides
      parameters:
        - name: locale
          required: true
          in: query
          schema:
            type: string
        - name: category
          required: true
          in: query
          schema:
            type: string
        - name: page
          required: true
          in: query
          schema:
            type: number
        - name: limit
          required: true
          in: query
          schema:
            type: number
      responses:
        '200':
          description: ''
      tags:
        - guides
      x-econtract-auth: public
      security: []
  /api/v1/guides/{slug}:
    get:
      operationId: GuidesController_findBySlug
      summary: Get guide by slug
      parameters:
        - name: slug
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - guides
      x-econtract-auth: public
      security: []
  /api/v1/guides/categories:
    get:
      operationId: GuidesController_getCategories
      summary: List guide categories
      parameters:
        - name: locale
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - guides
      x-econtract-auth: public
      security: []
  /api/v1/otp/send:
    post:
      operationId: OtpController_sendOtp
      summary: Send OTP to signer email
      responses:
        '200':
          description: OTP sent successfully
        '400':
          description: Invalid request
        '429':
          description: Rate limited
      tags:
        - otp
      x-econtract-auth: public
      security: []
  /api/v1/otp/verify:
    post:
      operationId: OtpController_verifyOtp
      summary: Verify OTP code
      responses:
        '200':
          description: OTP verification result
        '400':
          description: Invalid OTP or max attempts exceeded
      tags:
        - otp
      x-econtract-auth: public
      security: []
  /api/v1/signing-tokens/{token}/status:
    get:
      operationId: TokensController_getTokenStatus
      summary: Get signing token status (including OTP verification)
      parameters:
        - name: token
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Token status retrieved
        '404':
          description: Token not found
      tags:
        - tokens
      x-econtract-auth: machine
      x-econtract-scope: signing:read
      security:
        - bearerAuth: []
        - oauth2:
            - signing:read
  /api/v1/signing-tokens/validate:
    post:
      operationId: TokensController_validate
      summary: Validate signing token
      responses:
        '200':
          description: Token is valid
        '401':
          description: Token is invalid or expired
        '404':
          description: Token not found
      tags:
        - tokens
      x-econtract-auth: public
      security: []
  /api/v1/system-templates:
    get:
      operationId: TemplatesController_findAll
      summary: List system templates
      parameters:
        - name: category
          required: true
          in: query
          schema:
            type: string
        - name: locale
          required: true
          in: query
          schema:
            type: string
        - name: country
          required: true
          in: query
          schema:
            type: string
        - name: search
          required: true
          in: query
          schema:
            type: string
        - name: active
          required: true
          in: query
          schema:
            type: string
        - name: page
          required: true
          in: query
          schema:
            type: number
        - name: limit
          required: true
          in: query
          schema:
            type: number
      responses:
        '200':
          description: ''
      tags:
        - system-templates
      x-econtract-auth: public
      security: []
  /api/v1/system-templates/{slug}:
    get:
      operationId: TemplatesController_findBySlug
      summary: Get template by slug
      parameters:
        - name: slug
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - system-templates
      x-econtract-auth: public
      security: []
  /api/v1/system-templates/categories:
    get:
      operationId: TemplatesController_getCategories
      summary: List template categories
      parameters:
        - name: locale
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - system-templates
      x-econtract-auth: public
      security: []
  /api/v1/templates:
    get:
      operationId: TemplatesController_findAll
      summary: List all templates
      responses:
        '200':
          description: Templates retrieved successfully
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:read
      security:
        - bearerAuth: []
        - oauth2:
            - templates:read
    post:
      operationId: TemplatesController_create
      summary: Create new template
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTemplateDto'
      responses:
        '201':
          description: Template created successfully
        '409':
          description: Template code already exists
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:write
      security:
        - bearerAuth: []
        - oauth2:
            - templates:write
  /api/v1/templates/{id}:
    get:
      operationId: TemplatesController_findById
      summary: Get template by ID
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Template retrieved successfully
        '404':
          description: Template not found
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:read
      security:
        - bearerAuth: []
        - oauth2:
            - templates:read
    put:
      operationId: TemplatesController_update
      summary: Update template
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTemplateDto'
      responses:
        '200':
          description: Template updated successfully
        '404':
          description: Template not found
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:write
      security:
        - bearerAuth: []
        - oauth2:
            - templates:write
    delete:
      operationId: TemplatesController_delete
      summary: Delete template
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Template deleted successfully
        '404':
          description: Template not found
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:delete
      security:
        - bearerAuth: []
        - oauth2:
            - templates:delete
  /api/v1/templates/code/{code}:
    get:
      operationId: TemplatesController_findByCode
      summary: Get template by code
      parameters:
        - name: code
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Template retrieved successfully
        '404':
          description: Template not found
      tags:
        - templates
      x-econtract-auth: machine
      x-econtract-scope: templates:read
      security:
        - bearerAuth: []
        - oauth2:
            - templates:read
  /api/v1/verify/contract/{code}:
    get:
      operationId: VerifyController_verifyByContract
      summary: Verify signatures for a contract by code
      parameters:
        - name: code
          required: true
          in: path
          schema:
            type: string
        - name: tenant
          required: true
          in: query
          description: Workspace code
          schema:
            type: string
      responses:
        '200':
          description: Verification results
        '404':
          description: Contract not found
      tags:
        - verify
      x-econtract-auth: public
      security: []
  /api/v1/verify/pdf:
    post:
      operationId: VerifyController_verifyPdf
      summary: Verify digital signatures in an uploaded PDF
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
      responses:
        '200':
          description: Verification results
        '400':
          description: Invalid file
        '429':
          description: Too many requests
      tags:
        - verify
      x-econtract-auth: public
      security: []
  /api/v1/webhooks:
    post:
      operationId: WebhooksController_createWebhook
      summary: Create a webhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookDto'
      responses:
        '201':
          description: ''
      tags:
        - webhooks
      x-econtract-auth: machine
      x-econtract-scope: webhooks:write
      security:
        - bearerAuth: []
        - oauth2:
            - webhooks:write
    get:
      operationId: WebhooksController_listWebhooks
      summary: List webhooks for workspace
      responses:
        '200':
          description: ''
      tags:
        - webhooks
      x-econtract-auth: machine
      x-econtract-scope: webhooks:read
      security:
        - bearerAuth: []
        - oauth2:
            - webhooks:read
  /api/v1/webhooks/{id}:
    get:
      operationId: WebhooksController_getWebhook
      summary: Get webhook detail with recent deliveries
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - webhooks
      x-econtract-auth: machine
      x-econtract-scope: webhooks:read
      security:
        - bearerAuth: []
        - oauth2:
            - webhooks:read
    patch:
      operationId: WebhooksController_updateWebhook
      summary: Update a webhook
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWebhookDto'
      responses:
        '200':
          description: ''
      tags:
        - webhooks
      x-econtract-auth: machine
      x-econtract-scope: webhooks:write
      security:
        - bearerAuth: []
        - oauth2:
            - webhooks:write
    delete:
      operationId: WebhooksController_deleteWebhook
      summary: Delete a webhook
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - webhooks
      x-econtract-auth: machine
      x-econtract-scope: webhooks:write
      security:
        - bearerAuth: []
        - oauth2:
            - webhooks:write
  /api/v1/webhooks/{id}/deliveries:
    get:
      operationId: WebhooksController_listDeliveries
      summary: List webhook deliveries with pagination
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: page
          required: true
          in: query
          schema:
            type: string
        - name: limit
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - webhooks
      x-econtract-auth: machine
      x-econtract-scope: webhooks:read
      security:
        - bearerAuth: []
        - oauth2:
            - webhooks:read
  /api/v1/webhooks/{id}/test:
    post:
      operationId: WebhooksController_testWebhook
      summary: Send test webhook event
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - webhooks
      x-econtract-auth: machine
      x-econtract-scope: webhooks:write
      security:
        - bearerAuth: []
        - oauth2:
            - webhooks:write
  /api/v1/webhooks/deliveries/{deliveryId}/retry:
    post:
      operationId: WebhooksController_retryDelivery
      summary: Retry a failed delivery
      parameters:
        - name: deliveryId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - webhooks
      x-econtract-auth: machine
      x-econtract-scope: webhooks:write
      security:
        - bearerAuth: []
        - oauth2:
            - webhooks:write
  /api/v1/workflows/events/{contractId}:
    get:
      operationId: WorkflowsController_getWorkflowEvents
      summary: Get workflow events for a contract
      parameters:
        - name: contractId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - workflows
      x-econtract-auth: machine
      x-econtract-scope: workflow:read
      security:
        - bearerAuth: []
        - oauth2:
            - workflow:read
  /api/v1/workflows/state/{contractId}:
    get:
      operationId: WorkflowsController_getWorkflowState
      summary: Get workflow state for a contract
      parameters:
        - name: contractId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      tags:
        - workflows
      x-econtract-auth: machine
      x-econtract-scope: workflow:read
      security:
        - bearerAuth: []
        - oauth2:
            - workflow:read
  /verify-pdf:
    post:
      operationId: PublicVerifyController_verifyPdf
      summary: Verify digital signatures in a PDF document
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
      responses:
        '200':
          description: Verification results
        '400':
          description: Invalid file or no signatures
        '429':
          description: Rate limit exceeded
      tags:
        - Public Verify
      x-econtract-auth: public
      security: []
components:
  schemas:
    AcceptAISuggestionsDto:
      type: object
      properties:
        title:
          type: string
        description:
          type: string
    AddContractFileDto:
      type: object
      properties: {}
    AddSignerDto:
      type: object
      properties:
        signerType:
          type: string
          enum:
            - internal
            - external
          description: internal = workspace user, external = anyone by email (default external)
        signerId:
          type: string
          description: Tenant user id (required for internal signers)
        externalSignerId:
          type: string
          description: External signer (address book) id
        email:
          type: string
          description: Signer email (required)
          example: ceo@partner.vn
        name:
          type: string
          example: Tran Thi B
        signOrder:
          type: number
          description: Position in sequential signing (1-based)
          example: 1
        metadata:
          type: object
          additionalProperties: true
    AISuggestRequestDto:
      type: object
      properties: {}
    AnalyzeContractRequest:
      type: object
      properties: {}
    AuditTrailChainDto:
      type: object
      properties:
        valid:
          type: boolean
          description: Hash chain of the contract activities verifies
        checkedAt:
          type: string
        totalEvents:
          type: number
        chainedEvents:
          type: number
        verifiedEvents:
          type: number
        unchainedEvents:
          type: number
        brokenAt:
          type: number
          description: 1-based position of the first broken link
      required:
        - valid
        - checkedAt
    AuditTrailContractDto:
      type: object
      properties:
        id:
          type: string
        code:
          type: string
        title:
          type: string
        status:
          type: string
        createdAt:
          type: string
        completedAt:
          type: object
          nullable: true
      required:
        - id
        - code
        - title
        - status
        - createdAt
        - completedAt
    AuditTrailEventDto:
      type: object
      properties:
        type:
          type: string
          description: >-
            Activity action (created, submitted, signed, ...) or workflow event (signer_notified, signer_viewed,
            signer_signed, ...)
        source:
          type: string
          enum:
            - activity
            - workflow
          description: activity = hash-chained contract activity; workflow = signing workflow event
        actorName:
          type: object
          nullable: true
        actorEmail:
          type: object
          nullable: true
        at:
          type: string
        ip:
          type: object
          nullable: true
        userAgent:
          type: object
          nullable: true
        details:
          type: object
          nullable: true
          additionalProperties: true
      required:
        - type
        - source
        - actorName
        - actorEmail
        - at
        - ip
        - userAgent
        - details
    AuditTrailResponseDto:
      type: object
      properties:
        contract:
          $ref: '#/components/schemas/AuditTrailContractDto'
        signers:
          type: array
          items:
            $ref: '#/components/schemas/AuditTrailSignerDto'
        events:
          type: array
          items:
            $ref: '#/components/schemas/AuditTrailEventDto'
        chain:
          $ref: '#/components/schemas/AuditTrailChainDto'
        documentSha256:
          type: object
          nullable: true
          description: SHA-256 of the document GET /contracts/:id/document returns by default
        documentVersion:
          type: string
          nullable: true
          enum:
            - signed
            - current
      required:
        - contract
        - signers
        - events
        - chain
        - documentSha256
        - documentVersion
    AuditTrailSignerDto:
      type: object
      properties:
        id:
          type: string
        name:
          type: object
          nullable: true
        email:
          type: string
        signerType:
          type: string
          enum:
            - internal
            - external
        status:
          type: string
        signOrder:
          type: number
        notifiedAt:
          type: object
          nullable: true
        viewedAt:
          type: object
          nullable: true
        signedAt:
          type: object
          nullable: true
        rejectedAt:
          type: object
          nullable: true
        rejectionReason:
          type: object
          nullable: true
        ipAddress:
          type: object
          nullable: true
      required:
        - id
        - name
        - email
        - signerType
        - status
        - signOrder
    ContractPrefillRequestDto:
      type: object
      properties: {}
    CreateContractDto:
      type: object
      properties:
        title:
          type: string
          example: Service Agreement - Q2 2026
        description:
          type: string
        fileKey:
          type: string
          description: Key of a file uploaded with POST /files/upload
        fileName:
          type: string
        fileSize:
          type: number
        fileHash:
          type: string
        fileType:
          type: string
          enum:
            - pdf
            - docx
        signingOrderType:
          type: string
          enum:
            - sequential
            - parallel
          default: sequential
        expiresInDays:
          type: number
          description: Days until the contract expires (default 90)
          example: 30
        metadata:
          type: object
          additionalProperties: true
      required:
        - title
    CreateTemplateDto:
      type: object
      properties: {}
    CreateWebhookDto:
      type: object
      properties: {}
    ForgotPasswordDto:
      type: object
      properties: {}
    GenerateFromTemplateDto:
      type: object
      properties:
        values:
          type: object
          additionalProperties:
            type: string
          example:
            companyName: Acme Corp
        title:
          type: string
          example: Service Agreement - Acme
        description:
          type: string
        signingOrderType:
          type: string
          enum:
            - sequential
            - parallel
          default: sequential
        expiresInDays:
          type: number
          example: 30
      required:
        - values
        - title
    LoginDto:
      type: object
      properties: {}
    RegisterDto:
      type: object
      properties: {}
    ResendSigningRequestDto:
      type: object
      properties:
        signerId:
          type: string
          description: 'Only this signer (default: every pending signer)'
    ResetPasswordDto:
      type: object
      properties: {}
    ResolveSignerCandidatesRequestDto:
      type: object
      properties: {}
    SendContractDto:
      type: object
      properties:
        title:
          type: string
          example: Service Agreement - Q2 2026
        description:
          type: string
        signingOrderType:
          type: string
          enum:
            - sequential
            - parallel
          default: sequential
        signers:
          description: At least one signer
          type: array
          items:
            $ref: '#/components/schemas/AddSignerDto'
        sendNotifications:
          type: boolean
          default: true
          description: Legacy switch; prefer delivery. false = no invitation emails
        delivery:
          type: string
          enum:
            - email
            - link
            - both
          default: email
          description: 'email/both: invitation emails; link: no emails, get links from POST /contracts/:id/signing-links'
        files:
          maxItems: 10
          description: 'JSON uploads: base64 content or https URL (multipart requests use the files field instead)'
          type: array
          items:
            $ref: '#/components/schemas/SendContractFileDto'
        fileKey:
          type: string
          description: Key of a previously uploaded file
        fileName:
          type: string
        fileSize:
          type: number
        fileHash:
          type: string
        templateId:
          type: string
          description: Generate the document from this template
        templateValues:
          type: object
          additionalProperties:
            type: string
          description: Required with templateId
        expiresInDays:
          type: number
          default: 90
          example: 30
        metadata:
          type: object
          additionalProperties: true
        useAiTitle:
          type: boolean
        useAiDescription:
          type: boolean
      required:
        - title
        - signers
    SendContractFileDto:
      type: object
      properties:
        filename:
          type: string
          description: File name ending in .pdf or .docx (required with contentBase64)
          example: nda.pdf
        contentBase64:
          type: string
          description: Base64 file content (max 50 MB decoded). Use either contentBase64 or url.
        contentType:
          type: string
          enum:
            - application/pdf
            - application/vnd.openxmlformats-officedocument.wordprocessingml.document
        url:
          type: string
          description: https URL to fetch the file from (public host, no redirects, 30 s, 50 MB)
          example: https://example.com/nda.pdf
    SigningLinkDto:
      type: object
      properties:
        signerId:
          type: string
        name:
          type: object
          nullable: true
        email:
          type: string
        signerType:
          type: string
          enum:
            - internal
            - external
        status:
          type: string
          enum:
            - pending
            - notified
            - viewed
            - signed
            - rejected
            - expired
        signingUrl:
          type: object
          nullable: true
          description: 'External: /sign/<token>; internal: dashboard link; null once signed/declined'
        expiresAt:
          type: object
          nullable: true
          description: Token expiry (external signers)
      required:
        - signerId
        - name
        - email
        - signerType
        - status
        - signingUrl
        - expiresAt
    SigningLinksResponseDto:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SigningLinkDto'
      required:
        - data
    SubmitContractDto:
      type: object
      properties:
        sendNotifications:
          type: boolean
          default: true
          description: false = no invitation emails (distribute links yourself)
    UpdateContractDto:
      type: object
      properties:
        title:
          type: string
        description:
          type: string
        fileKey:
          type: string
        fileName:
          type: string
        fileSize:
          type: number
        fileHash:
          type: string
        fileType:
          type: string
          enum:
            - pdf
            - docx
        signingOrderType:
          type: string
          enum:
            - sequential
            - parallel
        expiresAt:
          type: string
          description: ISO 8601 date-time
          example: '2026-12-31T00:00:00.000Z'
        metadata:
          type: object
          additionalProperties: true
          description: Replaces the metadata object (e.g. signatureConfig)
    UpdateTemplateDto:
      type: object
      properties: {}
    UpdateWebhookDto:
      type: object
      properties: {}
    VerifyEmailDto:
      type: object
      properties: {}
    VoidContractDto:
      type: object
      properties:
        reason:
          type: string
          example: Terms changed
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        API key `cl_live_…` or OAuth access token `eco_at_…` in `Authorization: Bearer`. Public operations
        (`x-econtract-auth: public`) need no credential.
    oauth2:
      type: oauth2
      description: >-
        OAuth 2.1 authorization code flow with PKCE (S256 only). Discovery:
        https://api.econtract.online/.well-known/oauth-authorization-server. Clients register with a Client ID Metadata
        Document or dynamic client registration (https://api.econtract.online/oauth/register). Ask for
        resource=https://api.econtract.online to call this API. See https://developers.econtract.online/docs/oauth.
      flows:
        authorizationCode:
          authorizationUrl: https://api.econtract.online/oauth/authorize
          tokenUrl: https://api.econtract.online/oauth/token
          refreshUrl: https://api.econtract.online/oauth/token
          scopes:
            contracts:read: View contracts, their status, signed documents and audit trails
            contracts:write: Create, send, update and void contracts, and issue signing links
            contracts:delete: Delete contracts
            templates:read: View contract templates
            templates:write: Create and update contract templates
            templates:delete: Delete contract templates
            signing:read: View signing sessions and signer data
            signing:write: Manage signing sessions (never signs on your behalf)
            workflow:read: View workflow status
            workflow:write: Run workflow actions
            files:read: Download files
            files:write: Upload and update files
            files:delete: Delete files
            webhooks:read: View webhooks and their delivery logs
            webhooks:write: Create, update, test and delete webhooks
            ai:use: Use AI features such as contract analysis
            offline_access: Stay connected when you are not using the app (refresh tokens)
