openapi: 3.1.0
info:
  title: Verify API
  version: '0.1.0'
  summary: Persistent counterparty intelligence attached to agreements.
  description: |
    Verify is the trust infrastructure for contracts. Every counterparty extracted from
    an agreement is screened, monitored continuously after signature, and exposed as a
    machine-readable record over HTTPS, MCP, and webhooks.

    This reference documents the production HTTPS surface. Endpoints are versioned via
    the host header (`api.verify.inc`) and the base path is `/api`. The DocuSign
    integration lives under `/integrations/docusign` (no `/api` prefix).

    Verify is in **private beta**. Reach `viridiana@verify.inc` to request access.

servers:
  - url: https://api.verify.inc/api
    description: Production
  - url: http://localhost:3001/api
    description: Local development

# ────────────────────────────────────────────────────────────────────
# Tags — used to group operations in rendered docs.
# Order here is the developer story: contract in → counterparty out →
# reports → monitoring → integrations. Workspace/account plumbing the
# Verify web app uses (Authentication, Leads) is demoted to the end.
# ────────────────────────────────────────────────────────────────────
tags:
  - name: Contracts
    description: Upload agreements, extract counterparties, and run verification.
  - name: Counterparties
    description: Persistent counterparty profiles, decision history, and linked records.
  - name: Reports
    description: Verification records produced from screened counterparties.
  - name: Monitoring
    description: Continuous monitoring lifecycle and on-demand runs after signature.
  - name: DocuSign
    description: OAuth connection and integration with DocuSign.
  - name: Runs
    description: Verification run history for the current workspace.
  - name: Billing
    description: Plan status, Stripe checkout, and customer portal.
  - name: Authentication
    description: Workspace sign-up and sign-in. Used by the Verify web application.
  - name: Webhooks
    description: Event payloads delivered to your registered endpoints. Private preview.

# ────────────────────────────────────────────────────────────────────
# Tag groups — top-level sidebar sections (Scalar / Redoc convention).
# Ordered as the developer workflow: authenticate → upload a contract →
# identify counterparties → generate intelligence → monitor risk →
# receive events. Counterparties lead the object model and sit ahead of
# account management (Workspace), which is last. Only groups backed by
# real, public endpoints are wired up here. Getting Started, Webhooks,
# Developers (MCP, Models), and the remaining Integrations are documented
# narratively at /docs until their endpoints ship.
# ────────────────────────────────────────────────────────────────────
x-tagGroups:
  - name: Contracts
    tags: [Contracts]
  - name: Counterparties
    tags: [Counterparties]
  - name: Reports
    tags: [Reports]
  - name: Monitoring
    tags: [Monitoring]
  - name: Integrations
    tags: [DocuSign]
  - name: Workspace
    tags: [Runs, Billing, Authentication]

# ────────────────────────────────────────────────────────────────────
# Security
# ────────────────────────────────────────────────────────────────────
security:
  - ApiKey: []

components:
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        Workspace API key. Sent on every request alongside the user JWT for
        authenticated routes. Self-serve workspace key issuance (planned format
        `vk_live_*` / `vk_test_*` for sandbox separation) ships with the v1 API.
        Today, request a key via viridiana@verify.inc.
    Bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Bearer JWT issued by `/auth/login` or `/auth/signup`. Required for any operation
        that reads or mutates workspace-owned data (counterparties, runs, billing,
        monitoring, DocuSign integration).
    ClaimToken:
      type: apiKey
      in: header
      name: X-Claim-Token
      description: |
        Anonymous claim token returned from `/contracts/upload` for unauthenticated
        flows. Allows an anonymous user to access the contract they just uploaded
        before creating an account. Token is single-contract scoped and short-lived.

  # ────────────────────────────────────────────────────────────────
  # Schemas — derived from the production TypeScript types in api.ts
  # ────────────────────────────────────────────────────────────────
  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
          example: Unauthorized
        message:
          type: string
        request_id:
          type: string
          example: req_8x_42a...

    AuthResponse:
      type: object
      properties:
        token:
          type: string
          description: JWT bearer token. Store and send on subsequent authenticated requests.
        userId:
          type: string
        message:
          type: string

    UploadResponse:
      type: object
      required: [id, filename, status, message]
      properties:
        id:
          type: string
          description: Contract identifier.
          example: ctr_8xq...
        filename:
          type: string
        status:
          type: string
          enum: [uploaded, extracting, extracted, screening, completed, failed]
        message:
          type: string
        claimToken:
          type: string
          description: Anonymous claim token. Returned only when the upload was anonymous.

    PartyFindings:
      type: object
      properties:
        sanctionsHit: { type: boolean }
        courtCases: { type: integer }
        businessStatus: { type: string }
        registeredJurisdiction: { type: string }

    EvidenceItem:
      type: object
      required: [source]
      properties:
        source: { type: string }
        checkedAt: { type: string, format: date-time }
        confidence: { type: number, format: float }
        coverageNotes: { type: string }
        errors:
          type: array
          items: { type: string }
        matches:
          type: array
          items:
            type: object
            properties:
              data: { type: object, additionalProperties: true }
              confidence: { type: number, format: float }

    PartyEvidence:
      type: object
      properties:
        sanctions:        { type: array, items: { $ref: '#/components/schemas/EvidenceItem' } }
        businessIdentity: { type: array, items: { $ref: '#/components/schemas/EvidenceItem' } }
        webPresence:      { type: array, items: { $ref: '#/components/schemas/EvidenceItem' } }
        litigation:       { type: array, items: { $ref: '#/components/schemas/EvidenceItem' } }
        regulatory:       { type: array, items: { $ref: '#/components/schemas/EvidenceItem' } }
        identityConsistency: { type: array, items: { $ref: '#/components/schemas/EvidenceItem' } }

    PartyIdentityDetails:
      type: object
      properties:
        submittedEmail: { type: string }
        submittedPhone: { type: string }
        submittedAddress: { type: string }
        knownAliases:   { type: array, items: { type: string } }
        knownAddresses: { type: array, items: { type: string } }
        knownPhones:    { type: array, items: { type: string } }
        knownEmails:    { type: array, items: { type: string } }
        nameMatchResult:
          type: string
          enum: [exact, partial, none, not_checked]
        phoneType: { type: string }

    Party:
      type: object
      required: [name, type]
      properties:
        _id: { type: string }
        name: { type: string }
        type:
          type: string
          enum: [business, person, unknown]
        role: { type: string }
        confirmed: { type: boolean }
        confidence: { type: number, format: float }
        riskScore: { type: number, format: float }
        riskBadge:
          type: string
          enum: [green, orange, red]
        riskReasons:
          type: array
          items: { type: string }
        limitedData: { type: boolean }
        status: { type: string }
        evidence: { $ref: '#/components/schemas/PartyEvidence' }
        identityDetails: { $ref: '#/components/schemas/PartyIdentityDetails' }
        findings: { $ref: '#/components/schemas/PartyFindings' }

    ContractUnderstandingRow:
      type: object
      properties:
        field: { type: string }
        value: { type: string }
        source_quote: { type: string }

    ContractUnderstanding:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [pending, processing, completed, failed]
        processedAt: { type: string, format: date-time }
        parties:
          type: array
          items:
            type: object
            required: [name, role]
            properties:
              name: { type: string }
              role: { type: string }
              source_quote: { type: string }
        signers:
          type: array
          items:
            type: object
            required: [name]
            properties:
              name: { type: string }
              title: { type: string, nullable: true }
              source_quote: { type: string }
        key_terms:
          type: array
          items: { $ref: '#/components/schemas/ContractUnderstandingRow' }
        tabular_review:
          type: array
          items: { $ref: '#/components/schemas/ContractUnderstandingRow' }
        suggestions:
          type: array
          items: { type: string }
        workflow_recommendation:
          type: string
          enum: [ready_for_verification, needs_user_confirmation, manual_review_recommended]
        error: { type: string }

    Contract:
      type: object
      required: [id, filename, status, uploadedAt]
      properties:
        id: { type: string }
        filename: { type: string }
        status: { type: string }
        uploadedAt: { type: string, format: date-time }
        extractedParties:
          type: array
          items: { $ref: '#/components/schemas/Party' }
        contractUnderstanding:
          oneOf:
            - $ref: '#/components/schemas/ContractUnderstanding'
            - type: 'null'

    RunReport:
      type: object
      required: [reportId, partyName]
      properties:
        reportId: { type: string }
        partyName: { type: string }
        riskScore: { type: number, format: float }
        riskBadge:
          type: string
          enum: [green, orange, red]

    Run:
      type: object
      required: [_id, originalName, status, createdAt, source, reports]
      properties:
        _id: { type: string }
        originalName: { type: string }
        status: { type: string }
        createdAt: { type: string, format: date-time }
        source: { type: string }
        extractedParties:
          type: array
          items: { $ref: '#/components/schemas/Party' }
        reports:
          type: array
          items: { $ref: '#/components/schemas/RunReport' }

    IngestionContext:
      type: object
      required: [source, ingestionTimestamp]
      properties:
        source: { type: string }
        sourceRunId:
          type: string
          nullable: true
        initiatedBy:
          type: string
          nullable: true
        workflowId:
          type: string
          nullable: true
        ingestionTimestamp: { type: string, format: date-time }

    RiskEvent:
      type: object
      required: [_id, eventType, severity, ingestionContext, createdAt]
      properties:
        _id: { type: string, example: ev_4ax_T62d }
        eventType:
          type: string
          description: |
            Typed event name. Production event types include
            `screening_completed`, `sanctions_flag_added`, `sanctions_flag_cleared`,
            `registry_status_changed`, `risk_badge_changed`, `monitoring_enabled`,
            `monitoring_disabled`, `address_mismatch`, `litigation_found`,
            `verification_rerun`, `contract_linked`.
        severity:
          type: string
          enum: [info, warning, critical]
        ingestionContext: { $ref: '#/components/schemas/IngestionContext' }
        metadata:
          type: object
          additionalProperties: true
        contractId:
          type: string
          nullable: true
        partyId:
          type: string
          nullable: true
        createdAt: { type: string, format: date-time }

    MonitoringRun:
      type: object
      required: [_id, status, ingestionContext, startedAt, createdAt]
      properties:
        _id: { type: string }
        status:
          type: string
          enum: [running, completed, failed, skipped]
        ingestionContext: { $ref: '#/components/schemas/IngestionContext' }
        startedAt: { type: string, format: date-time }
        completedAt:
          type: string
          format: date-time
          nullable: true
        result:
          type: object
          properties:
            changesDetected: { type: boolean }
            eventsGenerated: { type: integer }
            highestSeverity:
              type: string
              enum: [info, warning, critical]
              nullable: true
        error:
          type: string
          nullable: true
        createdAt: { type: string, format: date-time }

    CounterpartyProfile:
      type: object
      required: [_id, canonicalName, type, screeningCount, createdAt, monitoringEnabled]
      properties:
        _id: { type: string, example: cp_3kn9... }
        canonicalName: { type: string }
        type:
          type: string
          enum: [business, person, unknown]
        currentRiskBadge:
          type: string
          enum: [green, orange, red]
          nullable: true
        currentRiskScore:
          type: number
          format: float
          nullable: true
        lastRiskSummary:
          type: string
          nullable: true
        domain:
          type: string
          nullable: true
        aliases:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              normalizedName: { type: string }
              seenAt: { type: string, format: date-time }
        screeningCount: { type: integer }
        firstScreenedAt:
          type: string
          format: date-time
          nullable: true
        lastScreenedAt:
          type: string
          format: date-time
          nullable: true
        monitoringEnabled: { type: boolean }
        monitoringFrequency:
          type: string
          enum: [daily, weekly, monthly]
          nullable: true
        createdAt: { type: string, format: date-time }

    BillingStatus:
      type: object
      required: [planType, billingStatus, seatCount, cancelAtPeriodEnd]
      properties:
        planType:
          type: string
          enum: [free, starter, team, enterprise]
          description: |
            Internal plan code. Display labels remap: `starter`/`team` render as
            "Usage", `enterprise` renders as "Platform".
        billingStatus:
          type: string
          enum: [free, trialing, active, past_due, canceled, incomplete, unpaid]
        seatCount: { type: integer }
        currentPeriodEnd:
          type: string
          format: date-time
          nullable: true
        cancelAtPeriodEnd: { type: boolean }
        trialEnd:
          type: string
          format: date-time
          nullable: true
        lastPaymentAt:
          type: string
          format: date-time
          nullable: true

    DocuSignStatus:
      type: object
      required: [connected]
      properties:
        connected: { type: boolean }
        dsAccountId: { type: string }
        connectedAt: { type: string, format: date-time }
        tokenExpires: { type: string, format: date-time }

    # NOTE: Outbound webhook delivery is in private preview. Today, the canonical
    # event surface is the per-counterparty timeline endpoint, which returns
    # `RiskEvent` objects (see above). WebhookEvent shape stabilizes at v1.
    WebhookEvent:
      type: object
      description: |
        **Private preview.** Outbound webhook payload shape. Subject to change
        before v1. Today, the same typed events are retrievable via
        `GET /counterparties/{id}/timeline`.
      required: [eventType, severity, createdAt]
      properties:
        eventType:
          type: string
          description: |
            Canonical event type. Matches `RiskEvent.eventType`:
            `screening_completed`, `sanctions_flag_added`, `sanctions_flag_cleared`,
            `registry_status_changed`, `risk_badge_changed`, `monitoring_enabled`,
            `monitoring_disabled`, `address_mismatch`, `litigation_found`,
            `verification_rerun`, `contract_linked`.
        severity:
          type: string
          enum: [info, warning, critical]
        counterpartyId:
          type: string
          nullable: true
        contractId:
          type: string
          nullable: true
        metadata:
          type: object
          additionalProperties: true
        createdAt: { type: string, format: date-time }

# ────────────────────────────────────────────────────────────────────
# Paths
# ────────────────────────────────────────────────────────────────────
paths:

  # ── Auth ──────────────────────────────────────────────────────────
  /auth/signup:
    post:
      tags: [Authentication]
      summary: Create a workspace
      description: |
        Create a new workspace account. Returns a Bearer JWT to use on subsequent
        authenticated requests. If a `claimToken` is included, any anonymous contract
        previously uploaded with that token is attached to the new account.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string, minLength: 8 }
                claimToken: { type: string }
      responses:
        '200': { description: Workspace created, content: { application/json: { schema: { $ref: '#/components/schemas/AuthResponse' } } } }
        '400': { description: Invalid request, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        '409': { description: Email already registered, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }

  /auth/login:
    post:
      tags: [Authentication]
      summary: Sign in
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string }
                claimToken: { type: string }
      responses:
        '200': { description: Authenticated, content: { application/json: { schema: { $ref: '#/components/schemas/AuthResponse' } } } }
        '401': { description: Invalid credentials, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }

  /auth/forgot-password:
    post:
      tags: [Authentication]
      x-internal: true
      summary: Begin password reset
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }
      responses:
        '200': { description: Reset email queued }

  /auth/reset-password:
    post:
      tags: [Authentication]
      x-internal: true
      summary: Complete password reset
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token, password]
              properties:
                token: { type: string }
                password: { type: string, minLength: 8 }
      responses:
        '200': { description: Password updated }
        '400': { description: Token invalid or expired, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }

  # ── Contracts ─────────────────────────────────────────────────────
  /contracts/upload:
    post:
      tags: [Contracts]
      summary: Upload an agreement
      description: |
        Upload a contract (PDF). The response returns extracted parties asynchronously.
        Poll `GET /contracts/{id}` until `status === 'extracted'` to read parties.

        Anonymous uploads return a `claimToken`. Pass that token on subsequent requests
        via the `X-Claim-Token` header until the user creates an account.
      security:
        - ApiKey: []
        - ApiKey: []
          Bearer: []
        - ApiKey: []
          ClaimToken: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [contract]
              properties:
                contract:
                  type: string
                  format: binary
      responses:
        '200': { description: Upload accepted, content: { application/json: { schema: { $ref: '#/components/schemas/UploadResponse' } } } }
        '400': { description: Invalid file, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        '413': { description: File too large, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }

  /contracts/{contractId}:
    get:
      tags: [Contracts]
      summary: Get contract + extracted parties
      security:
        - ApiKey: []
        - ApiKey: []
          Bearer: []
        - ApiKey: []
          ClaimToken: []
      parameters:
        - in: path
          name: contractId
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Contract record
          content:
            application/json:
              schema:
                type: object
                properties:
                  contract: { $ref: '#/components/schemas/Contract' }
                  parties:
                    type: array
                    items: { $ref: '#/components/schemas/Party' }
        '404': { description: Not found, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }

  /contracts/{contractId}/confirm-parties:
    post:
      tags: [Contracts]
      summary: Confirm extracted parties before screening
      security:
        - ApiKey: []
          Bearer: []
        - ApiKey: []
          ClaimToken: []
      parameters:
        - in: path
          name: contractId
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [parties]
              properties:
                parties:
                  type: array
                  items: { $ref: '#/components/schemas/Party' }
      responses:
        '200':
          description: Parties confirmed
          content:
            application/json:
              schema:
                type: object
                properties:
                  parties:
                    type: array
                    items: { $ref: '#/components/schemas/Party' }

  /contracts/{contractId}/run-risk:
    post:
      tags: [Contracts]
      summary: Run verification on confirmed parties
      description: |
        Dispatches sanctions, litigation, registry, ownership, and behavioral screening
        against the confirmed parties. Returns immediately; poll `GET /contracts/{id}`
        until `status === 'completed'` or each party's `status === 'completed'`.
      security:
        - ApiKey: []
          Bearer: []
        - ApiKey: []
          ClaimToken: []
      parameters:
        - in: path
          name: contractId
          required: true
          schema: { type: string }
      responses:
        '200': { description: Screening dispatched }
        '409': { description: Already running, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }

  /contracts/{contractId}/understand:
    post:
      tags: [Contracts]
      summary: Run contract intelligence on the agreement
      description: |
        Extracts structured fields, signers, key terms, and a workflow recommendation
        from the agreement text. Asynchronous; poll the contract until
        `contractUnderstanding.status === 'completed'`.
      security:
        - ApiKey: []
          Bearer: []
        - ApiKey: []
          ClaimToken: []
      parameters:
        - in: path
          name: contractId
          required: true
          schema: { type: string }
      responses:
        '200': { description: Analysis dispatched }
        '409': { description: Already processing }

  /contracts/{contractId}/lead:
    post:
      tags: [Contracts]
      x-internal: true
      summary: Submit lead email for an anonymous contract
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: contractId
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }
      responses:
        '200': { description: Email captured }

  # ── Reports ───────────────────────────────────────────────────────
  /reports/generate/{partyId}:
    post:
      tags: [Reports]
      summary: Generate a verification record for a screened party
      security:
        - ApiKey: []
        - ApiKey: []
          Bearer: []
        - ApiKey: []
          ClaimToken: []
      parameters:
        - in: path
          name: partyId
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Record generated
          content:
            application/json:
              schema:
                type: object
                required: [reportId, pdfUrl]
                properties:
                  reportId: { type: string }
                  pdfUrl: { type: string, format: uri }

  /reports/{reportId}:
    get:
      tags: [Reports]
      summary: Get a verification record
      description: |
        Returns the full record. May return `403` with `{ "error": "Email required" }`
        for anonymous flows that require lead capture before unlocking.
      security:
        - ApiKey: []
        - ApiKey: []
          Bearer: []
        - ApiKey: []
          ClaimToken: []
      parameters:
        - in: path
          name: reportId
          required: true
          schema: { type: string }
      responses:
        '200': { description: Record returned }
        '403':
          description: Email required (gated anonymous flow)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error: { type: string, example: Email required }

  /reports/{reportId}/pdf:
    get:
      tags: [Reports]
      summary: Download the verification record as PDF
      security:
        - ApiKey: []
        - ApiKey: []
          Bearer: []
        - ApiKey: []
          ClaimToken: []
      parameters:
        - in: path
          name: reportId
          required: true
          schema: { type: string }
      responses:
        '200':
          description: PDF binary
          content:
            application/pdf:
              schema:
                type: string
                format: binary

  /reports/{reportId}/html:
    get:
      tags: [Reports]
      summary: Render the verification record as HTML
      security:
        - ApiKey: []
        - ApiKey: []
          Bearer: []
        - ApiKey: []
          ClaimToken: []
      parameters:
        - in: path
          name: reportId
          required: true
          schema: { type: string }
      responses:
        '200':
          description: HTML
          content:
            text/html:
              schema:
                type: string

  /reports/{reportId}/lead:
    post:
      tags: [Reports]
      x-internal: true
      summary: Submit lead email to unlock a gated record
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: reportId
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }
      responses:
        '200': { description: Email captured. Record now accessible. }

  # ── Runs ──────────────────────────────────────────────────────────
  /runs:
    get:
      tags: [Runs]
      summary: List verification runs for the current workspace
      security:
        - ApiKey: []
          Bearer: []
      responses:
        '200':
          description: Run history
          content:
            application/json:
              schema:
                type: object
                properties:
                  runs:
                    type: array
                    items: { $ref: '#/components/schemas/Run' }

  # ── Counterparties ────────────────────────────────────────────────
  /counterparties:
    get:
      tags: [Counterparties]
      summary: List counterparty profiles
      security:
        - ApiKey: []
          Bearer: []
      parameters:
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: limit
          schema: { type: integer, default: 20 }
        - in: query
          name: riskBadge
          schema: { type: string, enum: [green, orange, red] }
        - in: query
          name: type
          schema: { type: string, enum: [business, person, unknown] }
      responses:
        '200':
          description: Paged counterparty profiles
          content:
            application/json:
              schema:
                type: object
                required: [profiles, total, page, limit]
                properties:
                  profiles:
                    type: array
                    items: { $ref: '#/components/schemas/CounterpartyProfile' }
                  total: { type: integer }
                  page: { type: integer }
                  limit: { type: integer }

  /counterparties/{id}:
    get:
      tags: [Counterparties]
      summary: Get a counterparty profile
      security:
        - ApiKey: []
          Bearer: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Counterparty record
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CounterpartyProfile' }
        '404': { description: Not found }

  /counterparties/{id}/timeline:
    get:
      tags: [Counterparties]
      summary: Get the append-only event stream for a counterparty
      description: |
        Returns the operational history of this counterparty as typed events.
        Events are append-only and server-timestamped. Paged.
      security:
        - ApiKey: []
          Bearer: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: limit
          schema: { type: integer, default: 50 }
      responses:
        '200':
          description: Event stream
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items: { $ref: '#/components/schemas/RiskEvent' }
                  total: { type: integer }
                  page: { type: integer }
                  limit: { type: integer }

  /counterparties/{id}/contracts:
    get:
      tags: [Counterparties]
      summary: List agreements linked to a counterparty
      security:
        - ApiKey: []
          Bearer: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Linked agreements
          content:
            application/json:
              schema:
                type: object
                properties:
                  contracts:
                    type: array
                    items:
                      type: object
                      required: [_id, originalName, status, createdAt]
                      properties:
                        _id: { type: string }
                        originalName: { type: string }
                        status: { type: string }
                        createdAt: { type: string, format: date-time }

  /counterparties/{id}/reports:
    get:
      tags: [Counterparties]
      summary: List verification records linked to a counterparty
      security:
        - ApiKey: []
          Bearer: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Linked records
          content:
            application/json:
              schema:
                type: object
                properties:
                  reports:
                    type: array
                    items:
                      type: object
                      required: [_id, partyName, createdAt]
                      properties:
                        _id: { type: string }
                        partyName: { type: string }
                        reportData:
                          type: object
                          properties:
                            riskBadge: { type: string, enum: [green, orange, red] }
                            riskScore: { type: number, format: float }
                        createdAt: { type: string, format: date-time }

  /counterparties/{id}/monitoring:
    patch:
      tags: [Monitoring]
      summary: Update monitoring settings
      security:
        - ApiKey: []
          Bearer: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled: { type: boolean }
                frequency:
                  type: string
                  enum: [daily, weekly, monthly]
      responses:
        '200':
          description: Updated profile
          content:
            application/json:
              schema:
                type: object
                properties:
                  profile: { $ref: '#/components/schemas/CounterpartyProfile' }

  /counterparties/{id}/monitoring/trigger:
    post:
      tags: [Monitoring]
      summary: Trigger an on-demand monitoring run
      security:
        - ApiKey: []
          Bearer: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Run started, or skipped if one is already in progress.
          content:
            application/json:
              schema:
                type: object
                properties:
                  run: { $ref: '#/components/schemas/MonitoringRun' }
                  skipped: { type: boolean }
                  reason: { type: string }

  /counterparties/{id}/monitoring/runs:
    get:
      tags: [Monitoring]
      summary: List monitoring runs for a counterparty
      security:
        - ApiKey: []
          Bearer: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
        - in: query
          name: limit
          schema: { type: integer, default: 20 }
      responses:
        '200':
          description: Monitoring run history
          content:
            application/json:
              schema:
                type: object
                properties:
                  runs:
                    type: array
                    items: { $ref: '#/components/schemas/MonitoringRun' }

  # ── Billing ───────────────────────────────────────────────────────
  /billing/status:
    get:
      tags: [Billing]
      summary: Get billing status for the current workspace
      security:
        - ApiKey: []
          Bearer: []
      responses:
        '200':
          description: Billing status
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BillingStatus' }

  /billing/checkout:
    post:
      tags: [Billing]
      summary: Create a Stripe Checkout session
      security:
        - ApiKey: []
          Bearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [planType]
              properties:
                planType:
                  type: string
                  enum: [starter, team]
      responses:
        '200':
          description: Checkout URL
          content:
            application/json:
              schema:
                type: object
                required: [url]
                properties:
                  url: { type: string, format: uri }

  /billing/portal:
    post:
      tags: [Billing]
      summary: Create a Stripe Customer Portal session
      security:
        - ApiKey: []
          Bearer: []
      responses:
        '200':
          description: Portal URL
          content:
            application/json:
              schema:
                type: object
                required: [url]
                properties:
                  url: { type: string, format: uri }

  # ── Leads ─────────────────────────────────────────────────────────
  /leads/demo:
    post:
      tags: [Leads]
      x-internal: true
      summary: Submit a platform-access or demo request
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [firstName, lastName, email, company, useCase]
              properties:
                firstName: { type: string }
                lastName: { type: string }
                email: { type: string, format: email }
                company: { type: string }
                useCase: { type: string }
      responses:
        '200': { description: Request submitted }

  # ── DocuSign integration ──────────────────────────────────────────
  # NOTE: DocuSign routes do NOT live under /api. They are exposed at
  # `/integrations/docusign/...` on the same backend host.
  /integrations/docusign/oauth/status:
    servers:
      - url: https://api.verify.inc
    get:
      tags: [DocuSign]
      summary: Get DocuSign connection status for the current workspace
      security:
        - Bearer: []
      responses:
        '200':
          description: Connection status
          content:
            application/json:
              schema: { $ref: '#/components/schemas/DocuSignStatus' }

  /integrations/docusign/oauth/connect-url:
    servers:
      - url: https://api.verify.inc
    get:
      tags: [DocuSign]
      summary: Get the DocuSign OAuth authorization URL
      description: |
        Redirect the user to the returned URL. DocuSign will redirect back to the
        platform with a code that the backend exchanges for tokens. The dashboard
        then receives a `?docusign=connected|denied|error` query parameter.
      security:
        - Bearer: []
      responses:
        '200':
          description: Authorization URL
          content:
            application/json:
              schema:
                type: object
                required: [url]
                properties:
                  url: { type: string, format: uri }

  /integrations/docusign/oauth/disconnect:
    servers:
      - url: https://api.verify.inc
    delete:
      tags: [DocuSign]
      summary: Disconnect DocuSign from the current workspace
      security:
        - Bearer: []
      responses:
        '200': { description: Disconnected }

# ────────────────────────────────────────────────────────────────────
# Webhooks — private preview
# ────────────────────────────────────────────────────────────────────
#
# Outbound webhook delivery is in private preview. Final delivery and signing
# semantics ship with the v1 release. Today, the canonical event surface is the
# per-counterparty timeline endpoint:
#
#   GET /counterparties/{id}/timeline → RiskEvent[]
#
# The intended webhook payload shape mirrors `WebhookEvent` (see schemas).
# Per-event-type operations will be enumerated here once finalized.

webhooks: {}
