---
openapi: 3.0.0
paths:
  "/auth/login":
    post:
      description: Signs in a full user and returns an access token, a refresh token
        and the user's profile with their organizations, and creates a session. Wrong
        credentials, an inactive account or an extraction-only user get 401. Each
        organization entry carries the caller's permission and the org's billing summary.
      operationId: AuthController_login
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/LoginEmailDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - accessToken
                    - refreshToken
                    - userProfile
                    properties:
                      accessToken:
                        type: string
                        example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2NWExYjJjMyJ9.c2lnbmF0dXJl
                      refreshToken:
                        type: string
                        example: b7c1e2f0a9d84c3e8f6a5b4c3d2e1f00
                      userProfile:
                        "$ref": "#/components/schemas/UserEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Sign in with email and password
      tags:
      - auth
  "/auth/generate-token":
    post:
      description: With `scopes`, mints a scoped automation token (lifetime from `ttlDays`
        or the configured default, 365 days when unset, never more than 400) and returns
        the token once with its id, label, scopes and expiry; returns 403 while automation
        tokens are turned off and 400 for an empty or unsupported scope list. Without
        `scopes`, mints a legacy full-access token valid for 5 years and returns only
        `token` and `validTill` (no id); sending `label` or `ttlDays` without `scopes`
        returns 400. An inactive user gets 401.
      operationId: AuthController_generateToken
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/GenerateTokenDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/GenerateTokenResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a long-lived API token
      tags:
      - auth
  "/auth/tokens":
    get:
      description: Lists the caller's scoped automation tokens, newest first, with
        label, scopes, last use, expiry and revoked flag. Token values are never returned,
        and legacy tokens are not listed.
      operationId: AuthController_listTokens
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/AutomationTokenListEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List my automation tokens
      tags:
      - auth
  "/auth/tokens/{id}":
    delete:
      description: Marks one of the caller's automation tokens as revoked. Works even
        while automation tokens are turned off. Returns 404 when the id is not one
        of the caller's automation tokens.
      operationId: AuthController_revokeToken
      parameters:
      - name: id
        required: true
        in: path
        description: Automation token (session) id
        schema:
          type: string
      responses:
        '204':
          description: Token revoked. No body.
      security:
      - access-token: []
      summary: Revoke an automation token
      tags:
      - auth
  "/auth/tokens/{id}/scopes":
    patch:
      description: Replaces the scopes of one of the caller's automation tokens (duplicates
        removed; an empty list disables the token without deleting it) and returns
        the updated token. Returns 403 while automation tokens are turned off, 400
        for an unsupported scope and 404 when the id is not one of the caller's automation
        tokens.
      operationId: AuthController_updateTokenScopes
      parameters:
      - name: id
        required: true
        in: path
        description: Automation token (session) id
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateTokenScopesDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AutomationTokenListEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Change an automation token's scopes
      tags:
      - auth
  "/auth/refresh":
    post:
      description: Issues a new access token for a refresh token and returns it with
        the same refresh token. The user profile is not included. Returns 404 for
        an unknown refresh token and 400 when it has expired or its user no longer
        exists.
      operationId: AuthController_refresh
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RefreshTokenDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RefreshTokenResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Refresh the access token
      tags:
      - auth
  "/auth/logout":
    post:
      description: Ends the current session by deleting the session of the bearer
        token. `data` is always an empty object.
      operationId: AuthController_logout
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: Always empty
                    properties: {}
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Sign out
      tags:
      - auth
  "/auth/register":
    post:
      description: Creates the user and, unless they already belong to one, an organization
        named from `organizationName` (or from the user's name) with the user as owner,
        invites `inviteEmails`, records acceptance of the terms, then signs the user
        in. Returns access and refresh tokens, the user's profile and the organization.
        Returns 400 when the terms are not accepted, the organization name is taken
        or the user cannot be created. Each organization entry carries the caller's
        permission and the org's billing summary.
      operationId: AuthController_register
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RegisterEmailDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - accessToken
                    - refreshToken
                    - userProfile
                    properties:
                      accessToken:
                        type: string
                        example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2NWExYjJjMyJ9.c2lnbmF0dXJl
                      refreshToken:
                        type: string
                        example: b7c1e2f0a9d84c3e8f6a5b4c3d2e1f00
                      userProfile:
                        "$ref": "#/components/schemas/UserEntity"
                      organizationInfo:
                        "$ref": "#/components/schemas/OrganizationDetailsEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Sign up a new user and organization
      tags:
      - auth
  "/auth/send-forgot-password-link":
    post:
      description: 'Emails a password-reset link when the address belongs to a user.
        Always answers 200: an unknown email comes back with `isSuccess: false` and
        the error message instead of an error status.'
      operationId: AuthController_sendForgotPasswordLink
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SendForgotPasswordLinkDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Email a password-reset link
      tags:
      - auth
  "/auth/reset-password":
    post:
      description: 'Sets a new password using the emailed reset token and the email,
        then sends a password-changed email. Always answers 200: an unknown or expired
        token comes back with `isSuccess: false` and the error message.'
      operationId: AuthController_resetPassword
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ResetPasswordDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Reset the password with an emailed token
      tags:
      - auth
  "/auth/check-email-availability":
    post:
      description: 'Says whether an email is free to sign up with. `isAvailable` is
        false when it belongs to a user (`type: ''user''`) or to a custodian on an
        extraction code (`type: ''custodian''`); `type` is null when it is available.
        No auth; limited to 10 requests per minute.'
      operationId: AuthController_checkEmailAvailability
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CheckEmailAvailabilityDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/EmailAvailabilityResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Check whether an email can sign up
      tags:
      - auth
  "/cases/{caseId}/pro-upgrade":
    post:
      description: 'Records a one-time Pro upgrade charge for the case and returns
        `status: ''processing''`; the case becomes Pro once the invoice is paid, or
        right away when the plan already includes Pro, and repeating the request does
        not charge again. Requires a member role or above in the organization, owner
        or admin access to the case and an active plan (403 otherwise); not available
        to opposing counsel. Returns 403 when case billing is not enabled for the
        organization or the case belongs to a different organization than the `organization`
        header, and 409 when the case is already Pro.'
      operationId: BillingController_requestCaseProUpgrade
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseProUpgradeResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Upgrade a case to Pro
      tags:
      - billing
  "/billing/setup-session":
    post:
      description: Creates a Stripe Checkout session in setup mode that puts a card
        on file without charging it, and returns its `url`; Stripe sends the user
        back to `successUrl` or `cancelUrl`. Requires an org admin, owner or billing
        role. Returns 403 when case billing is not enabled for the organization, and
        409 when its billing profile has no Stripe customer yet.
      operationId: BillingController_createSetupSession
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateBillingSetupSessionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BillingSetupSessionResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start adding a card
      tags:
      - billing
  "/billing/plan":
    post:
      description: Puts the organization on a self-serve tier in pay-as-you-go mode,
        or changes its tier or cadence (monthly by default, annual for an annual-only
        tier); a change settles the current base fee at once and returns the net amount
        in `prorationCents` (0 on a first selection), and a first selection also moves
        the organization off any legacy subscription. When no card is on file, `paymentRequired`
        is true and `setupSessionUrl` links to a card setup page, so `successUrl`
        and `cancelUrl` are then required (400 otherwise). Requires an org admin,
        owner or billing role; returns 403 while self-serve plan selection is not
        available and 400 for a monthly cadence on an annual-only tier.
      operationId: BillingController_selectPlan
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SelectPlanDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/SelectPlanResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Choose or change the plan
      tags:
      - billing
  "/cases/{caseId}/billing/statement":
    get:
      description: 'Lists the case''s billing rows newest first (50 by default, at
        most 200, paged with `limit` and `skip`) with the total charged and the list
        value of plan-included rows; both totals cover every row, not just the page.
        Plan-included rows have `informational: true` and, like waived or voided rows,
        never count toward `totalChargedCents`. Requires a member role or above in
        the organization and owner or admin access to the case; not available to opposing
        counsel.'
      operationId: BillingStatementController_getCaseStatement
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: limit
        required: false
        in: query
        schema:
          type: number
      - name: skip
        required: false
        in: query
        schema:
          type: number
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseStatementResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a case's billing statement
      tags:
      - billing
  "/billing/reconciliation":
    get:
      description: 'Lists the organization''s billing rows newest first (50 by default,
        at most 200, paged with `limit` and `skip`), grouped by case; org-wide charges
        such as the plan base fee come in a group with `caseId: ''organization''`
        and `chargeScope: ''platform''`. Each group''s `subtotalChargedCents` covers
        all of its rows, not just the page, and each row carries its Stripe invoice
        id and hosted invoice link once invoiced. Requires an org admin, owner or
        billing role.'
      operationId: BillingStatementController_getOrgReconciliation
      parameters:
      - name: limit
        required: false
        in: query
        schema:
          type: number
      - name: skip
        required: false
        in: query
        schema:
          type: number
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrgReconciliationResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the organization's charges by case
      tags:
      - billing
  "/billing/history":
    get:
      description: Lists the organization's billing events (subscription, invoicing,
        payment method, case billing and admin actions) newest first, filterable by
        `category` and `caseId`; 50 by default, at most 200, paged with `limit` and
        `skip`. Requires an org admin, owner or billing role. Adjustments made by
        Hearsay staff show a neutral description, no reason and no `createdBy`.
      operationId: BillingStatementController_getOrgHistory
      parameters:
      - name: category
        required: false
        in: query
        schema:
          type: string
          enum:
          - subscription
          - invoicing
          - payment_method
          - case_billing
          - admin
      - name: caseId
        required: false
        in: query
        schema:
          type: string
      - name: limit
        required: false
        in: query
        schema:
          type: number
      - name: skip
        required: false
        in: query
        schema:
          type: number
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/BillingHistoryResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List billing events (internal)
      tags:
      - billing
      x-internal: true
  "/billing/plans":
    get:
      description: Lists the self-serve tiers with monthly and annual base price in
        cents, seat and storage caps, included features and allowed cadences. The
        organization's own plan is on `GET /billing/plan/current`. Requires an org
        admin, owner or billing role.
      operationId: BillingStatementController_getPlans
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BillingPlansResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List self-serve plans
      tags:
      - billing
  "/billing/plan/current":
    get:
      description: Returns the organization's plan (tier, cadence, billing mode and
        the card that will be charged) with usage against its caps for seats, storage,
        this month's AI tokens and cases, and `delinquent`, which is true when a plan
        base fee failed more than 7 days ago. `plan` is null when the organization
        is not on case billing; usage is still returned. Requires an org admin, owner
        or billing role.
      operationId: BillingStatementController_getCurrentPlan
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BillingPlanCurrentResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the current plan and usage
      tags:
      - billing
  "/billing/credits":
    get:
      description: Returns the organization's pre-purchased credit balance in cents,
        its currency and the share of each invoice the balance covers (`effectiveDeductPercentage`),
        with the credit ledger newest first (50 entries by default, paged with `limit`
        and `skip`). Requires an org admin, owner or billing role; returns 403 while
        credits are turned off.
      operationId: BillingStatementController_getCredits
      parameters:
      - name: limit
        required: false
        in: query
        schema:
          type: number
      - name: skip
        required: false
        in: query
        schema:
          type: number
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CreditBalanceResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the credit balance
      tags:
      - billing
  "/billing/report":
    post:
      description: 'Returns the organization''s billing charges in a date window,
        grouped by case, with charged, plan-included, waived or voided, failed and
        refunded totals; the window defaults to the current billing period up to now
        and can be limited to some cases. With `format: ''pdf''` it renders the report
        as a PDF and returns the stored file and a time-limited download URL instead
        of the data. Requires an org admin, owner or billing role; returns 404 while
        billing reports are turned off and 400 for a start date after the end date
        or more than 50,000 rows.'
      operationId: BillingReportController_getReport
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BillingReportQueryDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    oneOf:
                    - "$ref": "#/components/schemas/BillingReportResponseEntity"
                    - "$ref": "#/components/schemas/BillingReportFileResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the billing report (internal)
      tags:
      - billing
      x-internal: true
  "/user/profile":
    get:
      description: Returns the signed-in user's profile with every organization they
        belong to. Each organization entry carries the caller's permission and the
        org's billing summary. The profile can come from a cache up to 60 seconds
        old.
      operationId: UserController_getProfile
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get my profile
      tags:
      - user
    patch:
      description: Updates the caller's own name, phone numbers, onboarding status
        or step, or user type, and returns the freshly built profile. Each organization
        entry carries the caller's permission and the org's billing summary.
      operationId: UserController_updateProfile
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateUserDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update my profile
      tags:
      - user
  "/user/change-password":
    post:
      description: Changes the caller's password after checking the old one and returns
        the freshly built profile. Returns 400 when the old password is wrong.
      operationId: UserController_changePassword
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ChangePasswordDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Change my password
      tags:
      - user
  "/user/verify-email":
    post:
      description: Marks a user's email as verified using the token from the verification
        email, optionally matched with `email`. No auth. Returns 404 for an unknown
        token and 400 when it has expired. `data` holds only `message`.
      operationId: UserController_verifyEmail
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/VerifyEmailDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - message
                    properties:
                      message:
                        type: string
                        example: Email verified
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Verify an email address
      tags:
      - user
  "/user/resend-verification-email":
    post:
      description: Sends the caller a new email-verification link. Returns 400 when
        the email is already verified. Limited to 3 requests per minute. `data` holds
        only `message`.
      operationId: UserController_resendVerificationEmail
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - message
                    properties:
                      message:
                        type: string
                        example: Verification email sent
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Resend the verification email (internal)
      tags:
      - user
      x-internal: true
  "/organization":
    post:
      description: Creates an organization with the caller as owner, attaches the
        free plan, sets up its billing customer and invites `inviteEmails` when given.
        Returns the organization, the caller's permission and its billing summary.
        Returns 400 when the name is already taken.
      operationId: OrganizationController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateOrganizationDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationDetailsResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create an organization
      tags:
      - organizations
  "/organization/invitations":
    get:
      description: Lists invitations of every organization the caller belongs to,
        or of the one in the `organization` query (403 when the caller is not a member
        of it), filterable by text, status and role; paginated, most recently updated
        first. Inviting, invited and accepting users and the organization come as
        objects. `data` is null, not an empty array, when nothing matches.
      operationId: OrganizationController_searchInvitations
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: organization
        required: true
        in: header
        description: id of the selected (active) organization
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 3
          maxLength: 100
          type: string
      - name: status
        required: false
        in: query
        schema:
          type: string
          enum:
          - pending
          - accepted
          - rejected
      - name: userRole
        required: false
        in: query
        schema:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
      - name: sortBy
        required: false
        in: query
        schema:
          default: updatedAt
          type: string
          enum:
          - createdAt
          - updatedAt
          - email
      - name: organization
        required: false
        in: query
        description: Limit to one of your organizations (defaults to all of them)
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/InvitationListEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List organization invitations
      tags:
      - organizations
  "/organization/validate-name":
    post:
      description: 'Returns `isValid: false` when any organization already uses the
        name (case-insensitive). No auth.'
      operationId: OrganizationController_validateName
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ValidateOrganizationNameDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationNameValidateResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Check whether an organization name is free (internal)
      tags:
      - organizations
      x-internal: true
  "/organization/pending-invitations":
    get:
      description: Lists the caller's own pending organization invitations, optionally
        narrowed to one organization or role; paginated, most recently updated first.
        `data` is null, not an empty array, when nothing matches.
      operationId: OrganizationController_searchPendingInvitations
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: organization
        required: false
        in: query
        schema:
          type: string
      - name: userRole
        required: false
        in: query
        schema:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
      - name: sortBy
        required: false
        in: query
        schema:
          default: updatedAt
          type: string
          enum:
          - createdAt
          - updatedAt
          - email
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/InvitationListEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List my pending organization invitations (internal)
      tags:
      - organizations
      x-internal: true
  "/organization/{orgId}":
    get:
      description: Returns the organization, the caller's permission in it and its
        billing summary (`billing` is null for a legacy-billed org). `organization.activePlan`
        is not populated on this route. Requires a role in the organization (403 otherwise).
      operationId: OrganizationController_getOrgDetails
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationDetailsResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an organization
      tags:
      - organizations
    patch:
      description: Updates organization fields (name, address, metadata, communication
        preferences, Plaid terms acceptance and similar) and returns the updated details.
        Renaming also regenerates the slug; returns 400 when the new name is taken.
        Requires an org admin or owner and an active plan (403 otherwise).
      operationId: OrganizationController_updateOrgDetails
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateOrganizationDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationDetailsResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update an organization
      tags:
      - organizations
  "/organization/slug/{slug}":
    get:
      description: Same as getting an organization by id, looked up by its slug. Requires
        a role in the organization (403 otherwise). An unknown slug also gets 403.
      operationId: OrganizationController_getOrgDetailsBySlug
      parameters:
      - name: slug
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationDetailsResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an organization by slug (internal)
      tags:
      - organizations
      x-internal: true
  "/organization/{orgId}/usages-stat":
    get:
      description: Returns the org's billing model, whether its plan is active and
        paid, seat, device and storage allowances and usage (storage also in KB/MB/GB/TB),
        whether the caller may manage billing, and whether the device in `deviceId`/`deviceType`
        is new to the org's cases. Requires a role in the organization (403 otherwise).
        Returns 400 when the organization has no active plan record.
      operationId: OrganizationController_getOrgUsagesStat
      parameters:
      - name: orgId
        required: true
        in: path
        schema:
          type: string
      - name: deviceId
        required: false
        in: query
        schema:
          type: string
      - name: deviceType
        required: false
        in: query
        schema:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationUsagesStatEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an organization's plan usage (internal)
      tags:
      - organizations
      x-internal: true
  "/organization/{orgId}/storage-stats":
    get:
      description: Returns the total storage used by the organization's cases and
        a per-case breakdown, largest first, counting only cases that are not archived
        or deleted, optionally limited to `caseIds`. Requires a role in the organization
        (403 otherwise).
      operationId: OrganizationController_getOrgStorageStats
      parameters:
      - name: orgId
        required: true
        in: path
        schema:
          type: string
      - name: caseIds
        required: false
        in: query
        description: Filter by case ids
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationStorageStatsEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an organization's storage by case
      tags:
      - organizations
  "/organization/user/organizations":
    get:
      description: Lists every organization the caller belongs to in a single page,
        each with the caller's permission and the billing summary; `organization.activePlan`
        is sent without its subscription (null) or payments.
      operationId: OrganizationController_getOrganizationListForUser
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationListEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List my organizations
      tags:
      - organizations
  "/organization/{orgId}/members":
    get:
      description: Lists the organization's members with their role, last activity
        and user details, filterable by name or email text and by role; paginated,
        10 per page by default. Requires a role in the organization (403 otherwise).
        `data` is null, not an empty array, when nothing matches.
      operationId: OrganizationController_getOrganizationMembers
      parameters:
      - name: orgId
        required: true
        in: path
        description: The id of the organization
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      - name: role
        required: false
        in: query
        schema:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
      - name: sortBy
        required: false
        in: query
        schema:
          default: createdAt
          type: string
          enum:
          - createdAt
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationMembersListEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List organization members
      tags:
      - organizations
  "/organization/{orgId}/invite":
    post:
      description: Invites each email, as `member` unless a role is given. Existing
        members are skipped and a pending invitation is sent again; when auto-accept
        is on, existing users are added directly. Requires a member role or above
        in the organization and an active plan (403 otherwise). A plain member may
        only invite clients or members (401 otherwise), and 403 is returned when the
        plan's seat limit is reached.
      operationId: OrganizationController_inviteUsers
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        description: Invitation request body
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/InviteOrganizationUsersDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Invite users to an organization
      tags:
      - organizations
  "/organization/{orgId}/invitation/{invitationId}":
    delete:
      description: Deletes a pending invitation of the organization. Requires a member
        role or above in the organization and an active plan (403 otherwise). Returns
        404 for an unknown invitation and 400 when it was already accepted or rejected.
      operationId: OrganizationController_removeInvitation
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: invitationId
        required: true
        in: path
        description: Id of the existing invitation
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a pending invitation
      tags:
      - organizations
  "/organization/{orgId}/invitation/{invitationId}/resend":
    post:
      description: Issues a new invitation token, valid for one month, and sends the
        invitation email again; a rejected invitation goes back to pending. Requires
        a member role or above in the organization and an active plan (403 otherwise).
        Returns 404 for an unknown invitation and 400 when it was already accepted.
      operationId: OrganizationController_resendInvitation
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: invitationId
        required: true
        in: path
        description: Id of the existing invitation
        schema:
          type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Resend an invitation
      tags:
      - organizations
  "/organization/{orgId}/invitation/accept":
    post:
      description: Accepts an invitation using the emailed token and email and returns
        the organization details. When the invitee has no account yet, `name` and
        `password` are required (400 otherwise), the account is created and signed
        in, and `login` holds its tokens and profile; for an existing user `login`
        is null. No auth, but the organization needs an active plan (403). Returns
        404 for an unknown organization or invitation, and 400 when the token has
        expired or the invitation is no longer pending.
      operationId: OrganizationController_acceptInvitation
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        description: Accept invitation body
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AcceptOrganizationInvitationRequestDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/InvitationAcceptResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Accept an organization invitation
      tags:
      - organizations
  "/organization/{orgId}/invitation/reject":
    post:
      description: Rejects an invitation using the emailed token and email, with an
        optional reason, and notifies the organization. No auth. Returns 404 for an
        unknown organization or invitation, and 400 when the token has expired or
        the invitation is no longer pending. The response has no `data`.
      operationId: OrganizationController_rejectInvitation
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        description: Reject invitation body
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RejectOrganizationInvitationRequestDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - statusCode
                - timestamp
                properties:
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Reject an organization invitation (internal)
      tags:
      - organizations
      x-internal: true
  "/organization/{orgId}/update-user-role":
    patch:
      description: Sets the organization role of each listed user. Requires an org
        admin or owner and an active plan (403 otherwise). Users who are not members
        are skipped without an error.
      operationId: OrganizationController_updateAccess
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        description: Request body
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateOrganizationUserAccessDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Change members' organization roles (internal)
      tags:
      - organizations
      x-internal: true
  "/organization/{orgId}/user":
    patch:
      description: Updates each listed member's organization role and/or their name
        and login email. Name and email change on the user account itself, not only
        in this organization, so they can be changed only for a member who belongs
        to no other organization (403 otherwise, and nothing in the request is applied).
        Requires an org admin or owner and an active plan (403 otherwise). Returns
        400 when an entry has none of role, name or email or the new email belongs
        to another account, and 404 when a user is not a member.
      operationId: OrganizationController_updateOrganizationUsers
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        description: Request body
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateOrganizationUsersDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update organization members
      tags:
      - organizations
  "/organization/{orgId}/user/{userId}":
    delete:
      description: Removes the user from the organization, deletes their invitations
        and case access in it, and frees one seat on the plan. Requires an org admin
        or owner and an active plan (403 otherwise).
      operationId: OrganizationController_removeUserFromOrg
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: userId
        required: true
        in: path
        description: Id of the user to remove
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove a member from an organization
      tags:
      - organizations
  "/organization/{orgId}/contact-admin":
    post:
      description: Queues an email to the organization asking for more devices on
        behalf of the caller, with the given text or a default message, optionally
        for a case. Requires a role in the organization and an active plan (403 otherwise).
        Returns 404 for an unknown organization.
      operationId: OrganizationController_contactAdminForDevice
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeviceRequestContactAdminDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Ask the organization for more devices (internal)
      tags:
      - organizations
      x-internal: true
  "/organization/tags/{orgId}":
    get:
      description: Lists the organization's tags, filterable by type, name, severity
        and origin; paginated, 10 per page by default. Requires a member role or above
        in the organization and an active plan (403 otherwise).
      operationId: OrganizationTagsController_search
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: name
        required: false
        in: query
        schema:
          type: string
      - name: severity
        required: false
        in: query
        schema:
          default: info
          type: string
          enum:
          - info
          - warning
          - danger
      - name: type
        required: false
        in: query
        schema:
          default: organization
          type: string
          enum:
          - system
          - organization
          - case
          - user
      - name: origin
        required: false
        in: query
        description: Filter by how the tag was created. Use "manual" to hide auto-import
          tags from the picker.
        schema:
          type: string
          enum:
          - manual
          - auto-import
      - name: sortBy
        required: false
        in: query
        schema:
          default: name
          type: string
          enum:
          - createdAt
          - name
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/TagEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List organization tags
      tags:
      - organizations
    post:
      description: Creates an organization tag (severity `info` and the default colors
        unless given) and returns it. Requires a member role or above in the organization
        and an active plan (403 otherwise). Returns 400 when the organization already
        has a tag with that name (case-insensitive).
      operationId: OrganizationTagsController_create
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateOrganizationTagDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/TagEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create an organization tag
      tags:
      - organizations
  "/organization/tags/{orgId}/{tagId}":
    patch:
      description: Changes an organization tag's name, colors or severity and returns
        it. Requires a member role or above in the organization and an active plan
        (403 otherwise). Returns 404 for an unknown tag, and 400 when the new name
        is taken or nothing was sent.
      operationId: OrganizationTagsController_update
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: tagId
        required: true
        in: path
        description: Id of the tag
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateOrganizationTagDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/TagEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update an organization tag
      tags:
      - organizations
    delete:
      description: Deletes an organization tag and detaches it from everything it
        was attached to. Requires an org admin or owner and an active plan (403 otherwise).
        Returns 404 for an unknown tag.
      operationId: OrganizationTagsController_delete
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: tagId
        required: true
        in: path
        description: Id of the tag
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete an organization tag
      tags:
      - organizations
  "/case":
    get:
      description: Lists the cases the caller can see in the organization from the
        `organization` header, most recently updated first by default, including cases
        shared with the caller by another firm. Each row carries the case plus the
        counts the case list shows (extraction codes, data used, data-source statuses,
        conversations). Use GET /case/:caseId for a single case.
      operationId: CaseController_search
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: organization
        required: true
        in: header
        description: id of the selected (active) organization
        schema:
          type: string
      - name: hasUpdates
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      - name: skipPagination
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 3
          maxLength: 100
          type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: status
        required: false
        in: query
        schema:
          type: string
          enum:
          - open
          - closed
          - archived
          - pending
      - name: priority
        required: false
        in: query
        schema:
          type: string
          enum:
          - low
          - medium
          - high
          - urgent
      - name: assignStatus
        required: false
        in: query
        schema:
          type: string
          enum:
          - assigned
          - unassigned
      - name: assignedTo
        required: false
        in: query
        schema:
          type: string
      - name: users
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: isDefault
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      - name: sortBy
        required: false
        in: query
        schema:
          default: updatedAt
          type: string
          enum:
          - createdAt
          - updatedAt
          - name
      - name: isArchived
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseListItemEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List cases
      tags:
      - cases
    post:
      description: Creates a case in the organization from the `organization` header
        and returns it with its members and pending invitations. The caller becomes
        the case owner.
      operationId: CaseController_create
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateCaseDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseViewEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a case
      tags:
      - cases
  "/case/check-name":
    get:
      description: Returns whether a case with this name (case-insensitive) already
        exists in the organization. Call it before POST /case to show a name clash
        early.
      operationId: CaseController_checkCaseName
      parameters:
      - name: name
        required: true
        in: query
        schema:
          minLength: 3
          maxLength: 256
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CheckCaseNameResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Check whether a case name is free
      tags:
      - cases
  "/case/{caseId}":
    get:
      description: Returns one case with its members, pending invitations, active
        extraction codes and conversation count. For an opposing-counsel caller, members
        and counts are limited to what that side may see.
      operationId: CaseController_getCaseDetails
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseViewEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a case
      tags:
      - cases
    patch:
      description: Updates the case name, description, matter id, status, priority,
        metadata or tags, and returns the updated case. Renaming also changes the
        slug.
      operationId: CaseController_update
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateCaseDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseViewEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update a case
      tags:
      - cases
  "/case/{caseId}/data-sources":
    get:
      description: Lists the data sources enabled on the case (for example iMessage
        or Gmail), as ids of the master data sources.
      operationId: CaseController_fetchCaseDataSources
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/CaseDataSourceListItemEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a case's data sources
      tags:
      - cases
  "/case/slug/{slug}":
    get:
      description: Same as GET /case/:caseId, looked up by the case slug within the
        organization from the `organization` header. Also finds a case another firm
        shared with the caller.
      operationId: CaseController_getCaseDetailsBySlug
      parameters:
      - name: slug
        required: true
        in: path
        description: slug of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseViewEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a case by slug
      tags:
      - cases
  "/case/{caseId}/archive":
    post:
      description: Archives the case. Archived cases stay readable but are hidden
        from the default case list.
      operationId: CaseController_archiveCase
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ArchiveCaseDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Archive a case
      tags:
      - cases
  "/case/{caseId}/unarchive":
    post:
      description: Restores an archived case to the active case list.
      operationId: CaseController_unarchiveCase
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Unarchive a case
      tags:
      - cases
  "/case/{caseId}/conversations":
    delete:
      description: Removes every conversation from the case. Conversations linked
        only to this case are deleted; conversations also linked to other cases are
        only unlinked from this one. This cannot be undone.
      operationId: CaseController_deleteCaseConversations
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove all conversations from a case
      tags:
      - cases
  "/case/{caseId}/link-conversations":
    patch:
      description: Links the given device conversations to the case and returns the
        updated case.
      operationId: CaseController_linkConversations
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/LinkConversationsToCaseDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseViewEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Link conversations to a case
      tags:
      - cases
  "/case/{caseId}/unlink-conversations":
    patch:
      description: Removes the given device conversations from the case and returns
        the updated case.
      operationId: CaseController_unlinkConversations
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UnlinkConversationsFromCaseDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseViewEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Unlink conversations from a case
      tags:
      - cases
  "/case/{caseId}/export":
    post:
      description: Queues an export of the case's conversations (PDF, RSMF and other
        formats). The export runs in the background; poll GET /case/:caseId/export
        or listen on the case socket for progress, then download the export's file.
      operationId: CaseController_export
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CaseCollectionExportDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionExportEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start a case export
      tags:
      - cases
    get:
      description: Lists the case's export jobs with their status, progress and file.
      operationId: CaseController_searchExports
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: source
        required: false
        in: query
        schema:
          type: string
          enum:
          - conversation
          - browser-history
          - emails
          - case-files
          - case-attachments
          - financial
          - linkedin
          - ai-chat
          - contacts
      - name: type
        required: false
        in: query
        schema:
          type: string
          enum:
          - doc
          - text
          - csv
          - pdf
          - goodfact
          - rsmf
          - eml
          - files-zip
      - name: exportId
        required: false
        in: query
        schema:
          type: string
      - name: exportedBy
        required: false
        in: query
        schema:
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: createdAt
          type: string
          enum:
          - createdAt
          - exportedAt
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseCollectionExportEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List case exports
      tags:
      - cases
  "/case/{caseId}/export/{exportId}":
    delete:
      description: Deletes the export job and all of its chunk records.
      operationId: CaseController_deleteExport
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: exportId
        required: true
        in: path
        description: Id of the export
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete an export
      tags:
      - cases
  "/case/{caseId}/export/{exportId}/rerun":
    post:
      description: Queues an export that did not finish successfully again, with the
        same settings. An export that already completed is rejected.
      operationId: CaseController_rerunExport
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: exportId
        required: true
        in: path
        description: Id of the export
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Re-run an export
      tags:
      - cases
  "/case/{caseId}/extraction":
    get:
      description: Lists the case's extraction codes (the codes custodians use to
        share data from their devices) with their upload status and, on cases shared
        with opposing counsel, their review status.
      operationId: CaseController_searchExtractionCode
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: skipPagination
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 3
          maxLength: 100
          type: string
      - name: extractionCode
        required: false
        in: query
        schema:
          maxLength: 100
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: updatedAt
          type: string
          enum:
          - createdAt
          - updatedAt
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/ExtractionCodeEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a case's extraction codes
      tags:
      - cases
    post:
      description: Creates an extraction code for a custodian. The code is emailed
        only when sendEmail is true and custodianEmail is set, and, on a case shared
        with opposing counsel, only once the code is approved. The custodian enters
        the code in the Hearsay app to share data into this case.
      operationId: CaseController_createExtractionCode
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateExtractionCodeDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create an extraction code
      tags:
      - cases
  "/case/{caseId}/data-status":
    get:
      description: Per data source, the status of each source the custodians shared,
        with status percentages. Superseded by data-status-tree-v3, which most clients
        should use.
      operationId: CaseController_fetchCaseDataStatus
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: extractionCode
        required: false
        in: query
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Tag ids (comma-separated). Filters device conversations, emails,
          files, and financial sources. Excludes discord/reddit.
        schema:
          type: array
          items:
            type: string
      - name: searchMatchType
        required: false
        in: query
        description: Match type for message text search. FUZZY allows typo tolerance,
          EXACT requires exact token match.
        schema:
          default: FUZZY
          type: string
          enum:
          - FUZZY
          - EXACT
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseDataStatusResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get data-collection status (v1)
      tags:
      - cases
  "/case/{caseId}/data-status-v2":
    get:
      description: Data-collection status grouped by data source, with item counts
        and the extraction code each source came from. For the full tree view, use
        data-status-tree-v3.
      operationId: CaseController_fetchCaseDataStatusV2
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: extractionCode
        required: false
        in: query
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Tag ids (comma-separated). Filters device conversations, emails,
          files, and financial sources. Excludes discord/reddit.
        schema:
          type: array
          items:
            type: string
      - name: searchMatchType
        required: false
        in: query
        description: Match type for message text search. FUZZY allows typo tolerance,
          EXACT requires exact token match.
        schema:
          default: FUZZY
          type: string
          enum:
          - FUZZY
          - EXACT
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseDataStatusV2ResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get data-collection status (v2)
      tags:
      - cases
  "/case/{caseId}/data-status-tree":
    get:
      description: Data-collection status as a tree of custodians, devices and sources.
        Kept as a fallback for data-status-tree-v3.
      operationId: CaseController_fetchCaseDataStatusTree
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: extractionCode
        required: false
        in: query
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Tag ids (comma-separated). Filters device conversations, emails,
          files, and financial sources. Excludes discord/reddit.
        schema:
          type: array
          items:
            type: string
      - name: searchMatchType
        required: false
        in: query
        description: Match type for message text search. FUZZY allows typo tolerance,
          EXACT requires exact token match.
        schema:
          default: FUZZY
          type: string
          enum:
          - FUZZY
          - EXACT
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseDataStatusListTreeResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get data-collection status as a tree
      tags:
      - cases
  "/case/{caseId}/data-status-tree-v3":
    get:
      description: 'The current data-collection view: a tree of extraction codes,
        devices, email accounts, files and financial accounts with item counts and,
        on opposing-counsel cases, items pending review.'
      operationId: CaseController_fetchCaseDataStatusTreeV3
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: extractionCode
        required: false
        in: query
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Tag ids (comma-separated). Filters device conversations, emails,
          files, and financial sources. Excludes discord/reddit.
        schema:
          type: array
          items:
            type: string
      - name: searchMatchType
        required: false
        in: query
        description: Match type for message text search. FUZZY allows typo tolerance,
          EXACT requires exact token match.
        schema:
          default: FUZZY
          type: string
          enum:
          - FUZZY
          - EXACT
      - name: conversationLimit
        required: false
        in: query
        description: Max conversations per source-type folder
        schema:
          default: 25
          type: number
      - name: fileLimit
        required: false
        in: query
        description: Max files per file-provider folder
        schema:
          default: 25
          type: number
      - name: sortConversationsBy
        required: false
        in: query
        description: Sort field for conversation children
        schema:
          default: lastMessageDate
          type: string
          enum:
          - lastMessageDate
          - displayName
      - name: sortConversationsOrder
        required: false
        in: query
        description: Sort direction override. Defaults to DESC for lastMessageDate,
          ASC for displayName.
        schema:
          type: string
          enum:
          - asc
          - desc
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseDataStatusTreeV3ResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get data-collection status (tree v3)
      tags:
      - cases
  "/case/{caseId}/extraction/{extractionCode}":
    patch:
      description: Updates an extraction code's label, due date, configuration or
        custodian. Configuration cannot change once the code is locked.
      operationId: CaseController_updateExtractionCode
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: extractionCode
        required: true
        in: path
        description: extraction code
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateExtractionCodeDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update an extraction code
      tags:
      - cases
    delete:
      description: Deletes an extraction code that has not collected any data yet.
        Returns 409 once data has been collected with it, and 403 while code deletion
        is turned off.
      operationId: CaseController_deleteExtractionCode
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: extractionCode
        required: true
        in: path
        description: extraction code
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete an extraction code
      tags:
      - cases
  "/case/{caseId}/extraction/{extractionCode}/custodian":
    patch:
      description: Updates the custodian's name, email or phone on an extraction code
        and, when the email changes and the code is usable, re-sends the code email.
      operationId: CaseController_updateCustodianContact
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: extractionCode
        required: true
        in: path
        description: extraction code
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateCustodianContactDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update a custodian's contact details
      tags:
      - cases
  "/case/{caseId}/extraction/{extractionCode}/send-email":
    post:
      description: Queues the extraction-code email to the code's custodian.
      operationId: CaseController_sendExtractionCodeEmail
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: extractionCode
        required: true
        in: path
        description: extraction code
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SendExtractionCodeEmailDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Email an extraction code
      tags:
      - cases
  "/case/{caseId}/search-tracking":
    get:
      description: Lists the conversation searches custodians ran in the Hearsay app
        for this case, with the search filters they used.
      operationId: CaseController_fetchDeviceSearchTrackingData
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: conversationId
        required: false
        in: query
        schema:
          type: string
      - name: deviceId
        required: false
        in: query
        schema:
          type: string
      - name: extractionCodeId
        required: false
        in: query
        schema:
          type: string
      - name: extractionCode
        required: false
        in: query
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DeviceSearchTrackingEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List conversation searches
      tags:
      - cases
  "/case/{caseId}/activity-logs":
    get:
      description: Lists device activity log entries for the case (backups, uploads
        and similar events).
      operationId: CaseController_fetchDeviceActivityLogsData
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: deviceId
        required: false
        in: query
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DeviceActivityLogSingleEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List device activity
      tags:
      - cases
  "/case/{caseId}/download-attachments":
    post:
      description: Queues a zip of the attachments in the given conversations. The
        job runs in the background; listen on the case socket or poll GET /case/:caseId/export
        for the file.
      operationId: CaseController_downloadConversationAttachments
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DownloadConversationAttachmentsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionExportEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Request a zip of conversation attachments
      tags:
      - cases
  "/case/access/invitations":
    get:
      description: Searches case invitations in organizations where the caller is
        an owner or admin, optionally narrowed to one organization, case, status or
        access level. For one case's invitations, use GET /case/access/:orgId/case/:caseId/invitations.
      operationId: CaseAccessController_searchInvitations
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: organization
        required: false
        in: query
        schema:
          type: string
      - name: case
        required: false
        in: query
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 3
          maxLength: 100
          type: string
      - name: status
        required: false
        in: query
        schema:
          type: string
          enum:
          - pending
          - accepted
          - rejected
      - name: accessLevel
        required: false
        in: query
        schema:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
      - name: sortBy
        required: false
        in: query
        schema:
          default: updatedAt
          type: string
          enum:
          - createdAt
          - updatedAt
          - email
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseInvitationListItemEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search case invitations across organizations
      tags:
      - case-access
  "/case/access/pending-invitations":
    get:
      description: Lists the pending case invitations addressed to the caller, optionally
        narrowed to one organization or access level.
      operationId: CaseAccessController_searchPendingInvitations
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: organization
        required: false
        in: query
        schema:
          type: string
      - name: accessLevel
        required: false
        in: query
        schema:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
      - name: sortBy
        required: false
        in: query
        schema:
          default: updatedAt
          type: string
          enum:
          - createdAt
          - updatedAt
          - email
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseInvitationListItemEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List my pending case invitations
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/members":
    get:
      description: Lists the active members of a case with their access level and
        type. On a case shared with opposing counsel, each side sees its own members
        plus the other side's lead contact.
      operationId: CaseAccessController_listCaseMembers
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 3
          maxLength: 100
          type: string
      - name: level
        required: false
        in: query
        schema:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
      - name: sortBy
        required: false
        in: query
        schema:
          default: joinedAt
          type: string
          enum:
          - name
          - joinedAt
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: orgId
        required: true
        in: path
        description: Id of the org
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseMemberEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List case members
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/invitations":
    get:
      description: Lists one case's invitations, optionally filtered by status or
        by invitee name or email. On a case shared with opposing counsel, each side
        sees only the invitations it may see.
      operationId: CaseAccessController_listCaseInvitations
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: status
        required: false
        in: query
        schema:
          type: string
          enum:
          - pending
          - accepted
          - rejected
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 3
          maxLength: 100
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: date
          type: string
          enum:
          - name
          - date
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: orgId
        required: true
        in: path
        description: Id of the org
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseInvitationListItemEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List case invitations
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/add-all-org-users":
    post:
      description: Gives every member of the organization access to the case at the
        requested access level.
      operationId: CaseAccessController_addAllOrgUsersToCase
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      requestBody:
        required: true
        description: Add all users request body
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddAllOrgUsersToCaseDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Add every organization member to a case
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/invite":
    post:
      description: Invites one or more people to the case by email. Each invitee receives
        an email with a link to accept.
      operationId: CaseAccessController_inviteUsers
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      requestBody:
        required: true
        description: Invitation request body
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/InviteCaseUsersDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Invite people to a case
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/invitation/{invitationId}":
    delete:
      description: Deletes a pending case invitation. Accepted or rejected invitations
        cannot be cancelled.
      operationId: CaseAccessController_removeInvitation
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: invitationId
        required: true
        in: path
        description: Id of the invitation to remove
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Cancel a pending invitation
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/invitation/{invitationId}/resend":
    post:
      description: Issues a new invitation link (valid for one month) and emails it
        to the invitee again.
      operationId: CaseAccessController_resendInvitation
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: invitationId
        required: true
        in: path
        description: Id of the existing invitation
        schema:
          type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Resend an invitation
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/invitation/accept":
    post:
      description: 'Accepts an invitation using the token from the invite email. Public:
        no sign-in needed. A new invitee must send name and password; their account
        is created and `login` carries their session.'
      operationId: CaseAccessController_acceptInvitation
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      requestBody:
        required: true
        description: Accept invitation body
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AcceptCaseInvitationDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseInvitationAcceptResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Accept a case invitation
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/invitation/reject":
    post:
      description: 'Rejects an invitation using the token from the invite email, with
        an optional reason. Public: no sign-in needed. Returns no data.'
      operationId: CaseAccessController_rejectInvitation
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      requestBody:
        required: true
        description: Reject invitation body
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RejectCaseInvitationRequestDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - statusCode
                - timestamp
                properties:
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Reject a case invitation
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/update-user-access":
    patch:
      description: Changes the access level of one or more members of the case.
      operationId: CaseAccessController_updateAccess
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      requestBody:
        required: true
        description: Request body
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateCaseUserAccessDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Change a member's case access
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/user/{userId}":
    delete:
      description: Removes a member's access to the case.
      operationId: CaseAccessController_removeUserFromCase
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: userId
        required: true
        in: path
        description: Id of the user to remove
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove a member from a case
      tags:
      - case-access
  "/case/tags/{caseId}":
    get:
      description: Lists the tags defined for the case.
      operationId: CaseTagsController_search
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: name
        required: false
        in: query
        schema:
          type: string
      - name: severity
        required: false
        in: query
        schema:
          default: info
          type: string
          enum:
          - info
          - warning
          - danger
      - name: type
        required: false
        in: query
        schema:
          default: organization
          type: string
          enum:
          - system
          - organization
          - case
          - user
      - name: origin
        required: false
        in: query
        description: Filter by how the tag was created. Use "manual" to hide auto-import
          tags from the picker.
        schema:
          type: string
          enum:
          - manual
          - auto-import
      - name: sortBy
        required: false
        in: query
        schema:
          default: name
          type: string
          enum:
          - createdAt
          - name
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/TagEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a case's tags
      tags:
      - cases
    post:
      description: Creates a tag in the case.
      operationId: CaseTagsController_create
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateCaseTagDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/TagEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a tag
      tags:
      - cases
  "/case/tags/{caseId}/{tagId}":
    patch:
      description: Changes a tag's name, colors or severity.
      operationId: CaseTagsController_update
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: tagId
        required: true
        in: path
        description: Id of the tag
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateCaseTagDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/TagEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update a tag
      tags:
      - cases
    delete:
      description: Deletes a tag from the case.
      operationId: CaseTagsController_delete
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: tagId
        required: true
        in: path
        description: Id of the tag
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a tag
      tags:
      - cases
  "/case/collections/{caseId}":
    get:
      description: Lists the case's message collections (curated sets of messages),
        optionally filtered by name or archived state.
      operationId: CaseCollectionController_search
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: name
        required: false
        in: query
        schema:
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: updatedAt
          type: string
          enum:
          - createdAt
          - updatedAt
          - name
      - name: isArchived
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseCollectionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a case's collections
      tags:
      - collections
    post:
      description: Creates an empty collection in the case.
      operationId: CaseCollectionController_create
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateCaseCollectionDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a collection
      tags:
      - collections
  "/case/collections/{caseId}/bulk":
    post:
      description: Searches the case's messages for the given terms and creates one
        collection per matching conversation, holding the matching messages. Returns
        how many collections were created.
      operationId: CaseCollectionController_createBulkCollections
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateBulkCollectionsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BulkCollectionCreateResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create collections from search terms
      tags:
      - collections
  "/case/collections/{caseId}/{collectionId}/entries":
    get:
      description: Lists the entries of a collection in order, each with its device
        message, optionally filtered by text or tags.
      operationId: CaseCollectionController_searchEntries
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 3
          maxLength: 100
          type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: linkedContacts
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: devices
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: startDate
        required: false
        in: query
        schema:
          format: date-time
          type: string
      - name: endDate
        required: false
        in: query
        schema:
          format: date-time
          type: string
      - name: source
        required: false
        in: query
        schema:
          type: string
          enum:
          - message
          - whatsapp
          - voicemail
          - call_log
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
      - name: type
        required: false
        in: query
        schema:
          type: string
          enum:
          - message
          - text
          - line_break
          - custom
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: collectionId
        required: true
        in: path
        description: Id of the collection
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseCollectionResponseEntryEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a collection's entries
      tags:
      - collections
  "/case/collections/{caseId}/{collectionId}":
    patch:
      description: Changes a collection's name (and its slug).
      operationId: CaseCollectionController_update
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: collectionId
        required: true
        in: path
        description: Id of the collection
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateCaseCollectionDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Rename a collection
      tags:
      - collections
    delete:
      description: Deletes a collection and its entries. The messages themselves stay
        in the case.
      operationId: CaseCollectionController_delete
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: collectionId
        required: true
        in: path
        description: Id of the collection
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a collection
      tags:
      - collections
    get:
      description: Returns one collection.
      operationId: CaseCollectionController_getById
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: collectionId
        required: true
        in: path
        description: Id of the collection
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a collection
      tags:
      - collections
  "/case/collections/{caseId}/slug/{slug}":
    get:
      description: Returns one collection, looked up by its slug within the case.
      operationId: CaseCollectionController_getBySlug
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: slug
        required: true
        in: path
        description: slug of the collection
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a collection by slug
      tags:
      - collections
  "/case/collections/{caseId}/{collectionId}/add":
    patch:
      description: Adds the given device messages to the collection.
      operationId: CaseCollectionController_addEntries
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: collectionId
        required: true
        in: path
        description: Id of the collection
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddEntriesToCaseCollectionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Add messages to a collection
      tags:
      - collections
  "/case/collections/{caseId}/{collectionId}/add-all":
    patch:
      description: Adds every message of the given conversations (up to 100) to the
        collection.
      operationId: CaseCollectionController_addAllMessages
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: collectionId
        required: true
        in: path
        description: Id of the collection
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddAllMessagesToCollectionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Add whole conversations to a collection
      tags:
      - collections
  "/case/collections/{caseId}/{collectionId}/remove":
    patch:
      description: Removes the given messages from the collection.
      operationId: CaseCollectionController_removeEntries
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: collectionId
        required: true
        in: path
        description: Id of the collection
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RemoveEntriesFromCaseCollectionDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove messages from a collection
      tags:
      - collections
  "/case/collections/{caseId}/{collectionId}/remove/all":
    patch:
      description: Removes every entry from the collection. The collection itself
        stays.
      operationId: CaseCollectionController_removeAllMessages
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: collectionId
        required: true
        in: path
        description: Id of the collection
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Empty a collection
      tags:
      - collections
  "/case/collections/{caseId}/{collectionId}/insert-entry":
    post:
      description: Inserts a message or a custom entry at the given position in the
        collection, shifting later entries down.
      operationId: CaseCollectionController_insertAtPosition
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: collectionId
        required: true
        in: path
        description: Id of the collection
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/InsertEntryAtPositionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionEntryEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Insert an entry at a position
      tags:
      - collections
  "/case/collections/{caseId}/{collectionId}/entry/{entryId}/{position}":
    patch:
      description: Moves an entry to a new position in the collection.
      operationId: CaseCollectionController_moveEntry
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: collectionId
        required: true
        in: path
        description: Id of the collection
        schema:
          type: string
      - name: entryId
        required: true
        in: path
        description: Id of the entry
        schema:
          type: string
      - name: position
        required: true
        in: path
        description: position to move the entry
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionEntryEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Move an entry
      tags:
      - collections
  "/case/contacts/{orgId}/case/{caseId}":
    get:
      description: Lists the contacts found on the case's devices, optionally filtered
        by device or conversation, or searched by text.
      operationId: CaseContactController_search
      parameters:
      - name: orgId
        required: true
        in: path
        description: ID of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: ID of the case
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 3
          maxLength: 100
          type: string
      - name: conversation
        required: false
        in: query
        schema:
          type: string
      - name: device
        required: false
        in: query
        schema:
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: name
          type: string
          enum:
          - createdAt
          - updatedAt
          - name
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DeviceContactEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a case's contacts
      tags:
      - case-contact
  "/case/contacts/{orgId}/case/{caseId}/{contactId}":
    patch:
      description: Edits a contact's name, email or phone. The values the device reported
        are kept, so the contact shows as edited.
      operationId: CaseContactController_update
      parameters:
      - name: orgId
        required: true
        in: path
        description: ID of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: ID of the case
        schema:
          type: string
      - name: contactId
        required: true
        in: path
        description: ID of the contact
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateCaseContactDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DeviceContactEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Edit a contact
      tags:
      - case-contact
  "/case/contacts/{orgId}/case/{caseId}/{sourceContactId}/{targetContactId}":
    patch:
      description: Links the source contact to the target contact so they are treated
        as the same person.
      operationId: CaseContactController_link
      parameters:
      - name: orgId
        required: true
        in: path
        description: ID of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: ID of the case
        schema:
          type: string
      - name: sourceContactId
        required: true
        in: path
        description: ID of the source contact
        schema:
          type: string
      - name: targetContactId
        required: true
        in: path
        description: ID of the target contact
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DeviceContactEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Link two contacts
      tags:
      - case-contact
  "/case/contacts/{orgId}/case/{caseId}/export":
    post:
      description: Queues a CSV export of the case's contacts and returns the export
        job id.
      operationId: CaseContactController_exportContacts
      parameters:
      - name: orgId
        required: true
        in: path
        description: ID of the organization
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: ID of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportCaseContactsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      jobId:
                        type: string
                        example: job-1234
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export contacts to CSV
      tags:
      - case-contact
  "/case/conversations/{caseId}/archive":
    post:
      description: Archives the given conversations in the case so they are hidden
        from the default conversation list.
      operationId: CaseConversationController_archiveConversations
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ArchiveConversationsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Archive conversations
      tags:
      - case-contact
  "/case/conversations/{caseId}/unarchive":
    post:
      description: Restores archived conversations to the case's conversation list.
      operationId: CaseConversationController_unarchiveConversations
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UnarchiveConversationsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Unarchive conversations
      tags:
      - case-contact
  "/case/conversations/{caseId}/{conversationId}":
    patch:
      description: Updates a conversation's display name or other editable details
        and returns the conversation.
      operationId: CaseConversationController_editConversation
      parameters:
      - name: caseId
        required: true
        in: path
        description: id of the case
        schema:
          type: string
      - name: conversationId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/EditConversationDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DeviceConversationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Edit a conversation
      tags:
      - case-contact
  "/case/conversations/{caseId}/{conversationId}/tags":
    patch:
      description: Replaces the tags applied to a conversation and returns the conversation.
      operationId: CaseConversationController_updateConversationTags
      parameters:
      - name: caseId
        required: true
        in: path
        description: id of the case
        schema:
          type: string
      - name: conversationId
        required: true
        in: path
        description: id of the conversation
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateCaseFileTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DeviceConversationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Set a conversation's tags
      tags:
      - case-contact
  "/case/conversations/{caseId}/add":
    post:
      description: Creates a conversation on a device in the case from the given participant
        and source details.
      operationId: CaseConversationController_addConversation
      parameters:
      - name: caseId
        required: true
        in: path
        description: id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddConversationDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DeviceConversationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Add a conversation manually
      tags:
      - case-contact
  "/case/browser-history/{caseId}/history":
    get:
      description: Lists the browser history custodians shared into the case, optionally
        filtered by device, sorted by last visit by default.
      operationId: CaseBrowserHistoryController_fetchCaseDataStatus
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: skipPagination
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 3
          maxLength: 100
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: lastDate
          type: string
          enum:
          - firstDate
          - lastDate
      - name: devices
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/BrowserHistoryEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List browser history
      tags:
      - case-browser-history
  "/cases/files/{caseId}":
    get:
      description: Lists the files uploaded to the case, optionally filtered by extraction
        code, opposing-counsel shared status, tags or text. On a case shared with
        opposing counsel, files are limited to what the caller may see.
      operationId: CaseFilesController_search
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      - name: extractionCode
        required: false
        in: query
        schema:
          type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: category
        required: false
        in: query
        schema:
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: createdAt
          type: string
          enum:
          - createdAt
          - name
          - type
          - size
      - name: sharedStatus
        required: false
        in: query
        description: 'Opposing-council only: filter to files whose ocSharedStatus
          matches (e.g. `pending` for "show me what I have not shared yet"). Ignored
          for non-OC callers.'
        schema:
          type: string
          enum:
          - shared
          - pending
          - not_gated
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseFileEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List case files
      tags:
      - cases/files
  "/cases/files/{caseId}/add-files":
    post:
      description: Adds already-uploaded files to the case, optionally with a category.
      operationId: CaseFilesController_addCaseFiles
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddCaseFilesViaCaseDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Add files to a case
      tags:
      - cases/files
  "/cases/files/{caseId}/zip-download":
    post:
      description: Queues a zip of the given case files. The job runs in the background;
        poll GET /case/:caseId/export or listen on the case socket for the file.
      operationId: CaseFilesController_requestZipDownload
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RequestCaseFilesZipDownloadDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseCollectionExportEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Request a zip of case files
      tags:
      - cases/files
  "/cases/files/{caseFileId}/category":
    patch:
      description: Sets or clears the category of a case file and returns the file.
      operationId: CaseFilesController_updateCaseFileCategory
      parameters:
      - name: caseFileId
        required: true
        in: path
        description: Id of the case file
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateCaseFileCategoryDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseFileEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Set a file's category
      tags:
      - cases/files
  "/cases/files/{caseFileId}/tags":
    patch:
      description: Replaces the tags applied to a case file and returns the file.
      operationId: CaseFilesController_updateCaseFileTags
      parameters:
      - name: caseFileId
        required: true
        in: path
        description: Id of the case file
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateCaseFileTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseFileEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Set a file's tags
      tags:
      - cases/files
  "/case/media/{caseId}":
    get:
      description: Lists the photos and videos shared into the case, optionally filtered
        by device or searched by name.
      operationId: CaseMediaController_searchCaseMedia
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: device
        required: false
        in: query
        description: Filter by device MongoDB id
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: createdAt
          type: string
          enum:
          - createdAt
          - fileName
          - externalId
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DeviceMediaEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List case media
      tags:
      - cases/media
  "/case/notes/{caseId}":
    get:
      description: Lists the device notes shared into the case, optionally filtered
        by device or searched by text.
      operationId: CaseNotesController_searchCaseNotes
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: device
        required: false
        in: query
        description: Filter by device MongoDB id
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: createdAt
          type: string
          enum:
          - createdAt
          - title
          - externalId
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DeviceNotesEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List case notes
      tags:
      - cases/notes
  "/case/{caseId}/suggest/{codeId}":
    post:
      description: On a case shared with opposing counsel, proposes a configuration
        for an extraction code. If the caller already has an open suggestion for the
        code, it is updated instead.
      operationId: CaseExtractionCodeController_createOrUpdateOwnOpen
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateExtractionCodeSuggestionDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeSuggestionEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Suggest an extraction-code configuration
      tags:
      - cases/extraction-code/suggestions
    get:
      description: Lists the configuration suggestions made for an extraction code,
        newest first by default, optionally filtered by status.
      operationId: CaseExtractionCodeController_list
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: status
        required: false
        in: query
        description: Filter suggestions by status
        schema:
          type: string
          enum:
          - open
          - approved
          - rejected
          - closed
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/ExtractionCodeSuggestionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List suggestions for an extraction code
      tags:
      - cases/extraction-code/suggestions
  "/case/{caseId}/suggest/{codeId}/{suggestionId}":
    patch:
      description: Changes the proposed configuration of the caller's own open suggestion.
      operationId: CaseExtractionCodeController_updateOwn
      parameters:
      - name: suggestionId
        required: true
        in: path
        description: Id of the suggestion
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema: {}
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema: {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateExtractionCodeSuggestionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeSuggestionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Edit my open suggestion
      tags:
      - cases/extraction-code/suggestions
    delete:
      description: Deletes the caller's own open suggestion. Returns no body.
      operationId: CaseExtractionCodeController_deleteOwn
      parameters:
      - name: suggestionId
        required: true
        in: path
        description: Id of the suggestion
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema: {}
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema: {}
      responses:
        '204':
          description: Suggestion withdrawn
      security:
      - access-token: []
      summary: Withdraw my open suggestion
      tags:
      - cases/extraction-code/suggestions
  "/case/{caseId}/suggest/{codeId}/{suggestionId}/message":
    post:
      description: Adds a message to an open suggestion's discussion thread.
      operationId: CaseExtractionCodeController_appendMessage
      parameters:
      - name: suggestionId
        required: true
        in: path
        description: Id of the suggestion
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema: {}
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema: {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateExtractionCodeSuggestionMessageDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeSuggestionEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Comment on a suggestion
      tags:
      - cases/extraction-code/suggestions
  "/case/{caseId}/suggest/{codeId}/{suggestionId}/approve":
    patch:
      description: Applies the suggested configuration to the extraction code, approves
        and locks the code, and closes other open suggestions. Only the side opposite
        the suggester can approve.
      operationId: CaseExtractionCodeController_approve
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: suggestionId
        required: true
        in: path
        description: Id of the suggestion
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema: {}
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeSuggestionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Approve a suggestion
      tags:
      - cases/extraction-code/suggestions
  "/case/{caseId}/suggest/{codeId}/{suggestionId}/reject":
    patch:
      description: Rejects a suggestion, with an optional message. Only the side opposite
        the suggester can reject. The extraction code is not changed.
      operationId: CaseExtractionCodeController_reject
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: suggestionId
        required: true
        in: path
        description: Id of the suggestion
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema: {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RejectExtractionCodeSuggestionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeSuggestionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Reject a suggestion
      tags:
      - cases/extraction-code/suggestions
  "/case/{caseId}/extraction-code/{codeId}/approve":
    patch:
      description: Opposing counsel approves an extraction code awaiting review, with
        an optional message. Approval unblocks the custodian email and locks the code's
        configuration.
      operationId: CaseExtractionCodeReviewController_approve
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ReviewExtractionCodeDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Approve an extraction code
      tags:
      - cases/extraction-code/review
  "/case/{caseId}/extraction-code/{codeId}/reject":
    patch:
      description: Opposing counsel rejects an extraction code awaiting review, with
        an optional message.
      operationId: CaseExtractionCodeReviewController_reject
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ReviewExtractionCodeDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Reject an extraction code
      tags:
      - cases/extraction-code/review
  "/case/{caseId}/oc-approvals/approve":
    post:
      description: Opposing counsel approves a data provider's data (all of it, or
        a subset of items) so the case owner can see it. Approvals add to earlier
        ones.
      operationId: OcApprovalsController_approve
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/OcDataApprovalRequestDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OcDataApprovalEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Approve data for the other side
      tags:
      - cases/oc-approvals
  "/case/{caseId}/oc-approvals/reject":
    post:
      description: Removes an approval for a data provider, or only for the given
        items. Returns no body.
      operationId: OcApprovalsController_reject
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/OcDataApprovalRequestDto"
      responses:
        '204':
          description: Approval removed
      security:
      - access-token: []
      summary: Withdraw data approval
      tags:
      - cases/oc-approvals
  "/case/{caseId}/oc-approvals":
    get:
      description: Lists the case's data approvals, optionally for one data-provider
        type.
      operationId: OcApprovalsController_list
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: dataProviderType
        required: false
        in: query
        description: Filter approval rows by data provider type
        schema:
          type: string
          enum:
          - Device
          - UserEmailDataProvider
          - CaseFileProvider
          - FinancialAccountProvider
          - LegalDocumentProvider
          - DiscordUserProvider
          - RedditUserProvider
          - LinkedInUserProvider
          - AiChatProvider
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/OcDataApprovalEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List data approvals
      tags:
      - cases/oc-approvals
  "/case/access/{orgId}/case/{caseId}/oc-members/invite":
    post:
      description: Opposing counsel invites a colleague from their own firm to the
        shared case.
      operationId: OcMembersController_inviteOcColleague
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: orgId
        required: true
        in: path
        description: Id of the org
        schema:
          type: string
      requestBody:
        required: true
        description: OC colleague invitation body
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/InviteOcColleagueDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Invite a colleague (opposing counsel)
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/oc-members/{userId}":
    delete:
      description: Opposing counsel removes a colleague they added from the shared
        case.
      operationId: OcMembersController_removeOcColleague
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: userId
        required: true
        in: path
        description: Id of the OC member to remove
        schema:
          type: string
      - name: orgId
        required: true
        in: path
        description: Id of the org
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove a colleague (opposing counsel)
      tags:
      - case-access
  "/case/access/{orgId}/case/{caseId}/oc-members/invitation/{invitationId}":
    delete:
      description: Opposing counsel cancels a pending invitation they sent to a colleague.
      operationId: OcMembersController_cancelOwnOcInvitation
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: invitationId
        required: true
        in: path
        description: Id of the pending OC invitation to cancel
        schema:
          type: string
      - name: orgId
        required: true
        in: path
        description: Id of the org
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Cancel a colleague invitation (opposing counsel)
      tags:
      - case-access
  "/extraction/collection-events":
    post:
      description: 'Desktop and portal plumbing: records a batch of up to 50 collection
        events against the caller''s extraction code and returns a result per event
        (accepted, duplicate, rejected or retry). Returns 404 when collection events
        are turned off, 411 without a Content-Length header and 413 over 256 KB.'
      operationId: CollectionEventsIngestionController_ingest
      parameters:
      - name: content-length
        required: true
        in: header
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/IngestCollectionEventsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/IngestCollectionEventsResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Record collection events (internal)
      tags:
      - extraction
      x-internal: true
  "/admin/collection-ops/queue":
    get:
      description: Lists collections, one row per extraction code, for a filter (`all`,
        `priority`, `watched`, `errors`, `stalled`), optionally narrowed by progress,
        organization or a code-prefix / custodian-name search. Returns the page, the
        total for the chosen filter and the count for every filter. Super admin only;
        404 while the collection-ops console is turned off.
      operationId: CollectionOpsQueueController_list
      parameters:
      - name: skip
        required: false
        in: query
        schema:
          maximum: 10000
          default: 0
          type: number
      - name: limit
        required: false
        in: query
        schema:
          maximum: 100
          default: 25
          type: number
      - name: filter
        required: false
        in: query
        schema:
          default: all
          type: string
          enum:
          - all
          - priority
          - watched
          - errors
          - stalled
      - name: progress
        required: false
        in: query
        schema:
          type: string
          enum:
          - not_started
          - in_progress
          - not_completed
      - name: orgId
        required: false
        in: query
        schema:
          type: string
      - name: q
        required: false
        in: query
        schema:
          maxLength: 100
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      rows:
                        type: array
                        items:
                          type: object
                          properties:
                            extractionCodeId:
                              type: string
                              example: 65a1b2c3d4e5f6a7b8c9d0e1
                            code:
                              type: string
                            custodianName:
                              type: string
                              nullable: true
                            orgId:
                              type: string
                              nullable: true
                            caseId:
                              type: string
                              nullable: true
                            orgPriority:
                              type: boolean
                            watched:
                              type: boolean
                            stage:
                              type: string
                              enum:
                              - created
                              - email_sent
                              - portal_login
                              - download_clicked
                              - code_login
                              - extracting
                              - extracted
                              - uploaded
                              - completed
                            stageIndex:
                              type: number
                            status:
                              type: string
                              enum:
                              - active
                              - stalled
                              - error
                              - completed
                            lastEvent:
                              type: object
                              nullable: true
                              properties:
                                type:
                                  type: string
                                message:
                                  type: string
                                severity:
                                  type: string
                                at:
                                  type: string
                                  format: date-time
                            lastActivityAt:
                              type: string
                              format: date-time
                              nullable: true
                            openInboxItems:
                              type: number
                            livePercent:
                              type: number
                              nullable: true
                      total:
                        type: number
                        description: Rows matching the chosen filter
                        example: 87
                      counts:
                        type: object
                        description: Rows matching each filter
                        properties:
                          all:
                            type: number
                            example: 87
                          priority:
                            type: number
                            example: 6
                          watched:
                            type: number
                            example: 3
                          errors:
                            type: number
                            example: 9
                          stalled:
                            type: number
                            example: 4
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List the collection queue (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/queue/in-flight":
    get:
      description: 'Counts collections that are not completed and were active since
        the first day of the previous calendar month (UTC): extracting now, signed
        in with the code but not started, app downloaded but not opened, and portal
        login only. Super admin only; 404 while the collection-ops console is turned
        off.'
      operationId: CollectionOpsQueueController_inFlight
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      extractingNow:
                        type: number
                        example: 3
                      codeLoginNotStarted:
                        type: number
                        example: 5
                      downloadClickedNotOpened:
                        type: number
                        example: 2
                      portalLoginOnly:
                        type: number
                        example: 7
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Count in-flight collections by stage (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/live":
    get:
      description: Lists backup and upload attempts whose last heartbeat is within
        the configured heartbeat-lost window, across up to 500 collections, priority
        organizations first, each marked `running` or `slow`. Super admin only; 404
        while the collection-ops console is turned off.
      operationId: CollectionOpsQueueController_listLive
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      rows:
                        type: array
                        items:
                          type: object
                          properties:
                            extractionCodeId:
                              type: string
                              example: 65a1b2c3d4e5f6a7b8c9d0e1
                            code:
                              type: string
                            custodianName:
                              type: string
                              nullable: true
                            orgPriority:
                              type: boolean
                            attemptId:
                              type: string
                            activity:
                              type: string
                              enum:
                              - backup
                              - upload
                            dataSource:
                              type: string
                              nullable: true
                              example: ios
                            percent:
                              type: number
                              nullable: true
                              example: 42
                            rateBps:
                              type: number
                              nullable: true
                            etaSec:
                              type: number
                              nullable: true
                            phase:
                              type: string
                              nullable: true
                            freeDiskBytes:
                              type: number
                              nullable: true
                            lastHeartbeatAt:
                              type: string
                              format: date-time
                            status:
                              type: string
                              enum:
                              - running
                              - slow
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List running backups and uploads (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/inbox":
    get:
      description: Lists inbox items for one status (`open` by default; `cleared`
        looks back `clearedSinceHours`, default 24), optionally priority-only, grouped
        into one card per extraction code, priority organizations first, then most
        recent. Returns the page of cards, the total and the number of codes per status.
        Super admin only; 404 while the collection-ops console is turned off.
      operationId: CollectionOpsInboxController_list
      parameters:
      - name: skip
        required: false
        in: query
        schema:
          maximum: 10000
          default: 0
          type: number
      - name: limit
        required: false
        in: query
        schema:
          maximum: 100
          default: 25
          type: number
      - name: status
        required: false
        in: query
        schema:
          default: open
          type: string
          enum:
          - open
          - snoozed
          - cleared
      - name: priority
        required: false
        in: query
        schema:
          type: boolean
      - name: clearedSinceHours
        required: false
        in: query
        schema:
          maximum: 720
          default: 24
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      rows:
                        type: array
                        items:
                          type: object
                          properties:
                            extractionCodeId:
                              type: string
                              example: 65a1b2c3d4e5f6a7b8c9d0e1
                            code:
                              type: string
                              nullable: true
                            custodianName:
                              type: string
                              nullable: true
                            orgId:
                              type: string
                              nullable: true
                            caseId:
                              type: string
                              nullable: true
                            orgPriority:
                              type: boolean
                            stage:
                              type: string
                              nullable: true
                              enum:
                              - created
                              - email_sent
                              - portal_login
                              - download_clicked
                              - code_login
                              - extracting
                              - extracted
                              - uploaded
                              - completed
                            status:
                              type: string
                              nullable: true
                              enum:
                              - active
                              - stalled
                              - error
                              - completed
                            lastTriggeredAt:
                              type: string
                              format: date-time
                            recovered:
                              type: boolean
                              description: Every item on the card has a later success
                            items:
                              type: array
                              items:
                                type: object
                                properties:
                                  id:
                                    type: string
                                    example: 65a1b2c3d4e5f6a7b8c9d0e1
                                  reason:
                                    type: string
                                    enum:
                                    - backup_failed
                                    - upload_failed
                                    - processing_failed
                                    - export_failed
                                    - invite_failed
                                    - extraction_too_long
                                    - upload_too_long
                                    - slow_transfer
                                    - went_silent
                                    - device_not_detected
                                    - stalled
                                  errorCode:
                                    type: string
                                    nullable: true
                                    example: DEVICE_UNPLUGGED
                                  summary:
                                    type: string
                                    example: 'Backup failed: device unplugged'
                                  status:
                                    type: string
                                    enum:
                                    - open
                                    - snoozed
                                    - cleared
                                  triggerCount:
                                    type: number
                                    example: 2
                                  lastTriggeredAt:
                                    type: string
                                    format: date-time
                                  recoveredAt:
                                    type: string
                                    format: date-time
                                    nullable: true
                                  snoozedUntil:
                                    type: string
                                    format: date-time
                                    nullable: true
                                  clearedAt:
                                    type: string
                                    format: date-time
                                    nullable: true
                                  clearedBy:
                                    type: string
                                    nullable: true
                                    description: User id
                      total:
                        type: number
                        example: 12
                      counts:
                        type: object
                        description: Number of extraction codes per status
                        properties:
                          open:
                            type: number
                            example: 12
                          snoozed:
                            type: number
                            example: 2
                          cleared:
                            type: number
                            example: 5
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List inbox cards (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/inbox/items/{id}/clear":
    post:
      description: Clears an open or snoozed inbox item and records it on the timeline.
        Returns the updated item as stored. Returns 404 when the item is unknown or
        already cleared. Super admin only; 404 while the collection-ops console is
        turned off.
      operationId: CollectionOpsInboxController_clear
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the inbox item to clear
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: 'The inbox item as stored (raw document: `_id`, not
                      `id`)'
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      extractionCodeId:
                        type: string
                      orgId:
                        type: string
                        nullable: true
                      caseId:
                        type: string
                        nullable: true
                      orgPriority:
                        type: boolean
                      reason:
                        type: string
                        enum:
                        - backup_failed
                        - upload_failed
                        - processing_failed
                        - export_failed
                        - invite_failed
                        - extraction_too_long
                        - upload_too_long
                        - slow_transfer
                        - went_silent
                        - device_not_detected
                        - stalled
                      errorCode:
                        type: string
                        nullable: true
                      summary:
                        type: string
                      status:
                        type: string
                        enum:
                        - open
                        - snoozed
                        - cleared
                      isLive:
                        type: boolean
                        description: true while open or snoozed
                      firstTriggeredAt:
                        type: string
                        format: date-time
                      lastTriggeredAt:
                        type: string
                        format: date-time
                      triggerCount:
                        type: number
                      triggerEventIds:
                        type: array
                        items:
                          type: string
                      recoveredAt:
                        type: string
                        format: date-time
                        nullable: true
                      snoozedUntil:
                        type: string
                        format: date-time
                        nullable: true
                      clearedBy:
                        type: string
                        nullable: true
                      clearedAt:
                        type: string
                        format: date-time
                        nullable: true
                      createdAt:
                        type: string
                        format: date-time
                      updatedAt:
                        type: string
                        format: date-time
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Clear one inbox item (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/inbox/codes/{codeId}/clear":
    post:
      description: Clears every open or snoozed inbox item of the extraction code
        and returns how many were cleared. Super admin only; 404 while the collection-ops
        console is turned off.
      operationId: CollectionOpsInboxController_clearCode
      parameters:
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      cleared:
                        type: number
                        example: 3
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Clear all inbox items of a code (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/inbox/items/{id}/snooze":
    post:
      description: Snoozes an open or snoozed inbox item until `until`, which must
        be at least 1 minute and at most 90 days ahead (400 otherwise), and records
        it on the timeline. Returns the updated item as stored; 404 when the item
        is unknown or cleared. Super admin only; 404 while the collection-ops console
        is turned off.
      operationId: CollectionOpsInboxController_snooze
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the inbox item to snooze
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SnoozeInboxItemDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: 'The inbox item as stored (raw document: `_id`, not
                      `id`)'
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      extractionCodeId:
                        type: string
                      orgId:
                        type: string
                        nullable: true
                      caseId:
                        type: string
                        nullable: true
                      orgPriority:
                        type: boolean
                      reason:
                        type: string
                        enum:
                        - backup_failed
                        - upload_failed
                        - processing_failed
                        - export_failed
                        - invite_failed
                        - extraction_too_long
                        - upload_too_long
                        - slow_transfer
                        - went_silent
                        - device_not_detected
                        - stalled
                      errorCode:
                        type: string
                        nullable: true
                      summary:
                        type: string
                      status:
                        type: string
                        enum:
                        - open
                        - snoozed
                        - cleared
                      isLive:
                        type: boolean
                        description: true while open or snoozed
                      firstTriggeredAt:
                        type: string
                        format: date-time
                      lastTriggeredAt:
                        type: string
                        format: date-time
                      triggerCount:
                        type: number
                      triggerEventIds:
                        type: array
                        items:
                          type: string
                      recoveredAt:
                        type: string
                        format: date-time
                        nullable: true
                      snoozedUntil:
                        type: string
                        format: date-time
                        nullable: true
                      clearedBy:
                        type: string
                        nullable: true
                      clearedAt:
                        type: string
                        format: date-time
                        nullable: true
                      createdAt:
                        type: string
                        format: date-time
                      updatedAt:
                        type: string
                        format: date-time
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Snooze an inbox item (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/inbox/items/{id}/unsnooze":
    post:
      description: Reopens a snoozed inbox item now. Returns the updated item as stored;
        404 when the item is not snoozed. Super admin only; 404 while the collection-ops
        console is turned off.
      operationId: CollectionOpsInboxController_unsnooze
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the inbox item to unsnooze
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: 'The inbox item as stored (raw document: `_id`, not
                      `id`)'
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      extractionCodeId:
                        type: string
                      orgId:
                        type: string
                        nullable: true
                      caseId:
                        type: string
                        nullable: true
                      orgPriority:
                        type: boolean
                      reason:
                        type: string
                        enum:
                        - backup_failed
                        - upload_failed
                        - processing_failed
                        - export_failed
                        - invite_failed
                        - extraction_too_long
                        - upload_too_long
                        - slow_transfer
                        - went_silent
                        - device_not_detected
                        - stalled
                      errorCode:
                        type: string
                        nullable: true
                      summary:
                        type: string
                      status:
                        type: string
                        enum:
                        - open
                        - snoozed
                        - cleared
                      isLive:
                        type: boolean
                        description: true while open or snoozed
                      firstTriggeredAt:
                        type: string
                        format: date-time
                      lastTriggeredAt:
                        type: string
                        format: date-time
                      triggerCount:
                        type: number
                      triggerEventIds:
                        type: array
                        items:
                          type: string
                      recoveredAt:
                        type: string
                        format: date-time
                        nullable: true
                      snoozedUntil:
                        type: string
                        format: date-time
                        nullable: true
                      clearedBy:
                        type: string
                        nullable: true
                      clearedAt:
                        type: string
                        format: date-time
                        nullable: true
                      createdAt:
                        type: string
                        format: date-time
                      updatedAt:
                        type: string
                        format: date-time
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Wake a snoozed inbox item (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/collections/{codeId}":
    get:
      description: 'Returns one extraction code''s collection state: header (code,
        custodian, stage, status, requester and case), stage bar, per-source state,
        live backup/upload attempts, the last computer seen, up to 50 live inbox items
        and per-requested-source progress. Returns 404 when the code has no collection
        state. Super admin only; 404 while the collection-ops console is turned off.'
      operationId: CollectionOpsCollectionsController_get
      parameters:
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      header:
                        type: object
                        properties:
                          extractionCodeId:
                            type: string
                            example: 65a1b2c3d4e5f6a7b8c9d0e1
                          code:
                            type: string
                            example: K7Q2ZP
                          custodianName:
                            type: string
                            nullable: true
                            example: Jane Doe
                          orgId:
                            type: string
                            nullable: true
                          caseId:
                            type: string
                            nullable: true
                          orgPriority:
                            type: boolean
                          watched:
                            type: boolean
                          stage:
                            type: string
                            enum:
                            - created
                            - email_sent
                            - portal_login
                            - download_clicked
                            - code_login
                            - extracting
                            - extracted
                            - uploaded
                            - completed
                          status:
                            type: string
                            enum:
                            - active
                            - stalled
                            - error
                            - completed
                          lifecycleMs:
                            type: number
                            nullable: true
                            description: From creation to completion, or to now while
                              open
                          completedAt:
                            type: string
                            format: date-time
                            nullable: true
                          failedAttempts:
                            type: number
                          requestedBy:
                            type: object
                            nullable: true
                            properties:
                              userId:
                                type: string
                              name:
                                type: string
                                nullable: true
                              email:
                                type: string
                                nullable: true
                                example: paralegal@example.com
                          caseName:
                            type: string
                            nullable: true
                            example: Smith v. Jones
                      stageBar:
                        type: array
                        items:
                          type: object
                          properties:
                            stage:
                              type: string
                            tracked:
                              type: boolean
                            reached:
                              type: boolean
                            at:
                              type: string
                              format: date-time
                              nullable: true
                      sources:
                        type: array
                        items:
                          type: object
                          properties:
                            key:
                              type: string
                              example: ios:00008110-001A2B3C4D5E6F70
                            dataSource:
                              type: string
                              nullable: true
                              example: ios
                            stage:
                              type: string
                              enum:
                              - created
                              - email_sent
                              - portal_login
                              - download_clicked
                              - code_login
                              - extracting
                              - extracted
                              - uploaded
                              - completed
                            stageOrder:
                              type: number
                            status:
                              type: string
                              enum:
                              - active
                              - error
                              - completed
                            lastAt:
                              type: string
                              format: date-time
                            lastErrorCode:
                              type: string
                              nullable: true
                      live:
                        type: array
                        items:
                          type: object
                          properties:
                            attemptId:
                              type: string
                            activity:
                              type: string
                              enum:
                              - backup
                              - upload
                            dataSource:
                              type: string
                              nullable: true
                            deviceId:
                              type: string
                              nullable: true
                            systemId:
                              type: string
                              nullable: true
                            percent:
                              type: number
                              nullable: true
                            bytesDone:
                              type: number
                              nullable: true
                            bytesTotal:
                              type: number
                              nullable: true
                            rateBps:
                              type: number
                              nullable: true
                            etaSec:
                              type: number
                              nullable: true
                            phase:
                              type: string
                              nullable: true
                            freeDiskBytes:
                              type: number
                              nullable: true
                            slowSince:
                              type: string
                              format: date-time
                              nullable: true
                            startedAt:
                              type: string
                              format: date-time
                              nullable: true
                            lastHeartbeatAt:
                              type: string
                              format: date-time
                      computer:
                        type: object
                        nullable: true
                        properties:
                          systemId:
                            type: string
                            nullable: true
                          appVersion:
                            type: string
                            nullable: true
                            example: 3.14.0
                          os:
                            type: string
                            nullable: true
                          model:
                            type: string
                            nullable: true
                          totalMemBytes:
                            type: number
                            nullable: true
                          freeDiskBytes:
                            type: number
                            nullable: true
                          at:
                            type: string
                            format: date-time
                            nullable: true
                      openItems:
                        type: array
                        description: Live inbox items, newest first, at most 50
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: 65a1b2c3d4e5f6a7b8c9d0e1
                            reason:
                              type: string
                              enum:
                              - backup_failed
                              - upload_failed
                              - processing_failed
                              - export_failed
                              - invite_failed
                              - extraction_too_long
                              - upload_too_long
                              - slow_transfer
                              - went_silent
                              - device_not_detected
                              - stalled
                            errorCode:
                              type: string
                              nullable: true
                              example: DEVICE_UNPLUGGED
                            summary:
                              type: string
                              example: 'Backup failed: device unplugged'
                            status:
                              type: string
                              enum:
                              - open
                              - snoozed
                              - cleared
                            triggerCount:
                              type: number
                              example: 2
                            lastTriggeredAt:
                              type: string
                              format: date-time
                            recoveredAt:
                              type: string
                              format: date-time
                              nullable: true
                            snoozedUntil:
                              type: string
                              format: date-time
                              nullable: true
                            clearedAt:
                              type: string
                              format: date-time
                              nullable: true
                            clearedBy:
                              type: string
                              nullable: true
                              description: User id
                      requestedSources:
                        type: array
                        items:
                          type: object
                          properties:
                            dataSource:
                              type: string
                              example: gmail
                            sourceType:
                              type: string
                            status:
                              type: string
                              enum:
                              - pending
                              - in_progress
                              - failed
                              - completed
                            rows:
                              type: number
                            completedRows:
                              type: number
                      sourcesProgress:
                        type: object
                        properties:
                          requested:
                            type: number
                            example: 3
                          completed:
                            type: number
                            example: 1
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get one collection's detail (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/collections/{codeId}/timeline":
    get:
      description: Returns a page of the code's collection events, newest first unless
        `order=asc`, filterable by event types and severities, with the total. Attachments
        are listed by index only; a download link is minted per attachment by its
        own route. Super admin only; 404 while the collection-ops console is turned
        off.
      operationId: CollectionOpsCollectionsController_list
      parameters:
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      - name: skip
        required: false
        in: query
        schema:
          maximum: 10000
          default: 0
          type: number
      - name: limit
        required: false
        in: query
        schema:
          maximum: 100
          default: 25
          type: number
      - name: order
        required: false
        in: query
        schema:
          default: desc
          type: string
          enum:
          - desc
          - asc
      - name: types
        required: false
        in: query
        description: Comma-separated event types.
        schema:
          type: array
          items:
            type: string
      - name: severity
        required: false
        in: query
        description: Comma-separated severities.
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      rows:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: 65a1b2c3d4e5f6a7b8c9d0e1
                            eventId:
                              type: string
                            type:
                              type: string
                              example: backup.failed
                            source:
                              type: string
                              enum:
                              - api
                              - app
                              - portal
                              - ops
                              - system
                            severity:
                              type: string
                              enum:
                              - info
                              - warning
                              - error
                            message:
                              type: string
                            occurredAt:
                              type: string
                              format: date-time
                            receivedAt:
                              type: string
                              format: date-time
                            clockSkewMs:
                              type: number
                              nullable: true
                            count:
                              type: number
                              description: Repeats collapsed into this row
                            firstAt:
                              type: string
                              format: date-time
                            lastAt:
                              type: string
                              format: date-time
                            dataSource:
                              type: string
                              nullable: true
                            deviceId:
                              type: string
                              nullable: true
                            systemId:
                              type: string
                              nullable: true
                            attemptId:
                              type: string
                              nullable: true
                            errorCode:
                              type: string
                              nullable: true
                            error:
                              type: object
                              nullable: true
                              properties:
                                title:
                                  type: string
                                  example: Device unplugged
                                explanation:
                                  type: string
                                category:
                                  type: string
                                  enum:
                                  - connection
                                  - disk
                                  - permission
                                  - device
                                  - encryption
                                  - server
                                  - unknown
                                severity:
                                  type: string
                                  enum:
                                  - info
                                  - warning
                                  - error
                            details:
                              type: object
                              nullable: true
                              additionalProperties: true
                            attachments:
                              type: array
                              items:
                                type: object
                                properties:
                                  index:
                                    type: number
                                  kind:
                                    type: string
                                  sizeBytes:
                                    type: number
                                    nullable: true
                            origin:
                              type: string
                              example: v2
                            actorUserId:
                              type: string
                              nullable: true
                      total:
                        type: number
                        example: 140
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a collection's event timeline (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/collections/{codeId}/attachments/{eventId}/{index}":
    get:
      description: Returns a signed URL, valid for 15 minutes, for one attachment
        of a collection event. Returns 404 when the event does not belong to the code,
        the index has no attachment or the file is unavailable. Super admin only;
        404 while the collection-ops console is turned off.
      operationId: CollectionOpsCollectionsController_attachment
      parameters:
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      - name: eventId
        required: true
        in: path
        description: Id of the collection event
        schema:
          type: string
      - name: index
        required: true
        in: path
        description: Attachment index on the event
        schema:
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - url
                    - expiresAt
                    properties:
                      url:
                        type: string
                        example: https://bucket.s3.amazonaws.com/logs/app-log.zip?X-Amz-Signature=abc123
                      expiresAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:30:00.000Z'
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a download link for an event attachment (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/collections/{codeId}/request-logs":
    post:
      description: Asks up to 10 online desktop apps on the code for their app log
        and one device log per phone source, and records the request on the timeline.
        Returns how many computers were asked and how many device-log requests went
        out; 404 for an unknown code. Super admin only; 404 while the collection-ops
        console is turned off.
      operationId: CollectionOpsCollectionsController_request
      parameters:
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      computers:
                        type: number
                        example: 1
                      deviceLogRequests:
                        type: number
                        example: 2
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Ask the custodian's computers for logs (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/collections/{codeId}/watch":
    patch:
      description: Adds the extraction code to the watch list or removes it, and returns
        the new value. Returns 404 for an unknown code. Super admin only; 404 while
        the collection-ops console is turned off.
      operationId: CollectionOpsCollectionsController_watch
      parameters:
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SetWatchedDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      watched:
                        type: boolean
                        example: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Watch or unwatch a collection (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/organizations/{id}/priority":
    patch:
      description: Sets or clears the organization's priority flag and copies it to
        its collections and live inbox items. Returns the new value and how many collections
        changed; 404 for an unknown organization. Super admin only; 404 while the
        collection-ops console is turned off.
      operationId: CollectionOpsCollectionsController_priority
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SetOrgPriorityDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      isPriority:
                        type: boolean
                        example: true
                      statesUpdated:
                        type: number
                        example: 4
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Mark an organization as priority (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/stats":
    get:
      description: 'Returns stats for collections created in a date range (default
        the last 30 days, at most 366; 400 when `from` is not before `to`), optionally
        for one organization: cohort size and completion rate, lifecycle times, the
        stage funnel, median time in each stage, phone-only portal logins, requested-source
        progress and current open inbox items by reason. Super admin only; 404 while
        the collection-ops console is turned off.'
      operationId: CollectionOpsStatsController_get
      parameters:
      - name: from
        required: false
        in: query
        schema:
          format: date-time
          type: string
      - name: to
        required: false
        in: query
        schema:
          format: date-time
          type: string
      - name: orgId
        required: false
        in: query
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      from:
                        type: string
                        format: date-time
                      to:
                        type: string
                        format: date-time
                      cohortSize:
                        type: number
                        example: 120
                      completed:
                        type: number
                        example: 96
                      completionRate:
                        type: number
                        nullable: true
                        example: 0.8
                      lifecycle:
                        type: object
                        properties:
                          avgMs:
                            type: number
                            nullable: true
                          medianMs:
                            type: number
                            nullable: true
                      funnel:
                        type: array
                        items:
                          type: object
                          properties:
                            stage:
                              type: string
                            tracked:
                              type: boolean
                            count:
                              type: number
                              nullable: true
                            drop:
                              type: number
                              nullable: true
                      medianTimeInStage:
                        type: array
                        items:
                          type: object
                          properties:
                            from:
                              type: string
                            to:
                              type: string
                            medianMs:
                              type: number
                              nullable: true
                            samples:
                              type: number
                            longest:
                              type: boolean
                      phoneOnlyPortalLogins:
                        type: number
                      sourcesRequested:
                        type: number
                      sourcesCompleted:
                        type: number
                      codesWithRequestedSources:
                        type: number
                      fullyCompleted:
                        type: number
                      fullCompletionRate:
                        type: number
                        nullable: true
                      bySourceType:
                        type: array
                        items:
                          type: object
                          properties:
                            sourceType:
                              type: string
                            requested:
                              type: number
                            completed:
                              type: number
                      needsAttention:
                        type: object
                        description: Open inbox items now, not limited to the date
                          range
                        properties:
                          total:
                            type: number
                            example: 9
                          byReason:
                            type: object
                            additionalProperties:
                              type: number
                            example:
                              backup_failed: 4
                              went_silent: 5
                      truncated:
                        type: boolean
                        description: The cohort hit the 50,000-row cap
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get collection funnel and lifecycle stats (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/errors/{errorCode}/stats":
    get:
      description: 'For one error code over the last `days` (default 30, at most 365):
        its catalog entry, how many times it fired, how many extraction codes it affected
        and how many of those later completed. Returns 400 for a malformed code. Super
        admin only; 404 while the collection-ops console is turned off.'
      operationId: CollectionOpsStatsController_errorStats
      parameters:
      - name: errorCode
        required: true
        in: path
        description: Raw error code, e.g. DEVICE_UNPLUGGED
        schema:
          type: string
      - name: days
        required: false
        in: query
        schema:
          minimum: 1
          maximum: 365
          default: 30
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      errorCode:
                        type: string
                        example: DEVICE_UNPLUGGED
                      days:
                        type: number
                        example: 30
                      error:
                        type: object
                        properties:
                          title:
                            type: string
                            example: Device unplugged
                          explanation:
                            type: string
                          category:
                            type: string
                            enum:
                            - connection
                            - disk
                            - permission
                            - device
                            - encryption
                            - server
                            - unknown
                          severity:
                            type: string
                            enum:
                            - info
                            - warning
                            - error
                      hits:
                        type: number
                        example: 17
                      codesAffected:
                        type: number
                        example: 9
                      laterCompleted:
                        type: number
                        example: 6
                      neverCompleted:
                        type: number
                        example: 3
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get stats for one error code (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/collections/{codeId}/timeline.md":
    get:
      description: Returns the code's timeline (the newest 2,000 events, oldest first)
        as a markdown document, with event messages passed through PII redaction and
        signed links to attachments. Returns 404 when the code has no collection state.
        Super admin only; 404 while the collection-ops console is turned off.
      operationId: CollectionOpsMarkdownController_markdown
      parameters:
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      responses:
        '200':
          description: Markdown document (text/markdown). Not wrapped in the JSON
            envelope.
          content:
            text/markdown:
              schema:
                type: string
      security:
      - access-token: []
      summary: Get a collection timeline as markdown (internal)
      tags:
      - admin
      x-internal: true
  "/admin/collection-ops/collections/{codeId}/claude-link":
    post:
      description: Mints a signed, read-only link to the code's markdown timeline,
        valid for the configured number of days (7 by default, at most 30), and records
        it on the timeline. A link cannot be revoked on its own. Returns 503 when
        the link secret is not configured and 404 when the code has no collection
        state. Super admin only; 404 while the collection-ops console is turned off.
      operationId: CollectionOpsMarkdownController_claudeLink
      parameters:
      - name: codeId
        required: true
        in: path
        description: Id of the extraction code
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - url
                    - expiresAt
                    properties:
                      url:
                        type: string
                        example: https://api.example.com/public/collection-ops/timeline/eyJjIjoiNjVhMWIyYzMifQ.c2lnbmF0dXJl
                      expiresAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:30:00.000Z'
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a read-only link to a collection timeline (internal)
      tags:
      - admin
      x-internal: true
  "/public/collection-ops/timeline/{token}":
    get:
      description: Returns the markdown timeline for a link minted by the timeline-link
        route. No auth; limited to 30 requests per minute. A bad, forged or expired
        token, an unconfigured secret, the console being off and an unknown collection
        all return the same 404.
      operationId: CollectionOpsPublicController_timeline
      parameters:
      - name: token
        required: true
        in: path
        description: Signed link token
        schema:
          type: string
      responses:
        '200':
          description: Markdown document (text/markdown). Not wrapped in the JSON
            envelope.
          content:
            text/markdown:
              schema:
                type: string
      summary: Read a collection timeline from a shared link (internal)
      tags:
      - public
      x-internal: true
  "/certificates":
    post:
      description: 'Stores a collection certificate for a case: its data, the PDF
        and JSON files (hashed so uploads can be verified later) and the source ids
        it covers, and records which side created it. Opposing counsel may create
        one only while that feature is turned on (403 otherwise). Returns 400 when
        the certificate id already exists and 404 for an unknown case. Requires a
        role in the organization and access to the case.'
      operationId: CertificateController_create
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateCertificateDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CertificateEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Store a certificate
      tags:
      - certificates
    get:
      description: Lists the organization's certificates for the case in `caseId`,
        newest first, optionally only those covering `sourceId`, paged with `page`
        and `perPage` (10 per page by default). Requires a role in the organization.
      operationId: CertificateController_list
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: caseId
        required: true
        in: query
        schema:
          type: string
      - name: sourceId
        required: false
        in: query
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CertificateListEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List the certificates of a case
      tags:
      - certificates
  "/certificates/verify-document":
    post:
      description: Public, no sign-in; at most 10 requests a minute. Checks an uploaded
        certificate PDF or JSON against the stored certificate (file hash, plus the
        PDF signature or the JSON content) and returns `authentic`, `tampered` or
        `revoked` with a message. Returns 400 for a missing file or another file type
        and 404 when the file names no known certificate.
      operationId: CertificateController_verifyDocument
      parameters: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DocumentVerificationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Verify a certificate file
      tags:
      - certificates
  "/certificates/{certificateId}/verify":
    get:
      description: 'Public, no sign-in; at most 20 requests a minute. Returns the
        public details of a certificate: status, which side created it, collection
        and issue dates, custodian, device, item and message counts, date range and
        data hash. Returns 404 for an unknown id.'
      operationId: CertificateController_verify
      parameters:
      - name: certificateId
        required: true
        in: path
        description: Certificate ID (e.g., HC-2026-05-15-A1B2-1F3)
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CertificateVerificationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
        '404':
          description: Certificate not found
      security:
      - access-token: []
      summary: Verify a certificate by id
      tags:
      - certificates
  "/certificates/{certificateId}/pdf":
    get:
      description: Returns a signed, time-limited download link to the certificate
        PDF. Returns 404 when the organization has no such certificate or the file
        cannot be fetched. Requires a role in the organization.
      operationId: CertificateController_getPdfUrl
      parameters:
      - name: certificateId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      url:
                        type: string
                        example: https://user-data.s3.amazonaws.com/certificates/HC-2026-05-15-A1B2-1F3.pdf?X-Amz-Signature=abc123
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the certificate PDF link
      tags:
      - certificates
  "/certificates/{certificateId}/json":
    get:
      description: Returns a signed, time-limited download link to the certificate
        JSON. Returns 404 when the organization has no such certificate or the file
        cannot be fetched. Requires a role in the organization.
      operationId: CertificateController_getJsonUrl
      parameters:
      - name: certificateId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      url:
                        type: string
                        example: https://user-data.s3.amazonaws.com/certificates/HC-2026-05-15-A1B2-1F3.json?X-Amz-Signature=abc123
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the certificate JSON link
      tags:
      - certificates
  "/certificates/{certificateId}":
    patch:
      description: Marks the certificate revoked with `revocationReason` and the time,
        so verification reports it as revoked from then on. Returns 404 when the organization
        has no such certificate. Requires an org admin or owner.
      operationId: CertificateController_revoke
      parameters:
      - name: certificateId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RevokeCertificateDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CertificateEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Revoke a certificate
      tags:
      - certificates
  "/conversation/bulk-import":
    post:
      description: 'Desktop upload plumbing: imports a batch of conversations and
        messages for a device. The desktop app uses the /extraction twin of this route.'
      operationId: ConversationController_bulkUpload
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        description: Bulk upload conversation
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UploadBulkConversationDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Bulk-import conversations (internal)
      tags:
      - conversation
      x-internal: true
  "/conversation/search":
    get:
      description: Lists conversations in the caller's cases (or one case), with their
        participants, message counts and dates, optionally filtered. On cases shared
        with opposing counsel, only conversations the caller may see are returned.
      operationId: ConversationController_searchConversations
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: case
        required: false
        in: query
        schema:
          type: string
      - name: conversationIds
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 3
          maxLength: 100
          type: string
      - name: source
        required: false
        in: query
        schema:
          type: string
          enum:
          - message
          - whatsapp
          - voicemail
          - call_log
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
      - name: sources
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
            enum:
            - message
            - whatsapp
            - voicemail
            - call_log
            - facebook_dump
            - instagram_dump
            - threads_dump
            - email
      - name: linkedContacts
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: devices
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: name
          type: string
          enum:
          - createdAt
          - updatedAt
          - name
          - lastMessageDate
      - name: hideExported
        required: false
        in: query
        description: 0 false, 1 true, default 0 (false)
        schema:
          default: 0
          type: number
      - name: isArchived
        required: false
        in: query
        description: 0 false, 1 true, default 0 (false)
        schema:
          default: 0
          type: number
      - name: isStarred
        required: false
        in: query
        description: 0 false, 1 true, default 0 (false)
        schema:
          default: 0
          type: number
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DeviceConversationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List conversations
      tags:
      - conversation
  "/conversation/search-entries":
    get:
      description: Lists messages for the selected conversations of a case, with attachments,
        tags and comment counts. Dates are the device's wall-clock time labelled UTC.
      operationId: ConversationController_searchConversationEntries
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: case
        required: false
        in: query
        description: Required if caseSlug not present
        schema:
          type: string
      - name: caseSlug
        required: false
        in: query
        description: Required if case not present
        schema:
          type: string
      - name: conversation
        required: false
        in: query
        description: ID of the conversation to search for
        schema:
          type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: excludeTags
        required: false
        in: query
        description: Exclude messages carrying any of these tag ids. Must not overlap
          `tags`.
        schema:
          type: array
          items:
            type: string
      - name: untagged
        required: false
        in: query
        description: When truthy (1/true), return only messages with no tags attached.
          Takes precedence over the `tags`/`excludeTags` filters.
        schema:
          type: boolean
      - name: source
        required: false
        in: query
        schema:
          type: string
          enum:
          - message
          - whatsapp
          - voicemail
          - call_log
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
      - name: sources
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
            enum:
            - message
            - whatsapp
            - voicemail
            - call_log
            - facebook_dump
            - instagram_dump
            - threads_dump
            - email
      - name: linkedContacts
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: devices
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: startDate
        required: false
        in: query
        schema:
          format: date-time
          type: string
      - name: endDate
        required: false
        in: query
        schema:
          format: date-time
          type: string
      - name: hasAttachments
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      - name: sortBy
        required: false
        in: query
        schema:
          default: messageIndex
          type: string
          enum:
          - sentAt
          - date
          - messageIndex
      - name: sharedStatus
        required: false
        in: query
        description: 'Opposing-council only: filter to entries whose ocSharedStatus
          matches (e.g. `pending` for "show me what I have not shared yet"). Ignored
          for non-OC callers.'
        schema:
          type: string
          enum:
          - shared
          - pending
          - not_gated
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/SearchEntryMessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List messages in a conversation
      tags:
      - conversation
  "/conversation/compare":
    post:
      description: Merges messages from several conversations into one timeline, grouped
        by minute (up to 200 groups per page). Pass the returned `next` token to get
        the following page.
      operationId: ConversationController_compareConversationsEntries
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CompareConversationMessagesDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      next:
                        type: string
                        nullable: true
                        description: Opaque cursor for the next page; null on the
                          last page
                        example: eyJkYXRlIjoiMjAyNi0wMS0xNVQxNDozMTowMC4wMDBaIn0
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            _id:
                              type: string
                              format: date-time
                              description: Start of the minute
                            date:
                              type: string
                              format: date-time
                            count:
                              type: number
                              example: 3
                            messages:
                              type: array
                              items:
                                "$ref": "#/components/schemas/CompareDeviceMessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Compare conversations side by side
      tags:
      - conversation
  "/conversation/search-fuzzy":
    get:
      description: Finds messages in a case whose text matches the search text, with
        the match highlighted.
      operationId: ConversationController_searchConversationEntriesText
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: case
        required: false
        in: query
        description: Required if caseSlug not present
        schema:
          type: string
      - name: caseSlug
        required: false
        in: query
        description: Required if case not present
        schema:
          type: string
      - name: conversation
        required: false
        in: query
        description: ID of the conversation to search for
        schema:
          type: string
      - name: searchText
        required: true
        in: query
        schema:
          minLength: 3
          maxLength: 100
          type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: sources
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
            enum:
            - message
            - whatsapp
            - voicemail
            - call_log
            - facebook_dump
            - instagram_dump
            - threads_dump
            - email
      - name: linkedContacts
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: devices
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: startDate
        required: false
        in: query
        schema:
          format: date-time
          type: string
      - name: endDate
        required: false
        in: query
        schema:
          format: date-time
          type: string
      - name: bbCode
        required: false
        in: query
        schema:
          default: highlight="#FFFF00"
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DeviceMessageTextSearchResponse"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search message text
      tags:
      - conversation
  "/conversation/search-advance":
    post:
      description: Finds messages in a case matching keyword conditions, with the
        matches highlighted.
      operationId: ConversationController_searchConversationEntriesAdvance
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AdvanceTextSearchDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DeviceMessageTextSearchResponse"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Advanced message search
      tags:
      - conversation
  "/conversation/{caseId}/tags-count":
    post:
      description: Returns how many messages in the case carry each tag, for the given
        filters.
      operationId: ConversationController_getMessageTagsCount
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/MessageTagsCountDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageTagsCountResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Count messages per tag
      tags:
      - conversation
  "/conversation/search-uploaded-conversations":
    get:
      description: 'Desktop upload plumbing: lists conversations already uploaded
        for a device. The desktop app uses the /extraction twin of this route.'
      operationId: ConversationController_searchUploadedConversation
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: case
        required: true
        in: query
        schema:
          type: string
      - name: deviceExternalId
        required: false
        in: query
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UploadedConversation"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List uploaded conversations (internal)
      tags:
      - conversation
      x-internal: true
  "/conversation/{conversationId}/nearest-message-by-date":
    get:
      description: Returns the id, position and date of the message in a conversation
        closest to the given date, so a client can jump to it.
      operationId: ConversationController_searchNearestMessageByDate
      parameters:
      - name: date
        required: true
        in: query
        description: Date to search
        schema:
          type: string
      - name: case
        required: true
        in: query
        description: Case id to search for messages
        schema:
          type: string
      - name: conversationId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/SearchMessageByDateResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Find the message nearest a date
      tags:
      - conversation
  "/conversation/entries/tags/{caseId}/bulk":
    post:
      description: Applies tags to many messages at once and returns the updated messages.
      operationId: ConversationEntriesTagsController_bulkCreate
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        description: Tags to update
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateBulkMessageTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UpdateBulkMessageTagsResultEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag many messages
      tags:
      - conversation
  "/conversation/entries/tags/{caseId}/{messageId}":
    post:
      description: Adds tags to one message and returns it.
      operationId: ConversationEntriesTagsController_create
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: messageId
        required: true
        in: path
        description: Id of the message
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        description: Tags to update
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateMessageTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DeviceMessageEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag a message
      tags:
      - conversation
    delete:
      description: Removes tags from one message and returns it.
      operationId: ConversationEntriesTagsController_remove
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: messageId
        required: true
        in: path
        description: Id of the message
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        description: Tags to remove
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RemoveMessageTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DeviceMessageEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Untag a message
      tags:
      - conversation
  "/aws/s3-user-data-upload-credentials":
    post:
      description: Returns temporary AWS credentials, with the bucket, region and
        API version, for uploading the caller's data to S3. The organization named
        by `orgId` or `organization` in the body or query, or by the `organization`
        header, must have an active, paid plan; otherwise 403. Requires membership
        of the organization in the `organization` header (403 otherwise).
      operationId: AwsController_getTemporaryUserDataUploadToken
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AwsTemporaryS3CredentialEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get temporary S3 upload credentials
      tags:
      - aws
  "/common/file/add":
    post:
      description: 'Desktop plumbing: registers an object already in the user-data
        bucket as a file record (name from the key, content type from `type` or from
        S3) and returns it; a key that is already registered returns the existing
        record. Returns 404 when the key is not in the bucket. Keys in folders the
        API writes itself (email bodies, certificates, statements, exports, logs,
        device backups) cannot be registered (403).'
      operationId: CommonFileController_addFile
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddFileDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserFileEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Register an uploaded S3 object as a file (internal)
      tags:
      - common
      x-internal: true
  "/common/file/{fileId}/signed-url":
    get:
      description: Returns a signed download URL for a file and the time it is valid
        until (up to 24 hours). Returns 404 for an unknown or non-S3 file.
      operationId: CommonFileController_getSignedUrl
      parameters:
      - name: fileId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - url
                    - expiresAt
                    properties:
                      url:
                        type: string
                        example: https://bucket.s3.amazonaws.com/files/report.pdf?X-Amz-Signature=abc123
                      expiresAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:30:00.000Z'
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a signed download URL for a file
      tags:
      - common
  "/common/file/amd-driver/{systemType}":
    get:
      description: 'Desktop plumbing: streams the Apple Mobile Device Support installer
        for Windows; `x64` gets the 64-bit build and any other value the 32-bit one.
        No auth. Returns 404 when the file cannot be streamed.'
      operationId: CommonFileController_getAmdFile
      parameters:
      - name: systemType
        required: true
        in: path
        description: "`x64` for the 64-bit installer; any other value returns the
          32-bit one"
        schema:
          type: string
      responses:
        '200':
          description: The .msi installer. Not wrapped in the JSON envelope.
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
      summary: Download the Apple Mobile Device Support installer (internal)
      tags:
      - common
      x-internal: true
  "/common/file/{fileId}":
    get:
      description: Downloads a file by id. No auth. Usually answers 307 with a redirect
        to a signed storage URL of the original; otherwise streams the bytes. Returns
        404 when the stored original is missing.
      operationId: CommonFileController_getFile
      parameters:
      - name: fileId
        required: true
        in: path
        description: Id of the file
        schema:
          type: string
      responses:
        '200':
          description: The file bytes, streamed when no signed URL is available. Not
            wrapped in the JSON envelope.
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '307':
          description: 'Usual answer: redirect (Location header) to a short-lived
            signed storage URL of the original file.'
      summary: Download a file
      tags:
      - common
  "/common/file/{fileId}/original/{fileName}":
    get:
      description: Downloads a file by id. No auth. Usually answers 307 with a redirect
        to a signed storage URL of the original; otherwise streams the bytes. Returns
        404 when the stored original is missing. The trailing file name is ignored.
      operationId: CommonFileController_getOriginalFileWithName
      parameters:
      - name: fileId
        required: true
        in: path
        description: Id of the file
        schema:
          type: string
      - name: fileName
        required: true
        in: path
        description: Any file name; ignored, it only gives the URL a readable name
        schema: {}
      responses:
        '200':
          description: The file bytes, streamed when no signed URL is available. Not
            wrapped in the JSON envelope.
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '307':
          description: 'Usual answer: redirect (Location header) to a short-lived
            signed storage URL of the original file.'
      summary: Download a file's original, with a file name in the URL
      tags:
      - common
  "/common/file/{fileId}/display/{fileName}":
    get:
      description: Downloads the browser-displayable copy of a file (for example HEIC
        converted to PNG, or audio converted to MP3). No auth. Redirects (307) to
        that copy when it exists; otherwise redirects to the original and, for convertible
        types, queues the conversion so the copy exists next time. Streams the bytes
        when no signed URL is available. Returns 404 when neither copy is available.
      operationId: CommonFileController_getDisplayableFileWithName
      parameters:
      - name: fileId
        required: true
        in: path
        description: Id of the file
        schema:
          type: string
      - name: fileName
        required: true
        in: path
        description: Any file name; ignored, it only gives the URL a readable name
        schema: {}
      responses:
        '200':
          description: The file bytes, streamed when no signed URL is available. Not
            wrapped in the JSON envelope.
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '307':
          description: 'Usual answer: redirect (Location header) to a short-lived
            signed storage URL of the displayable copy, or of the original when the
            copy is missing.'
      summary: Download a file's browser-displayable version
      tags:
      - common
  "/common/file/{fileId}/{fileName}":
    get:
      description: Downloads a file by id. No auth. Usually answers 307 with a redirect
        to a signed storage URL of the original; otherwise streams the bytes. Returns
        404 when the stored original is missing. The trailing file name is ignored.
      operationId: CommonFileController_getFileWithName
      parameters:
      - name: fileId
        required: true
        in: path
        description: Id of the file
        schema:
          type: string
      - name: fileName
        required: true
        in: path
        description: Any file name; ignored, it only gives the URL a readable name
        schema: {}
      responses:
        '200':
          description: The file bytes, streamed when no signed URL is available. Not
            wrapped in the JSON envelope.
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '307':
          description: 'Usual answer: redirect (Location header) to a short-lived
            signed storage URL of the original file.'
      summary: Download a file, with a file name in the URL
      tags:
      - common
  "/application/latest":
    get:
      description: Returns the latest desktop app version and installer files (URL,
        SHA-512 and size) for macOS and Windows; each file also carries a full `downloadLink`.
        `organization` selects a white-label build; an unknown or missing value returns
        the standard app. No auth.
      operationId: ApplicationController_latestAppLinks
      parameters:
      - name: organization
        required: false
        in: query
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LatestAppLinksEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      summary: Get the latest desktop app downloads
      tags:
      - application
  "/application/refresh":
    get:
      description: 'CI hook: clears and re-reads the cached app links for a white-label
        build (`organization`, default the standard app), invalidates the CDN and,
        when `referrer=ci` and `type` is `mac` or `windows`, announces the new build
        internally. No auth. Always reports success, even when a step failed.'
      operationId: ApplicationController_refreshAppLinks
      parameters:
      - name: referrer
        required: false
        in: query
        schema:
          type: string
      - name: type
        required: false
        in: query
        schema:
          type: string
      - name: organization
        required: false
        in: query
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      summary: Refresh the cached desktop app links (internal)
      tags:
      - application
      x-internal: true
  "/application/refresh-android":
    get:
      description: 'Invalidates the CDN path of the Android APK. No auth. A failure
        comes back as 200 with `isSuccess: false` and the error message.'
      operationId: ApplicationController_refreshAndroidAppLink
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      summary: Refresh the Android app download (internal)
      tags:
      - application
      x-internal: true
  "/device/file-hash":
    post:
      description: 'Desktop plumbing: stores the hashes of a device''s backup files.'
      operationId: DeviceController_addUpdateFileHash
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddUpdateDeviceFileHashDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Record device file hashes (internal)
      tags:
      - devices
      x-internal: true
  "/device/match-file-hash":
    post:
      description: 'Desktop plumbing: checks a list of backup files against the hashes
        recorded for the device. Currently always returns matched: true (known issue).'
      operationId: DeviceController_matchFileHash
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/MatchDeviceFileHashDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MatchDeviceFileHashResultEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Check a device file hash (internal)
      tags:
      - devices
      x-internal: true
  "/device/{deviceId}":
    get:
      description: Returns one device, including which data types exist on it (availableData),
        optionally limited to one case or extraction code.
      operationId: DeviceController_getDeviceDetails
      parameters:
      - name: deviceId
        required: true
        in: path
        description: Id of the device
        schema:
          type: string
      - name: caseId
        required: false
        in: query
        description: Scope availability to this case
        schema:
          type: string
      - name: extractionCode
        required: false
        in: query
        description: Scope availability to this extraction code
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DeviceEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a device
      tags:
      - devices
    patch:
      description: Updates a device's name, manufacturer, brand or model and returns
        the device.
      operationId: DeviceController_updateDeviceDetails
      parameters:
      - name: deviceId
        required: true
        in: path
        description: Id of the device
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateDeviceDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DeviceEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update a device
      tags:
      - devices
  "/device-activity-log":
    post:
      description: 'Desktop plumbing: records a device activity event (plugged in,
        backup started or finished, upload progress). A repeat of the same event within
        a short window is skipped.'
      operationId: DeviceActivityLogController_addActivityLog
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddDeviceActivityLogDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Record device activity (internal)
      tags:
      - device-activity-log
      x-internal: true
  "/comments/case/{caseId}/message/{messageId}":
    post:
      description: Adds a comment to a message. Prefer POST /comments/case/:caseId/source/:sourceType/:sourceId,
        which works for every kind of source.
      operationId: CommentController_createComment
      parameters:
      - name: messageId
        required: true
        in: path
        description: Id of the message
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        description: Created comment
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateCommentDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CommentEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Comment on a message (legacy)
      tags:
      - comments
    get:
      description: Lists the comments on one message. Prefer the source-based route.
      operationId: CommentController_searchComments
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: parent
        required: false
        in: query
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      - name: messages
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: messageId
        required: true
        in: path
        description: Id of the message
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CommentEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a message's comments (legacy)
      tags:
      - comments
  "/comments/case/{caseId}":
    get:
      description: Lists the comments in a case, oldest first by default. On a case
        shared with opposing counsel, each side sees only its own side's comments.
      operationId: CommentController_searchAllCommentsByCase
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: sourceType
        required: false
        in: query
        schema:
          type: string
          enum:
          - DeviceMessage
          - DeviceConversation
          - UserEmailData
          - FinancialAccount
          - CaseFile
          - UserEmailDataThread
          - LinkedInConversation
          - LinkedInJobApplication
          - AiChatConversation
      - name: sourceId
        required: false
        in: query
        schema:
          type: string
      - name: collection
        required: false
        in: query
        schema:
          type: string
      - name: conversation
        required: false
        in: query
        schema:
          type: string
      - name: parent
        required: false
        in: query
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      - name: messages
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: startMessageIndex
        required: false
        in: query
        description: Required if endMessageIndex is provided
        schema:
          type: number
      - name: endMessageIndex
        required: false
        in: query
        description: Required if startMessageIndex is provided
        schema:
          type: number
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CommentEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a case's comments
      tags:
      - comments
  "/comments/case/{caseId}/message/{messageId}/{commentId}":
    patch:
      description: Edits the caller's own comment on a message. Prefer PATCH /comments/case/:caseId/:commentId.
      operationId: CommentController_updateComment
      parameters:
      - name: messageId
        required: true
        in: path
        description: Id of the message
        schema:
          type: string
      - name: commentId
        required: true
        in: path
        description: Id of the comment to update
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        description: Updated comment
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateCommentDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CommentEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Edit a comment (legacy)
      tags:
      - comments
    post:
      description: Replies to a comment on a message. Prefer POST /comments/case/:caseId/:commentId/reply.
      operationId: CommentController_replyToComment
      parameters:
      - name: messageId
        required: true
        in: path
        description: Id of the message
        schema:
          type: string
      - name: commentId
        required: true
        in: path
        description: Id of the comment to reply to
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        description: New reply comment
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ReplyCommentDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CommentEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Reply to a comment (legacy)
      tags:
      - comments
    delete:
      description: Deletes the caller's own comment on a message. Prefer DELETE /comments/case/:caseId/:commentId.
      operationId: CommentController_deleteComment
      parameters:
      - name: messageId
        required: true
        in: path
        description: Id of the message
        schema:
          type: string
      - name: commentId
        required: true
        in: path
        description: Id of the comment to update
        schema:
          type: string
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: false
        description: Delete Comment (and replies)
        content:
          application/json:
            schema:
              type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a comment (legacy)
      tags:
      - comments
  "/comments/case/{caseId}/source/{sourceType}/{sourceId}":
    post:
      description: Adds a comment to any commentable item in the case (a message,
        email, file and so on), identified by sourceType and sourceId.
      operationId: CommentController_createCommentBySource
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: sourceType
        required: true
        in: path
        schema:
          enum:
          - DeviceMessage
          - DeviceConversation
          - UserEmailData
          - FinancialAccount
          - CaseFile
          - UserEmailDataThread
          - LinkedInConversation
          - LinkedInJobApplication
          - AiChatConversation
          type: string
      - name: sourceId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateCommentDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CommentEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Comment on an item
      tags:
      - comments
    get:
      description: Lists the comments on one item. On a case shared with opposing
        counsel, each side sees only its own side's comments.
      operationId: CommentController_searchCommentsBySource
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: parent
        required: false
        in: query
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      - name: messages
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: sourceType
        required: true
        in: path
        schema:
          enum:
          - DeviceMessage
          - DeviceConversation
          - UserEmailData
          - FinancialAccount
          - CaseFile
          - UserEmailDataThread
          - LinkedInConversation
          - LinkedInJobApplication
          - AiChatConversation
          type: string
      - name: sourceId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CommentEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List an item's comments
      tags:
      - comments
  "/comments/case/{caseId}/{commentId}":
    patch:
      description: Edits the caller's own comment.
      operationId: CommentController_updateCommentById
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: commentId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateCommentDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CommentEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Edit a comment
      tags:
      - comments
    delete:
      description: Deletes the caller's own comment.
      operationId: CommentController_deleteCommentById
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: commentId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a comment
      tags:
      - comments
  "/comments/case/{caseId}/{commentId}/reply":
    post:
      description: Adds a reply to a comment.
      operationId: CommentController_replyToCommentById
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: commentId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ReplyCommentDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CommentEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Reply to a comment
      tags:
      - comments
  "/dashboard/{orgId}/alerts":
    get:
      description: Lists the caller's own alert notifications in the organization,
        newest first, filterable by read state, case and type, with the total and
        the read and unread counts; skip/limit or page/perPage pagination, 10 by default.
        Comment notifications also carry the comment's position for deep linking.
        No organization role is checked; only the caller's notifications are returned.
      operationId: DashboardController_searchAlerts
      parameters:
      - name: orgId
        required: true
        in: path
        description: id of the organization
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: isRead
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      - name: case
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: type
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
            enum:
            - default
            - imported-new-conversation
            - added-conversation-to-existing-case
            - replied-to-comment
            - mentioned-in-comment
            - added-to-case
            - added-to-organization
            - new-comment
            - oc-suggestion-created-or-updated
            - oc-suggestion-approved
            - oc-suggestion-rejected
            - oc-suggestion-message
            - oc-data-approved
            - oc-member-added
            - oc-member-removed
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UserNotificationsEntity"
                        noOfReadItems:
                          type: number
                          example: 12
                        noOfUnreadItems:
                          type: number
                          example: 3
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List my alerts (internal)
      tags:
      - dashboard
      x-internal: true
  "/dashboard/{orgId}/updates":
    get:
      description: Lists the caller's own update notifications in the organization,
        newest first, filterable by read state, case and type, with the total and
        the read and unread counts; skip/limit or page/perPage pagination, 10 by default.
        Comment notifications also carry the comment's position for deep linking.
        No organization role is checked; only the caller's notifications are returned.
      operationId: DashboardController_searchUpdates
      parameters:
      - name: orgId
        required: true
        in: path
        description: id of the organization
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: isRead
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      - name: case
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: type
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
            enum:
            - default
            - imported-new-conversation
            - added-conversation-to-existing-case
            - replied-to-comment
            - mentioned-in-comment
            - added-to-case
            - added-to-organization
            - new-comment
            - oc-suggestion-created-or-updated
            - oc-suggestion-approved
            - oc-suggestion-rejected
            - oc-suggestion-message
            - oc-data-approved
            - oc-member-added
            - oc-member-removed
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UserNotificationsEntity"
                        noOfReadItems:
                          type: number
                          example: 12
                        noOfUnreadItems:
                          type: number
                          example: 3
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List my updates (internal)
      tags:
      - dashboard
      x-internal: true
  "/dashboard/{orgId}/mark-as-read/{notificationId}":
    patch:
      description: Marks one of the caller's notifications in the organization as
        read. Reports success even when no notification matched.
      operationId: DashboardController_markAsRead
      parameters:
      - name: notificationId
        required: true
        in: path
        description: id of the notification
        schema:
          type: string
      - name: orgId
        required: true
        in: path
        description: id of the organization
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Mark a notification as read (internal)
      tags:
      - dashboard
      x-internal: true
  "/dashboard/{orgId}/mark-all-as-read":
    patch:
      description: Marks every notification of the caller in the organization as read.
      operationId: DashboardController_markAllAsRead
      parameters:
      - name: orgId
        required: true
        in: path
        description: id of the organization
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Mark all notifications as read (internal)
      tags:
      - dashboard
      x-internal: true
  "/webhooks/stripe/invoice-charge":
    post:
      description: Receives Stripe invoice and charge webhook events. The Stripe signature
        is verified against the raw body; an invalid signature or payload returns
        400. With webhook hardening on, each event is processed once and a retryable
        failure returns 500 so Stripe retries; otherwise a processing failure is logged
        and still answered with 200.
      operationId: StripeWebhookController_processStripeWebhookInvoiceCharges
      parameters: []
      responses:
        '200':
          description: The event was accepted (or was already processed).
          content:
            application/json:
              schema:
                type: object
                example:
                  received: true
      summary: Stripe invoice and charge events (internal)
      tags:
      - StripeWebhook
      x-internal: true
  "/webhooks/stripe/subscriptions":
    post:
      description: Receives Stripe subscription webhook events. The Stripe signature
        is verified against the raw body; an invalid signature or payload returns
        400. With webhook hardening on, each event is processed once and a retryable
        failure returns 500 so Stripe retries; otherwise a processing failure is logged
        and still answered with 200.
      operationId: StripeWebhookController_processStripeWebhookSubscriptions
      parameters: []
      responses:
        '200':
          description: The event was accepted (or was already processed).
          content:
            application/json:
              schema:
                type: object
                example:
                  received: true
      summary: Stripe subscription events (internal)
      tags:
      - StripeWebhook
      x-internal: true
  "/payments/plans":
    get:
      deprecated: true
      description: Legacy subscription model, replaced by GET /billing/plans and GET
        /billing/plan/current. Lists the legacy Stripe plans with prices and which
        one is current, plus the attached payment method, the upcoming invoice (null
        without a subscription) and `billingAlert` (null when billing is healthy,
        otherwise the past-due and blocked state). Requires an org admin, owner or
        billing role.
      operationId: PaymentsController_getAvailablePlans
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AvailablePlansListEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List legacy subscription plans (internal)
      tags:
      - payments
      x-internal: true
  "/payments/check-coupon":
    post:
      deprecated: true
      description: 'Looks up a Stripe promotion code and returns whether it can still
        be redeemed, its id and its discount as a percentage or a flat amount in cents.
        An unknown code returns `isValid: false` with the other fields null.'
      operationId: PaymentsController_checkCouponValidity
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CheckCouponValidityDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CouponCodeDetailsResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Check a coupon code (internal)
      tags:
      - payments
      x-internal: true
  "/payments/checkout":
    post:
      deprecated: true
      description: Legacy subscription model, replaced by POST /billing/plan. Creates
        a Stripe Checkout session for the given price, with an optional promotion
        code, creating the organization's Stripe customer first when needed, and returns
        its `url`. Requires an org admin, owner or billing role; returns 409 when
        the organization is billed per case.
      operationId: PaymentsController_createCheckoutSession
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateCheckoutSessionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CheckoutSessionResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start a legacy subscription checkout (internal)
      tags:
      - payments
      x-internal: true
  "/payments/upgrade/{productId}":
    post:
      deprecated: true
      description: Legacy subscription model, replaced by POST /billing/plan. Moves
        the organization's active Stripe subscription to the product in `productId`,
        invoicing the prorated difference right away, and returns the organization
        details. Requires an org admin, owner or billing role; returns 409 when the
        organization is billed per case and 400 when it has no active subscription
        or is already on that plan.
      operationId: PaymentsController_subscribe
      parameters:
      - name: productId
        required: true
        in: path
        description: Id of the selected (new) plan/product
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationDetailsEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Change the legacy subscription plan (internal)
      tags:
      - payments
      x-internal: true
  "/payments/customer-session":
    post:
      deprecated: true
      description: Legacy subscription model, replaced by POST /billing/setup-session.
        Creates a Stripe customer session for the pricing table, creating the organization's
        Stripe customer first when needed, and returns its client secret and expiry.
        Requires an org admin, owner or billing role; returns 409 when the organization
        is billed per case and 400 when it already has an active subscription.
      operationId: PaymentsController_customerSession
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/StripeCustomerSessionEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a legacy pricing-table session (internal)
      tags:
      - payments
      x-internal: true
  "/payments/billing-session":
    post:
      deprecated: true
      description: Creates a Stripe billing portal session for the organization's
        legacy subscription, to update the payment method (the default `type`) or
        update or cancel the subscription, and returns its `url`; Stripe sends the
        user back to `returnUrl`. Requires an org admin, owner or billing role; any
        failure, including having no subscription, returns 400.
      operationId: PaymentsController_billingSession
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        description: Create billing portal session
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateBillingPortalSessionDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BillingPortalResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Open the Stripe billing portal (internal)
      tags:
      - payments
      x-internal: true
  "/app-config":
    post:
      description: 'Desktop plumbing: creates or updates the app configuration for
        a desktop install (systemId) and returns it with its device settings.'
      operationId: AppConfigController_createOrUpdateAppConfig
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateUpdateAppConfigDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AppConfigEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create or update the desktop app config (internal)
      tags:
      - app-config
      x-internal: true
    get:
      description: 'Desktop plumbing: returns the caller''s app configuration for
        a desktop install (systemId).'
      operationId: AppConfigController_getAppConfig
      parameters:
      - name: systemId
        required: true
        in: query
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AppConfigEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the desktop app config (internal)
      tags:
      - app-config
      x-internal: true
  "/app-config/device-settings":
    post:
      description: 'Desktop plumbing: creates or updates the backup settings for one
        device on a desktop install and returns the full app config.'
      operationId: AppConfigController_createOrUpdateDeviceSettings
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddUpdateAppConfigDeviceSettingsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AppConfigEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Save a device's settings (internal)
      tags:
      - app-config
      x-internal: true
  "/app-config/device-settings/delete":
    post:
      description: 'Desktop plumbing: removes one device''s settings from a desktop
        install and returns the remaining app config.'
      operationId: AppConfigController_deleteDeviceSettings
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeleteAppConfigDeviceSettingsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AppConfigEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a device's settings (internal)
      tags:
      - app-config
      x-internal: true
  "/integration/goodfact/create":
    post:
      description: 'Partner route: called with the partner API key, not a user token.
        Finds the lawyer by `lawyerEmail` or, when there is no such user, creates
        the user and an organization (named from `organizationName` or the email domain)
        with a subscription. Then creates the case and attaches the partner integration
        with `goodfactEndpoint` and `caseAPIKey`, and returns that integration (type,
        attach date, remote case id and name).'
      operationId: IntegrationController_addNewGoodFactCase
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddGoodFactCaseDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseIntegrationEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      summary: Create a case for a partner
      tags:
      - integrations
  "/legal/clio/matters":
    get:
      description: Lists a page of matters from the connected Clio account, through
        the caller's own Clio connection when they have one and the firm's primary
        connection otherwise, filtered by `status` and `query` (25 per page by default,
        at most 200), each marked with whether it is already linked to a case. Requires
        a member role or above in the organization.
      operationId: LegalController_getClioMatters
      parameters:
      - name: status
        required: false
        in: query
        schema:
          enum:
          - open
          - pending
          - closed
          type: string
      - name: query
        required: false
        in: query
        description: Search term to filter matters by name/description
        schema:
          type: string
      - name: page
        required: false
        in: query
        description: 1-based page number (default 1)
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: Items per page (default 25, max 200)
        schema:
          type: number
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LegalMattersResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Clio matters
      tags:
      - legal
  "/legal/clio/matters/{matterId}/link":
    post:
      description: 'Links the case in `caseId` to a Clio matter: grants the matter''s
        attorneys access to the case, imports them as participants and starts the
        first document sync. Returns the matter id, its reference number (or name)
        and the attorneys. Only an org admin or owner or the case creator may link
        it, and not an opposing counsel organization (403 otherwise). Returns 404
        for an unknown case or matter and 409 when the case or the matter is already
        linked. Requires a member role or above in the organization.'
      operationId: LegalController_linkCaseToClioMatter
      parameters:
      - name: matterId
        required: true
        in: path
        description: matterId for the existing matter in CLIO
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/LinkMatterDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      integrationType:
                        type: string
                        enum:
                        - clio
                        - smokeball
                        example: clio
                      matterId:
                        type: string
                        example: '12345'
                      matterName:
                        type: string
                        description: The matter reference number, or its name when
                          it has none.
                        example: 00123-Smith
                      attorneys:
                        type: array
                        items:
                          type: object
                          properties:
                            contact:
                              type: object
                              properties:
                                id:
                                  type: string
                                  example: '987654'
                                name:
                                  type: string
                                  example: Jane Doe
                                firstName:
                                  type: string
                                  example: Jane
                                lastName:
                                  type: string
                                  example: Doe
                                email:
                                  type: string
                                  example: jane.doe@example.com
                                phone:
                                  type: string
                                  example: "+15555550123"
                                type:
                                  type: string
                                  example: Person
                            hearsayUserId:
                              type: string
                              description: Present when the attorney has a user account.
                              example: 65a1b2c3d4e5f6a7b8c9d0e4
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Link a case to a Clio matter
      tags:
      - legal
    delete:
      description: Removes the link between the case in `caseId` and the Clio matter,
        with its sync bookkeeping and imported participants; documents already synced
        and case access granted to attorneys stay. Only an org admin or owner or the
        case creator may unlink it (403 otherwise). Returns 400 without `caseId`,
        404 when the case is not linked to Clio and 409 when it is linked to a different
        matter. Requires a member role or above in the organization.
      operationId: LegalController_unlinkCaseFromClioMatter
      parameters:
      - name: matterId
        required: true
        in: path
        description: matterId of the currently linked Clio matter
        schema:
          type: string
      - name: caseId
        required: true
        in: query
        description: Hearsay case to unlink from the matter
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      integrationType:
                        type: string
                        enum:
                        - clio
                        - smokeball
                        example: clio
                      caseId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      matterId:
                        type: string
                        example: '12345'
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Unlink a case from a Clio matter
      tags:
      - legal
  "/legal/smokeball/matters":
    get:
      description: Lists a page of matters from the connected Smokeball account, through
        the caller's own Smokeball connection when they have one and the firm's primary
        connection otherwise, filtered by `status` and `query` (25 per page by default,
        at most 200), each marked with whether it is already linked to a case. Requires
        a member role or above in the organization; returns 403 while the Smokeball
        integration is turned off.
      operationId: LegalController_getSmokeballMatters
      parameters:
      - name: status
        required: false
        in: query
        schema:
          enum:
          - open
          - pending
          - closed
          type: string
      - name: query
        required: false
        in: query
        description: Search term to filter matters by name/description
        schema:
          type: string
      - name: page
        required: false
        in: query
        description: 1-based page number (default 1)
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: Items per page (default 25, max 200)
        schema:
          type: number
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LegalMattersResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Smokeball matters
      tags:
      - legal
  "/legal/smokeball/matters/{matterId}/link":
    post:
      description: 'Links the case in `caseId` to a Smokeball matter: grants the matter''s
        attorneys access to the case, imports them as participants and starts the
        first document sync. Returns the matter id, its reference number (or name)
        and the attorneys. Only an org admin or owner or the case creator may link
        it, and not an opposing counsel organization (403 otherwise). Returns 404
        for an unknown case or matter and 409 when the case or the matter is already
        linked. Requires a member role or above in the organization; returns 403 while
        the Smokeball integration is turned off.'
      operationId: LegalController_linkCaseToSmokeballMatter
      parameters:
      - name: matterId
        required: true
        in: path
        description: matterId for the existing matter in Smokeball
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/LinkMatterDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      integrationType:
                        type: string
                        enum:
                        - clio
                        - smokeball
                        example: clio
                      matterId:
                        type: string
                        example: '12345'
                      matterName:
                        type: string
                        description: The matter reference number, or its name when
                          it has none.
                        example: 00123-Smith
                      attorneys:
                        type: array
                        items:
                          type: object
                          properties:
                            contact:
                              type: object
                              properties:
                                id:
                                  type: string
                                  example: '987654'
                                name:
                                  type: string
                                  example: Jane Doe
                                firstName:
                                  type: string
                                  example: Jane
                                lastName:
                                  type: string
                                  example: Doe
                                email:
                                  type: string
                                  example: jane.doe@example.com
                                phone:
                                  type: string
                                  example: "+15555550123"
                                type:
                                  type: string
                                  example: Person
                            hearsayUserId:
                              type: string
                              description: Present when the attorney has a user account.
                              example: 65a1b2c3d4e5f6a7b8c9d0e4
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Link a case to a Smokeball matter
      tags:
      - legal
    delete:
      description: Removes the link between the case in `caseId` and the Smokeball
        matter, with its sync bookkeeping and imported participants; documents already
        synced and case access granted to attorneys stay. Only an org admin or owner
        or the case creator may unlink it (403 otherwise). Returns 400 without `caseId`,
        404 when the case is not linked to Smokeball and 409 when it is linked to
        a different matter. Requires a member role or above in the organization.
      operationId: LegalController_unlinkCaseFromSmokeballMatter
      parameters:
      - name: matterId
        required: true
        in: path
        description: matterId of the currently linked Smokeball matter
        schema:
          type: string
      - name: caseId
        required: true
        in: query
        description: Hearsay case to unlink from the matter
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      integrationType:
                        type: string
                        enum:
                        - clio
                        - smokeball
                        example: clio
                      caseId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      matterId:
                        type: string
                        example: '12345'
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Unlink a case from a Smokeball matter
      tags:
      - legal
  "/legal/cases/{caseId}/participants":
    get:
      description: Lists the people imported from the Clio or Smokeball matters linked
        to the case (attorneys and contacts) with name, email, phone, role, whether
        they are a custodian and their provider id; the optional `provider` query
        narrows to one provider. Empty when the case is not linked. Requires a member
        role or above in the organization.
      operationId: LegalController_getExternalParticipantsByCaseId
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: provider
        required: true
        in: query
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      participants:
                        type: array
                        items:
                          type: object
                          properties:
                            _id:
                              type: string
                              example: 65a1b2c3d4e5f6a7b8c9d0e7
                            organizationId:
                              type: string
                              example: 65a1b2c3d4e5f6a7b8c9d0e3
                            caseId:
                              type: string
                              example: 65a1b2c3d4e5f6a7b8c9d0e2
                            caseIntegrationId:
                              type: string
                              example: 65a1b2c3d4e5f6a7b8c9d0e8
                            provider:
                              type: string
                              example: clio
                            firstName:
                              type: string
                              example: Jane
                            lastName:
                              type: string
                              example: Doe
                            email:
                              type: string
                              example: jane.doe@example.com
                            phone:
                              type: string
                              example: "+15555550123"
                            role:
                              type: string
                              enum:
                              - client
                              - witness
                              - opposing_party
                              - other
                              - attorney
                              example: attorney
                            isCustodian:
                              type: boolean
                              example: false
                            externalId:
                              type: string
                              example: '987654'
                            hearsayUserId:
                              type: string
                              example: 65a1b2c3d4e5f6a7b8c9d0e4
                            extractionCodeId:
                              type: string
                            createdAt:
                              type: string
                              format: date-time
                              example: '2026-01-15T10:15:00.000Z'
                            updatedAt:
                              type: string
                              format: date-time
                              example: '2026-01-15T10:15:00.000Z'
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List the matter participants of a case
      tags:
      - legal
  "/legal/{provider}/settings":
    patch:
      description: Updates the organization's sync settings for one destination; only
        the fields sent change. For Clio and Smokeball the webhook registration follows
        the change, and a webhook failure is logged without failing the save. Dropbox
        has outbound settings only, so sending `cases` or `documents` for it returns
        400, as does an unknown provider. Requires an org admin or owner.
      operationId: LegalController_updateLegalSettings
      parameters:
      - name: provider
        required: true
        in: path
        description: 'Export destination: a CMP (clio, smokeball) or a storage backend
          (dropbox). Storage destinations are outbound-only — sending ''cases'' or
          ''documents'' for one is a 400.'
        schema:
          enum:
          - clio
          - smokeball
          - dropbox
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateLegalSettingsDto"
      responses:
        '204':
          description: Settings saved. No body.
      security:
      - access-token: []
      summary: Update the sync settings of a destination
      tags:
      - legal
    get:
      description: 'Returns the organization''s sync settings for one destination:
        inbound case creation and document sync (Clio and Smokeball only) and outbound
        export per data source, or the defaults when it was never configured. An unknown
        provider returns 400. Requires a member role or above in the organization.'
      operationId: LegalController_getLegalSettings
      parameters:
      - name: provider
        required: true
        in: path
        description: 'Export destination: a CMP (clio, smokeball) or a storage backend
          (dropbox).'
        schema:
          enum:
          - clio
          - smokeball
          - dropbox
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: Sync settings for the destination.
                    properties:
                      inbound:
                        type: object
                        description: Clio and Smokeball only.
                        properties:
                          cases:
                            type: object
                            properties:
                              autoCreateFromWebhook:
                                type: boolean
                                example: false
                              autoCollection:
                                type: object
                          documents:
                            type: object
                            properties:
                              autoSync:
                                type: boolean
                                example: false
                      outbound:
                        type: object
                        properties:
                          dataSources:
                            type: object
                            description: 'Per data source: enabled, mode (auto or
                              manual) and source options.'
                            example:
                              file:
                                enabled: false
                                mode: auto
                              email:
                                enabled: false
                                mode: manual
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the sync settings of a destination
      tags:
      - legal
  "/legal/cases/{caseId}/legal-sync/export-to-provider":
    post:
      description: 'Queues an export of the case data not yet sent to the destination
        in `provider` (Clio by default, Smokeball or Dropbox), narrowed by source
        types, extraction codes and formats, and returns what the request matched:
        `queued`, the job id and the record counts per source. A request while an
        export of the same case and destination is running joins it. `queued: false`
        means nothing matched and no job started; delivery status per record is on
        the legal sync status route. Returns 409 when a requested source is turned
        off for export or matches nothing and 404 for an unknown case. Requires a
        member role or above in the organization and access to the case; not available
        to opposing counsel.'
      operationId: LegalSyncController_uploadPendingSourceDataToProvider
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportToProviderDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExportToProviderResultEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
        '409':
          description: An explicitly requested source is disabled for outbound export,
            or matched no exportable records.
      security:
      - access-token: []
      summary: Export case data to a provider
      tags:
      - legal
  "/legal/cases/{caseId}/legal-sync/status":
    get:
      description: Lists what has been or is being sent from the case to Clio, Smokeball
        or Dropbox, one row per record and destination, most recently updated first,
        with status, file name, target matter or folder, error and timings; filter
        by `provider`, `status` and `sourceType`. Destinations whose integration is
        turned off are left out. Requires a member role or above in the organization.
      operationId: LegalSyncController_getCaseSyncStatus
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: provider
        required: false
        in: query
        schema:
          type: string
          enum:
          - clio
          - smokeball
          - dropbox
      - name: status
        required: false
        in: query
        schema:
          type: string
          enum:
          - pending
          - processing
          - completed
          - failed
          - disabled
          - skipped
      - name: sourceType
        required: false
        in: query
        schema:
          type: string
          enum:
          - Device
          - UserEmailDataProvider
          - CaseFileProvider
          - FinancialAccountProvider
          - LegalDocumentProvider
          - DiscordUserProvider
          - RedditUserProvider
          - LinkedInUserProvider
          - AiChatProvider
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      records:
                        type: array
                        items:
                          "$ref": "#/components/schemas/LegalSyncRecordEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List the legal sync records of a case
      tags:
      - legal
  "/legal/clio/matters/{matterId}/billing/expense":
    post:
      description: 'Test route: creates a billable expense (`amount`, `description`,
        an optional `date` that defaults to today and an optional expense category)
        directly on a Clio matter by its Clio id, through the firm''s primary connection,
        and returns the created entry in `activity`. Requires an org admin or owner;
        returns 403 while the Clio integration is turned off.'
      operationId: LegalBillingController_createClioExpenseEntry
      parameters:
      - name: matterId
        required: true
        in: path
        description: Clio matter ID to attach the expense to
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateLegalExpenseDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      activity:
                        type: object
                        properties:
                          id:
                            type: string
                            example: '445566'
                          matterId:
                            type: string
                            example: '12345'
                          date:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                          description:
                            type: string
                            example: 'Hearsay: phone extraction upload'
                          type:
                            type: string
                            enum:
                            - time
                            - flat_fee
                            - expense
                            example: expense
                          rate:
                            type: number
                            example: 49
                          total:
                            type: number
                            example: 49
                          staffId:
                            type: string
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a Clio expense entry (internal)
      tags:
      - legal
      x-internal: true
  "/legal/smokeball/matters/{matterId}/billing/expense":
    post:
      description: 'Test route: creates a billable expense (`amount`, `description`,
        an optional `date` that defaults to today and an optional expense category)
        directly on a Smokeball matter by its Smokeball id, through the firm''s primary
        connection, and returns the created entry in `activity`. Requires an org admin
        or owner; returns 403 while the Smokeball integration is turned off.'
      operationId: LegalBillingController_createSmokeballExpenseEntry
      parameters:
      - name: matterId
        required: true
        in: path
        description: Smokeball matter ID to attach the expense to
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateLegalExpenseDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      activity:
                        type: object
                        properties:
                          id:
                            type: string
                            example: '445566'
                          matterId:
                            type: string
                            example: '12345'
                          date:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                          description:
                            type: string
                            example: 'Hearsay: phone extraction upload'
                          type:
                            type: string
                            enum:
                            - time
                            - flat_fee
                            - expense
                            example: expense
                          rate:
                            type: number
                            example: 49
                          total:
                            type: number
                            example: 49
                          staffId:
                            type: string
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a Smokeball expense entry (internal)
      tags:
      - legal
      x-internal: true
  "/legal/clio/connect":
    get:
      description: Returns the Clio authorization URL to send the user to, for the
        firm's Clio `region` (us, ca, eu or au; us by default). The first user to
        connect becomes the firm's primary connection. Returns 409 when the caller
        already has an active Clio connection in this organization. Requires a member
        role or above in the organization.
      operationId: LegalConnectionController_connectClio
      parameters:
      - name: region
        required: false
        in: query
        description: The firm's Clio region (defaults to 'us')
        schema:
          enum:
          - us
          - ca
          - eu
          - au
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      authorizationUrl:
                        type: string
                        example: https://app.clio.com/oauth/authorize?response_type=code&client_id=abc123&state=9f2c1e
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start a Clio connection
      tags:
      - legal
  "/legal/clio/callback":
    get:
      description: Where Clio sends the browser back after the user authorizes; it
        needs no sign-in, the `state` value ties it to the connect request. Saves
        the connection and redirects (302) to the portal with `clio=connected`. Returns
        401 when `code` or `state` is missing.
      operationId: LegalConnectionController_clioCallback
      parameters:
      - name: code
        required: true
        in: query
        schema:
          type: string
      - name: state
        required: true
        in: query
        schema:
          type: string
      responses:
        '302':
          description: Redirects (Location header) to the portal with `clio=connected`.
      security:
      - access-token: []
      summary: Finish a Clio connection (internal)
      tags:
      - legal
      x-internal: true
  "/legal/clio/firm":
    delete:
      description: 'Disconnects Clio for the whole organization: turns off every Clio
        sync setting, removes the Clio webhooks, deactivates the firm and every user
        connection, and unlinks every case from its Clio matter (synced documents
        and case access stay). Requires an org admin or owner.'
      operationId: LegalConnectionController_disconnectClio
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Disconnected. No body.
      security:
      - access-token: []
      summary: Disconnect Clio for the organization
      tags:
      - legal
  "/legal/clio/connections/me":
    delete:
      description: Disconnects the caller's own Clio connection. When it was the firm's
        primary connection, another connected user becomes primary, or the firm is
        disconnected when there is none. Returns 404 when the caller has no active
        Clio connection. Requires a member role or above in the organization.
      operationId: LegalConnectionController_disconnectMyClio
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Disconnected. No body.
      security:
      - access-token: []
      summary: Disconnect my Clio connection
      tags:
      - legal
  "/legal/smokeball/connect":
    get:
      description: Returns the Smokeball authorization URL to send the user to, for
        the firm's Smokeball `region` (us, au or uk; us by default). The first user
        to connect becomes the firm's primary connection. Returns 409 when the caller
        already has an active Smokeball connection in this organization and 403 while
        the Smokeball integration is turned off. Requires a member role or above in
        the organization.
      operationId: LegalConnectionController_connectSmokeball
      parameters:
      - name: region
        required: false
        in: query
        description: The firm's Smokeball region (defaults to 'us')
        schema:
          enum:
          - us
          - au
          - uk
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      authorizationUrl:
                        type: string
                        example: https://auth.smokeball.com/oauth2/authorize?response_type=code&client_id=abc123&state=9f2c1e
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start a Smokeball connection
      tags:
      - legal
  "/legal/smokeball/callback":
    get:
      description: Where Smokeball sends the browser back after the user authorizes;
        it needs no sign-in, the `state` value ties it to the connect request. Saves
        the connection and redirects (302) to the portal with `smokeball=connected`.
        Returns 401 when `code` or `state` is missing and 403 while the Smokeball
        integration is turned off.
      operationId: LegalConnectionController_smokeballCallback
      parameters:
      - name: code
        required: true
        in: query
        schema:
          type: string
      - name: state
        required: true
        in: query
        schema:
          type: string
      responses:
        '302':
          description: Redirects (Location header) to the portal with `smokeball=connected`.
      security:
      - access-token: []
      summary: Finish a Smokeball connection (internal)
      tags:
      - legal
      x-internal: true
  "/legal/smokeball/firm":
    delete:
      description: 'Disconnects Smokeball for the whole organization: turns off every
        Smokeball sync setting, removes the Smokeball webhooks, deactivates the firm
        and every user connection, and unlinks every case from its Smokeball matter
        (synced documents and case access stay). Requires an org admin or owner.'
      operationId: LegalConnectionController_disconnectSmokeball
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Disconnected. No body.
      security:
      - access-token: []
      summary: Disconnect Smokeball for the organization
      tags:
      - legal
  "/legal/smokeball/connections/me":
    delete:
      description: Disconnects the caller's own Smokeball connection. When it was
        the firm's primary connection, another connected user becomes primary, or
        the firm is disconnected when there is none. Returns 404 when the caller has
        no active Smokeball connection. Requires a member role or above in the organization.
      operationId: LegalConnectionController_disconnectMySmokeball
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Disconnected. No body.
      security:
      - access-token: []
      summary: Disconnect my Smokeball connection
      tags:
      - legal
  "/legal/dropbox/connect":
    get:
      description: Returns the Dropbox authorization URL to send the user to; the
        connection covers the whole organization. Requires an org admin or owner;
        returns 403 while the Dropbox integration is turned off.
      operationId: DropboxConnectionController_connectDropbox
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      authorizationUrl:
                        type: string
                        example: https://www.dropbox.com/oauth2/authorize?response_type=code&client_id=abc123&state=9f2c1e
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start a Dropbox connection
      tags:
      - legal
  "/legal/dropbox/callback":
    get:
      description: Where Dropbox sends the browser back after the user authorizes;
        it needs no sign-in, the `state` value ties it to the connect request. Saves
        the connection and redirects (302) to the portal with `dropbox=connected`.
        Returns 401 when `code` or `state` is missing and 403 while the Dropbox integration
        is turned off.
      operationId: DropboxConnectionController_dropboxCallback
      parameters:
      - name: code
        required: true
        in: query
        schema:
          type: string
      - name: state
        required: true
        in: query
        schema:
          type: string
      responses:
        '302':
          description: Redirects (Location header) to the portal with `dropbox=connected`.
      security:
      - access-token: []
      summary: Finish a Dropbox connection (internal)
      tags:
      - legal
      x-internal: true
  "/legal/dropbox/status":
    get:
      description: Returns whether the Dropbox integration is turned on (`enabled`)
        and whether the organization has an active Dropbox connection, with the Dropbox
        account id and when it was connected. `enabled` can be false for an organization
        that is still connected. Requires a member role or above in the organization.
      operationId: DropboxConnectionController_getDropboxStatus
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      enabled:
                        type: boolean
                        example: true
                      connected:
                        type: boolean
                        example: true
                      remoteAccountId:
                        type: string
                        nullable: true
                        description: Present when connected.
                        example: dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc
                      connectedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        description: Present when connected.
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the Dropbox connection status
      tags:
      - legal
  "/legal/dropbox/connection":
    delete:
      description: Deactivates the organization's Dropbox connection. The folders
        chosen for cases are kept, so reconnecting the same account needs no new choices.
        Works while the integration is turned off. Returns 404 when there is no active
        connection. Requires an org admin or owner.
      operationId: DropboxConnectionController_disconnectDropbox
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Disconnected. No body.
      security:
      - access-token: []
      summary: Disconnect Dropbox
      tags:
      - legal
  "/legal/dropbox/folders":
    get:
      description: Lists the folders one level below `path` (the account root when
        omitted) in the connected Dropbox, each with the `path` to pass back to go
        one level deeper. Requires a member role or above in the organization; returns
        403 while the Dropbox integration is turned off.
      operationId: DropboxConnectionController_listDropboxFolders
      parameters:
      - name: path
        required: false
        in: query
        description: Folder to list. Omit for the account root. Use a `path` returned
          by a previous call.
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      folders:
                        type: array
                        items:
                          type: object
                          properties:
                            path:
                              type: string
                              example: "/Clients/Smith v. Acme"
                            name:
                              type: string
                              example: Smith v. Acme
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Dropbox folders
      tags:
      - legal
  "/legal/cases/{caseId}/dropbox/folder":
    get:
      description: 'Returns the Dropbox folder the case''s exports go to. `path: null`
        means no folder has been chosen, and an export to Dropbox is refused until
        one is. Requires a member role or above in the organization and access to
        the case.'
      operationId: DropboxConnectionController_getCaseDropboxFolder
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      path:
                        type: string
                        nullable: true
                        example: "/Clients/Smith v. Acme"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the Dropbox folder of a case
      tags:
      - legal
    put:
      description: Sets the Dropbox folder the case's later exports go to; documents
        already sent stay where they are. Returns 404 when `path` is not an existing
        folder in the connected account. Requires a member role or above in the organization
        and access to the case; not available to opposing counsel; returns 403 while
        the Dropbox integration is turned off.
      operationId: DropboxConnectionController_setCaseDropboxFolder
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SetCaseDestinationFolderDto"
      responses:
        '204':
          description: Folder saved. No body.
      security:
      - access-token: []
      summary: Set the Dropbox folder of a case
      tags:
      - legal
    delete:
      description: Removes the case's Dropbox folder, so exports to Dropbox are refused
        until a new one is chosen. Works while the integration is turned off. Requires
        a member role or above in the organization and access to the case; not available
        to opposing counsel.
      operationId: DropboxConnectionController_clearCaseDropboxFolder
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Folder cleared. No body.
      security:
      - access-token: []
      summary: Clear the Dropbox folder of a case
      tags:
      - legal
  "/legal/clio/webhook/matters":
    post:
      description: Called by Clio when a matter is created, updated or deleted. Deliveries
        are signed with the subscription secret and rejected with 401 when the signature
        is missing or does not match. The registration handshake, which carries an
        `X-Hook-Secret` header, is answered by echoing that header. Processing errors
        are logged and still answered 200, so Clio keeps the subscription.
      operationId: LegalClioWebhookController_clioMatterWebhook
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ClioMatterWebHookDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - statusCode
                - timestamp
                properties:
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      summary: Receive Clio matter events (internal)
      tags:
      - legal
      x-internal: true
  "/legal/clio/webhook/documents":
    post:
      description: Called by Clio when a document is created, updated or deleted.
        Deliveries are signed with the subscription secret and rejected with 401 when
        the signature is missing or does not match. The registration handshake, which
        carries an `X-Hook-Secret` header, is answered by echoing that header. Processing
        errors are logged and still answered 200, so Clio keeps the subscription.
      operationId: LegalClioWebhookController_clioDocumentWebhook
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ClioDocumentWebhookDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - statusCode
                - timestamp
                properties:
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      summary: Receive Clio document events (internal)
      tags:
      - legal
      x-internal: true
  "/legal/clio/deauthorize":
    post:
      description: Called by Clio when a user removes access to the app from Clio.
        Deactivates that user's Clio connection (`user_id`); when it was the firm's
        primary connection, another connected user becomes primary, or the firm is
        disconnected when there is none. Answers 200 with an empty body, also when
        `user_id` is missing or unknown.
      operationId: LegalClioWebhookController_clioDeauthorize
      parameters: []
      responses:
        '200':
          description: Empty body (not wrapped).
      summary: Receive a Clio deauthorization (internal)
      tags:
      - legal
      x-internal: true
  "/legal/smokeball/webhook":
    post:
      description: Called by Smokeball for matter and file events on the firm's single
        subscription. Requests must carry a valid signature, timestamp and request
        id (401 otherwise). Answers 200 without processing while the Smokeball integration
        is turned off, for changes Hearsay itself made and for duplicate events.
      operationId: LegalSmokeballWebhookController_smokeballWebhook
      parameters:
      - name: requestid
        required: true
        in: header
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SmokeballWebhookDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - statusCode
                - timestamp
                properties:
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      summary: Receive Smokeball events (internal)
      tags:
      - legal
      x-internal: true
  "/legal/status":
    get:
      description: 'Lists one entry per legal provider (Clio, Smokeball) the organization
        has connected: its status (`degraded` when no user connection is primary),
        the provider account id, when it was connected, how many users are connected,
        the primary user connection and whether the matter and document webhooks are
        registered. An empty list means none is connected. Requires a member role
        or above in the organization.'
      operationId: LegalConnectionStatusController_getConnectionStatuses
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      connections:
                        type: array
                        items:
                          type: object
                          properties:
                            provider:
                              type: string
                              enum:
                              - clio
                              - smokeball
                              example: clio
                            status:
                              type: string
                              enum:
                              - connected
                              - not_connected
                              - degraded
                              example: connected
                            remoteAccountId:
                              type: string
                              example: '3456789'
                            connectedAt:
                              type: string
                              format: date-time
                              example: '2026-01-15T10:15:00.000Z'
                            activeUserCount:
                              type: number
                              example: 2
                            primaryUser:
                              type: object
                              description: Absent when status is degraded.
                              properties:
                                hearsayUserId:
                                  type: string
                                  example: 65a1b2c3d4e5f6a7b8c9d0e4
                                remoteUserId:
                                  type: string
                                  example: '345678901'
                                tokenExpiresAt:
                                  type: string
                                  format: date-time
                                  example: '2026-01-15T10:15:00.000Z'
                                lastUsedAt:
                                  type: string
                                  format: date-time
                                  example: '2026-01-15T10:15:00.000Z'
                            webhooks:
                              type: object
                              properties:
                                matters:
                                  type: boolean
                                  example: true
                                documents:
                                  type: boolean
                                  example: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List legal provider connections
      tags:
      - legal
  "/admin/case":
    get:
      description: Lists cases across all organizations, most recently updated first,
        leaving out deleted cases, narrowed by `orgIds` and by `searchText` (matched
        against name, description and slug). Each row carries the full case details.
        Paged with `page` and `perPage` (default 10). Super admin only.
      operationId: CaseAdminController_searchCases
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: orgIds
        required: false
        in: query
        description: Comma-separated organization ids to filter cases by
        schema:
          type: array
          items:
            type: string
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 1
          maxLength: 100
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseDetailsEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search all cases (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/{caseId}/events":
    get:
      description: Lists every event recorded on the case, newest first, in every
        category, with actors' names and emails shown in full, filtered by `type`
        and `category` and paged with `page` and `perPage`. Super admin only.
      operationId: CaseAdminController_listCaseEvents
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          maximum: 100
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: type
        required: false
        in: query
        schema:
          type: string
          enum:
          - financial_connection_removed
          - oc_invited
          - oc_accepted
          - oc_member_added
          - oc_member_removed
          - oc_ec_created
          - oc_suggestion_created
          - oc_suggestion_updated
          - oc_suggestion_message
          - oc_suggestion_rejected
          - oc_ec_approved
          - oc_data_approved
          - oc_data_revoked
          - oc_code_released
          - oc_code_unreleased
          - member_invited
          - member_accepted
          - case_created
          - case_status_changed
          - extraction_code_created
          - extraction_code_updated
          - extraction_code_locked
          - custodian_updated
          - device_attached
          - data_uploaded
          - export_started
          - export_finished
          - export_errored
          - export_cancelled
      - name: category
        required: false
        in: query
        schema:
          type: string
          enum:
          - general
          - case_data
          - oc
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseEventEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a case's event log (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/create-case":
    post:
      description: Creates a case in `organization` on behalf of the user given by
        `userId` or `userEmail`, who becomes its owner, with the same options as the
        member route that creates a case, and returns the case. Returns 404 for an
        unknown user and 400 when the organization already has a case with that name.
        Super admin only.
      operationId: CaseAdminController_createCase
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateCaseForUserDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseViewEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a case for a user (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/delete-case-conversations":
    delete:
      description: 'Removes every conversation from the case found by `caseId` or
        `caseSlug` in the organization given by `orgId` or `orgSlug`: conversations
        linked only to this case are deleted and the rest are unlinked. With `deleteCaseIfNoConversationsLeft`,
        a case left with no conversations is soft-deleted along with its access, extraction
        codes, comments, data sources and files. Returns a message with the counts,
        or 404 for an unknown organization or case. Super admin only.'
      operationId: CaseAdminController_deleteCaseConversations
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeleteCaseConversationsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a case's conversations (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/create-extraction-codes":
    post:
      description: Creates one extraction code for each entry in `ecCodeData`, on
        its `caseId` with the given custodian email, configuration and requested sources,
        marks those sources pending on the case and returns each case id with its
        new code. While the Plaid integration is on, a financial source needs the
        case's organization to have accepted the Plaid terms and have an active Plaid
        customer (400 or 404 otherwise); this check runs after the codes are saved.
        Super admin only.
      operationId: CaseAdminController_createExtractionCodes
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateMultipleExtractionCodeDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CreateExtractionCodeResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create extraction codes for cases (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/update-extraction-codes":
    post:
      description: Updates the extraction code on `caseId` found by `extractionCodeId`
        or `code`, with the same fields as the member route (label, due date, configuration
        or custodian), and returns the code. Configuration cannot change once the
        code is locked. Returns 404 when the case has no such code. Super admin only.
      operationId: CaseAdminController_updateExtractionCodes
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateExtractionCodeAdminDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update an extraction code (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/fetch-extraction-codes":
    get:
      description: Lists extraction codes newest first, narrowed by `caseId` or `orgId`,
        each with its case, paged with `page` and `perPage`. Super admin only.
      operationId: CaseAdminController_fetchExtractionCodes
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: caseId
        required: false
        in: query
        schema:
          type: string
      - name: orgId
        required: false
        in: query
        schema:
          type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/ExtractionCodeEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List extraction codes (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/create-bulk-extraction-codes":
    post:
      description: Creates an extraction code for every case that has none, or for
        every case when `createIfEvenExists` is true, in batches of 1000, with the
        case creator as custodian and default settings. Returns a message with the
        number created. Super admin only.
      operationId: CaseAdminController_createBulkExtractionCode
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateBulkExtractionCodeDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create extraction codes for all cases (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/rerun-export":
    post:
      description: Clears the export's completion and progress and queues it to run
        again with the same settings, even when it already completed. Returns 404
        for an unknown export and 400 for a collection export, which is no longer
        supported. Super admin only.
      operationId: CaseAdminController_rerunExport
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RerunExportDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Re-run an export (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/rerun-exports":
    post:
      description: Creates a batch from `exportIds` and queues it; the batch re-runs
        the exports one at a time, waiting for each to finish. Ids that match no export
        are counted as invalid. Returns the batch id, the number of ids and the number
        invalid; follow progress with the batch route. Super admin only.
      operationId: CaseAdminController_rerunExports
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RerunExportsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RerunExportsResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Re-run several exports as a batch (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/rerun-exports/{batchId}":
    get:
      description: Returns the batch's status, its counts of exports that succeeded
        and failed, its timings and each export's status and error. Returns 404 for
        an unknown batch. Super admin only.
      operationId: CaseAdminController_getRerunBatch
      parameters:
      - name: batchId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseExportRerunBatchEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
        '404':
          description: Rerun batch not found
      security:
      - access-token: []
      summary: Get a re-run batch (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/clear-export-locks":
    post:
      description: Deletes every export lock, including ones left by workers that
        crashed or restarted, so blocked exports can start again, and returns how
        many were cleared. Super admin only.
      operationId: CaseAdminController_clearExportLocks
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Clear all export locks (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/fetch-messages-count":
    get:
      description: Counts the conversations linked to the case, their messages and
        their export copies of messages. Returns 404 for an unknown case. Super admin
        only.
      operationId: CaseAdminController_fetchMessagesCount
      parameters:
      - name: caseId
        required: true
        in: query
        description: The case ID
        schema:
          example: 683cce0e314514073ab59156
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/FetchMessagesCountResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Count a case's messages (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/move-case-to-org":
    post:
      description: 'Moves the case to `targetOrgId`: its devices are added to that
        organization, all its case access is replaced with admin access for the users
        in `emails` (each must already belong to the target organization) and its
        extraction codes are reassigned to the first of them. Returns 404 for an unknown
        case, organization or users, and 400 when the case is already there or some
        emails have no user or no membership there. Super admin only.'
      operationId: CaseAdminController_moveCaseToOrg
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/MoveCaseToOrgDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Move a case to another organization (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/move-device-to-case":
    post:
      description: Moves a device and what was collected from it (extraction codes,
        backups, conversations, contacts, media, browser history, notes, files, comments
        and data source statuses) from `sourceCaseId` to `targetCaseId` in the same
        organization; with `dryRun` it only reports what would move. The message lists
        the counts and any step that failed. Returns 404 for an unknown case or device
        and 400 when the cases are the same, in different organizations, or the device
        is not on the source case. Super admin only.
      operationId: CaseAdminController_moveDeviceToCase
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/MoveDeviceToCaseDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Move a device to another case (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/delete-discord-profile":
    delete:
      description: Archives the Discord profile in `discordProviderId`, or with `forceDelete`
        deletes it with all its conversations, messages, activity, support tickets
        and import jobs; either way the data source status of its linked cases is
        reset. Returns 404 for an unknown profile. Super admin only.
      operationId: CaseAdminController_deleteDiscordProfile
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AdminDeleteDiscordProfileDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a Discord profile (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case/delete-reddit-profile":
    delete:
      description: Archives the Reddit profile in `redditProviderId`, or with `forceDelete`
        deletes it with all its posts, comments, messages, chat messages, votes and
        import jobs; either way the data source status of its linked cases is reset.
        Returns 404 for an unknown profile. Super admin only.
      operationId: CaseAdminController_deleteRedditProfile
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AdminDeleteRedditProfileDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a Reddit profile (internal)
      tags:
      - admin
      x-internal: true
  "/admin/organization":
    get:
      description: Lists organizations whose name, slug or email contains `searchText`
        (case-insensitive), one page at a time, sorted by `sortBy` (newest first by
        default), each with its active plan. Super admin only.
      operationId: OrganizationAdminController_searchOrganizations
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        schema:
          minLength: 1
          maxLength: 100
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: createdAt
          type: string
          enum:
          - createdAt
          - updatedAt
          - name
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/OrganizationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search organizations (internal)
      tags:
      - admin
      x-internal: true
  "/admin/organization/directory":
    get:
      description: Lists organizations one page at a time (newest first by default),
        each with a capped list of its members (id, name, email, role in that organization,
        active state) that leaves out claimable placeholder accounts and deleted users;
        `membershipCount` counts every membership and `hasMoreUsers` means the list
        was cut, so page the rest with `GET /admin/user?organizationId=<id>` using
        `skip`/`limit`. `searchText` matches the organization name, slug or email,
        not users, and `total` counts organizations. Super admin only.
      operationId: OrganizationAdminController_getOrganizationDirectory
      parameters:
      - name: page
        required: false
        in: query
        schema:
          maximum: 1000
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          maximum: 100
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: 'Takes precedence over page/perPage when provided. Mutually exclusive
          with page in practice: nextPage/previousPage track `page`, so they are meaningless
          when paging by skip.'
        schema:
          maximum: 100000
          type: number
      - name: limit
        required: false
        in: query
        description: Takes precedence over perPage when provided.
        schema:
          maximum: 100
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: desc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Case-insensitive partial match on organization name, slug, or
          email. Does not match users.
        schema:
          minLength: 1
          maxLength: 100
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: createdAt
          type: string
          enum:
          - createdAt
          - updatedAt
          - name
      - name: isActive
        required: false
        in: query
        description: Filter by the ORGANIZATION's active state. Does not filter the
          nested users.
        schema:
          type: boolean
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/OrganizationDirectoryEntryEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List organizations with their members (internal)
      tags:
      - admin
      x-internal: true
  "/admin/organization/slug/{slug}":
    get:
      description: Returns the organization with that slug and its billing summary
        (plan, limits and prices, or null on the legacy model); `permission` is always
        null here. Returns 404 for an unknown slug. Super admin only.
      operationId: OrganizationAdminController_getOrganizationBySlug
      parameters:
      - name: slug
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationDetailsEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an organization by slug (internal)
      tags:
      - admin
      x-internal: true
  "/admin/organization/id/{orgId}":
    get:
      description: Returns the organization and its billing summary (plan, limits
        and prices, or null on the legacy model); `permission` is always null here.
        Returns 400 for a malformed id and 404 for an unknown one. Super admin only.
      operationId: OrganizationAdminController_getOrganizationById
      parameters:
      - name: orgId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationDetailsEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an organization by id (internal)
      tags:
      - admin
      x-internal: true
  "/admin/organization/storage-stats":
    get:
      description: Lists organizations by stored data, largest first, with each case's
        name, creation date and size in bytes summed from its collection records,
        optionally only for `organizationIds` (comma-separated, up to 1000); one page
        at a time. Super admin only.
      operationId: OrganizationAdminController_getOrganizationStorageStats
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: organizationIds
        required: false
        in: query
        description: Filter by organization ids
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/OrganizationStorageStatsItemEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get storage use per organization (internal)
      tags:
      - admin
      x-internal: true
  "/admin/organization/add-all-users-to-all-org-cases":
    post:
      description: 'Grants every member of organization `orgId` access at `caseAccessLevel`
        to every case of the organization; access a member already has is left unchanged.
        Returns `isSuccess: false` when some grants failed, 400 when the organization
        has no members or no cases, and 404 for an unknown organization. Super admin
        only.'
      operationId: OrganizationAdminController_addAllUsersToAllOrgCases
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddAllUsersToAllOrgCasesDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Give every member access to every case (internal)
      tags:
      - admin
      x-internal: true
  "/admin/organization/add-user-to-all-org-cases":
    post:
      description: 'Grants the member with `email` access at `caseAccessLevel` to
        every active case of organization `orgId` they cannot reach yet; archived
        and deleted cases are skipped and existing access, including revoked access,
        is kept as is. Returns `isSuccess: false` when processing failed part way
        (retry to fill the rest), 400 when the user is not a member or there are no
        active cases, and 404 for an unknown organization or user or while the feature
        is turned off. Super admin only.'
      operationId: OrganizationAdminController_addUserToAllOrgCases
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddUserToAllOrgCasesDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
        '400':
          description: Invalid input, user is not an organization member, or no active
            cases found.
        '404':
          description: Feature disabled, organization not found, or user not found.
      security:
      - access-token: []
      summary: Give one member access to every active case (internal)
      tags:
      - admin
      x-internal: true
  "/admin/organization/add-user-to-org":
    post:
      description: Adds the user with `email` to the organization given by `orgId`
        or `orgName` with role `invitedUserRole` (member by default), first creating
        the account with `name` and `password` (random when omitted) when no user
        has that email, and recalculates the seats. Sends a password-reset email unless
        `sendResetPasswordEmail` is false, and succeeds without changes when the user
        is already a member; returns 404 for an unknown organization. Super admin
        only.
      operationId: OrganizationAdminController_addUserToOrg
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddNewUserToOrgDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Add a user to an organization (internal)
      tags:
      - admin
      x-internal: true
  "/admin/organization/remove-user-from-org":
    post:
      description: Removes the membership of the user given by `userId` or `email`
        from the organization given by `orgId` or `orgName`; the user account itself
        is kept. Returns 404 for an unknown organization or user and 400 when the
        user is not a member. Super admin only.
      operationId: OrganizationAdminController_removeUserFromOrg
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RemoveUserFromOrgDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove a user from an organization (internal)
      tags:
      - admin
      x-internal: true
  "/admin/organization/transfer-ownership/{orgId}":
    patch:
      description: Makes the member `newOwnerEmail` the owner of the organization
        and its contact email, and turns `currentOwnerEmail` into an admin. Returns
        400 when both emails are the same, the current user is not the owner or the
        new one is not a member, and 404 for an unknown organization or user. Super
        admin only.
      operationId: OrganizationAdminController_transferOwnership
      parameters:
      - name: orgId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/TransferOrgOwnershipDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Transfer organization ownership (internal)
      tags:
      - admin
      x-internal: true
  "/admin/organization/update-org/{orgId}":
    patch:
      description: Updates the profile fields that are sent (name, email, logo, website,
        contact, address, description, phone, version); `metadata` is merged into
        the existing metadata instead of replacing it. Returns the updated organization,
        or 404 for an unknown one. Super admin only.
      operationId: OrganizationAdminController_updateOrg
      parameters:
      - name: orgId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateAdminOrganizationDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update an organization (internal)
      tags:
      - admin
      x-internal: true
  "/admin/common/cache/reset":
    post:
      description: Clears every entry in the shared cache; cached data is rebuilt
        from the database on next use. Super admin only.
      operationId: CommonAdminController_deleteAllCache
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Clear the whole cache (internal)
      tags:
      - admin
      x-internal: true
  "/admin/common/cache/keys":
    get:
      description: Returns the names of the cache keys matching the glob `pattern`,
        or every key when it is omitted. The lookup scans the whole key space, so
        prefer a narrow pattern. Super admin only.
      operationId: CommonAdminController_getCacheKeys
      parameters:
      - name: pattern
        required: false
        in: query
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      type: string
                    example:
                    - cache-master-data-source-all
                    - cache-master-data-source-name-whatsapp
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List cache keys (internal)
      tags:
      - admin
      x-internal: true
  "/admin/common/cache/value/{key}":
    get:
      description: Returns the value stored under `key` as it was stored, of any JSON
        type; `data` is empty when the key is missing or has expired. Super admin
        only.
      operationId: CommonAdminController_getCacheValue
      parameters:
      - name: key
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    description: The stored value, of any JSON type.
                    nullable: true
                    example:
                      name: whatsapp
                      displayName: WhatsApp
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a cache value (internal)
      tags:
      - admin
      x-internal: true
    post:
      description: Stores `value` under `key`, replacing any existing value, for `ttl`
        milliseconds (at least 1000; one hour when omitted). Super admin only.
      operationId: CommonAdminController_setCacheValueForKey
      parameters:
      - name: key
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SetCacheValueDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Set a cache value (internal)
      tags:
      - admin
      x-internal: true
    delete:
      description: Removes the entry stored under `key`; succeeds whether or not the
        key existed. Super admin only.
      operationId: CommonAdminController_deleteCacheByKey
      parameters:
      - name: key
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a cache key (internal)
      tags:
      - admin
      x-internal: true
  "/admin/user":
    get:
      description: Lists the members of the organizations matched by `organizationId`
        or `organizationName` (one of the two is required; without either, no users
        are returned), optionally narrowed by `email` and `name` (partial, case-insensitive),
        with each user's full profile, one page at a time (10 users by default). Returns
        404 when no user matches. Super admin only.
      operationId: UserAdminController_fetchUsers
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: email
        required: true
        in: query
        schema:
          type: string
      - name: name
        required: true
        in: query
        schema:
          type: string
      - name: organizationName
        required: true
        in: query
        schema:
          type: string
      - name: organizationId
        required: true
        in: query
        schema:
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: createdAt
          type: string
          enum:
          - createdAt
          - updatedAt
          - name
          - email
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UserEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search users (internal)
      tags:
      - admin
      x-internal: true
  "/admin/user/add":
    post:
      description: Creates a user account and its own organization as a sign-up would,
        optionally already email-verified or claimable, and returns the new account's
        access token, profile and organization. Super admin only.
      operationId: UserAdminController_addNewUser
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddUserDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RegisterResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a user (internal)
      tags:
      - admin
      x-internal: true
  "/admin/user/set-password":
    post:
      description: Sets the password of the user given by `userId` or `email`. Returns
        404 for an unknown user. Super admin only.
      operationId: UserAdminController_setPassword
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SetUserPasswordDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Set a user's password (internal)
      tags:
      - admin
      x-internal: true
  "/admin/user/send-reset-password-email":
    post:
      description: Emails a password-reset link to the user given by `userId` or `email`.
        Returns 404 for an unknown user. Super admin only.
      operationId: UserAdminController_sendPasswordResetEmail
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SendPasswordResetEmailDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Send a password-reset email (internal)
      tags:
      - admin
      x-internal: true
  "/admin/user/login-as":
    post:
      description: Signs in as the user with `email` and returns their access and
        refresh tokens and profile, as a normal login would. Returns 404 for an unknown
        user and 400 when the account is not a full account or is inactive. Super
        admin only.
      operationId: UserAdminController_loginAs
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/LoginAsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LoginResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Log in as a user (internal)
      tags:
      - admin
      x-internal: true
  "/admin/user/delete-account":
    post:
      description: Permanently deletes the user with `email`, their memberships and
        every organization they own, with its cases, conversations, messages, collections
        and case access; this cannot be undone. Returns 404 for an unknown user. Super
        admin only.
      operationId: UserAdminController_deleteUserAccount
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeleteUserAccountDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a user account (internal)
      tags:
      - admin
      x-internal: true
  "/admin/user/update-email":
    post:
      description: Changes the email of the user from `oldEmail` to `newEmail` (lower-cased),
        also updating the contact email of organizations they own that used the old
        address. Returns 400 when the two are equal or the new one is taken, and 404
        when no user has the old one. Super admin only.
      operationId: UserAdminController_updateEmail
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateEmailDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Change a user's email (internal)
      tags:
      - admin
      x-internal: true
  "/admin/user/lock":
    post:
      description: Deactivates the user with `email` and the organizations they own,
        and ends all their sessions at once. Returns 404 for an unknown user. Super
        admin only.
      operationId: UserAdminController_lockUserByEmail
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/LockUserByEmailDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Lock a user (internal)
      tags:
      - admin
      x-internal: true
  "/admin/user/unlock":
    post:
      description: Reactivates the user with `email` and the organizations they own;
        sessions ended by the lock stay ended. Returns 404 for an unknown user. Super
        admin only.
      operationId: UserAdminController_unlockUserByEmail
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UnlockUserByEmailDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Unlock a user (internal)
      tags:
      - admin
      x-internal: true
  "/admin/report/device-uploads":
    post:
      description: Counts devices with a completed backup that shared conversations,
        created between `startDate` and `endDate` (both optional, whole days), split
        into Android, iOS and other, plus the organizations whose cases hold them
        with their email, device count and plan type. Returns `{ isSuccess, message
        }` instead when no backup matches. Super admin only.
      operationId: ReportAdminController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/FetchDeviceUploadReportDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      totalBackups:
                        type: number
                        example: 12
                      android:
                        type: number
                        example: 5
                      ios:
                        type: number
                        example: 6
                      others:
                        type: number
                        example: 1
                      organizations:
                        type: array
                        items:
                          type: object
                          properties:
                            organization:
                              type: string
                              nullable: false
                              example: Example Firm
                            email:
                              type: string
                              nullable: false
                              example: admin@example.com
                            deviceCount:
                              type: number
                              example: 3
                            plan:
                              type: string
                              nullable: false
                              example: tier6
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Count device uploads (internal)
      tags:
      - admin
      x-internal: true
  "/admin/report/latest-stats":
    get:
      description: 'Returns platform-wide totals: shared messages, devices and organizations,
        plus message, WhatsApp, voicemail, call-log and contact counts summed over
        one successful extraction per device. Super admin only.'
      operationId: ReportAdminController_fetchLatestStats
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/StatCountResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get platform totals (internal)
      tags:
      - admin
      x-internal: true
  "/admin/report/new-devices":
    post:
      description: Lists devices whose first successful extraction falls in the month
        of `date` (through the month of `endDate` when sent), optionally only for
        organization `orgId`, with the organization's name, email, billing customer
        id and plan, the device type, the case name and whether conversations were
        uploaded. Super admin only.
      operationId: ReportAdminController_fetchNewDevices
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/FetchNewDevicesReportDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/NewExtractionsReportResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List newly extracted devices (internal)
      tags:
      - admin
      x-internal: true
  "/admin/report/org-report":
    get:
      description: 'Lists organizations whose name or email contains `searchText`,
        one page at a time, each with its active plan and an activity summary: account
        age, case and extraction-code counts, unused codes on recent cases, every
        extraction attempt with its outcome and the attempts per device. It loads
        every case, code and extraction of each organization on the page, so keep
        pages small. Super admin only.'
      operationId: ReportAdminController_fetchOrgReport
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: searchText
        required: false
        in: query
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/OrganizationReportEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the organization activity report (internal)
      tags:
      - admin
      x-internal: true
  "/admin/report/last-day-report":
    get:
      description: Returns yesterday's extraction attempts per code, the extraction
        codes created yesterday with their progress, and the data-source collections
        of the day; each part is null when there was nothing. Super admin only.
      operationId: ReportAdminController_fetchLastDayReport
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      extractions:
                        type: object
                        nullable: true
                        description: Extraction attempts per code; null when there
                          were none.
                        properties:
                          extractionCodes:
                            type: array
                            items:
                              type: object
                              properties:
                                extractionCode:
                                  type: string
                                  nullable: false
                                  example: AB12CD
                                codeCreatedByEmail:
                                  type: string
                                  nullable: false
                                  example: paralegal@example.com
                                custodian:
                                  type: string
                                  nullable: false
                                  example: Jane Doe
                                custodianEmail:
                                  type: string
                                  nullable: false
                                  example: jane.doe@example.com
                                caseName:
                                  type: string
                                  nullable: false
                                  example: Doe v. Roe
                                orgName:
                                  type: string
                                  nullable: false
                                  example: Example Firm
                                hasBackupCompleted:
                                  type: boolean
                                  example: true
                                hasConversationShared:
                                  type: boolean
                                  example: false
                                dueDate:
                                  type: string
                                  nullable: true
                                  example: 01/31/2026
                                  description: MM/DD/YYYY.
                                deviceType:
                                  type: string
                                  nullable: false
                                  example: ios
                                totalAttempt:
                                  type: number
                                  example: 2
                                totalSuccessAttempt:
                                  type: number
                                  example: 1
                                totalErrorAttempt:
                                  type: number
                                  example: 1
                                failedReasons:
                                  type: string
                                  nullable: false
                                  example: Backup interrupted
                                isOldDevice:
                                  type: number
                                  enum:
                                  - 0
                                  - 1
                                  example: 0
                      newCodes:
                        type: object
                        nullable: true
                        description: Extraction codes created that day; null when
                          there were none.
                        properties:
                          extractionCodes:
                            type: array
                            items:
                              type: object
                              properties:
                                extractionCode:
                                  type: string
                                  nullable: false
                                  example: AB12CD
                                codeCreatedByEmail:
                                  type: string
                                  nullable: false
                                  example: paralegal@example.com
                                custodian:
                                  type: string
                                  nullable: false
                                  example: Jane Doe
                                custodianEmail:
                                  type: string
                                  nullable: false
                                  example: jane.doe@example.com
                                caseName:
                                  type: string
                                  nullable: false
                                  example: Doe v. Roe
                                orgName:
                                  type: string
                                  nullable: false
                                  example: Example Firm
                                hasBackupCompleted:
                                  type: boolean
                                  example: true
                                hasConversationShared:
                                  type: boolean
                                  example: false
                                dueDate:
                                  type: string
                                  nullable: true
                                  example: 01/31/2026
                                  description: MM/DD/YYYY.
                                organizationPlan:
                                  type: string
                                  nullable: false
                                  example: Pay as you go
                                attachedDataSources:
                                  type: string
                                  nullable: false
                                  example: Phone, Email
                                dataSourcesTotal:
                                  type: number
                                  example: 2
                                dataSourcesCompleted:
                                  type: number
                                  example: 1
                      dataSources:
                        type: object
                        nullable: true
                        description: Data-source collections that day; null when there
                          were none.
                        properties:
                          rows:
                            type: array
                            items:
                              type: object
                              properties:
                                sourceName:
                                  type: string
                                  nullable: false
                                  example: gmail
                                displayName:
                                  type: string
                                  nullable: false
                                  example: Gmail
                                category:
                                  type: string
                                  nullable: false
                                  example: email
                                sourceType:
                                  type: string
                                  nullable: false
                                  example: email
                                status:
                                  type: string
                                  nullable: false
                                  example: completed
                                caseName:
                                  type: string
                                  nullable: false
                                  example: Doe v. Roe
                                orgName:
                                  type: string
                                  nullable: false
                                  example: Example Firm
                                custodian:
                                  type: string
                                  nullable: false
                                  example: Jane Doe
                                extractionCode:
                                  type: string
                                  nullable: false
                                  example: AB12CD
                                itemsCount:
                                  type: number
                                  example: 340
                                totalSize:
                                  type: number
                                  example: 52428800
                                createdBy:
                                  type: string
                                  nullable: false
                                  example: paralegal@example.com
                                createdAt:
                                  type: string
                                  format: date-time
                                  example: '2026-01-15T10:15:00.000Z'
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get yesterday's extraction report (internal)
      tags:
      - admin
      x-internal: true
  "/admin/report/trigger-last-day-report":
    get:
      description: 'Builds yesterday''s extraction report and posts it to the internal
        notification channel; although it is a GET, every call sends the report again.
        Returns `isSuccess: false` when there were no extractions or new codes. Super
        admin only.'
      operationId: ReportAdminController_triggerLastDayReport
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Send yesterday's extraction report (internal)
      tags:
      - admin
      x-internal: true
  "/admin/report/extractions":
    get:
      description: Lists data-source collection statuses that have an extraction code,
        newest first, one page at a time, filtered by `status` (in progress by default),
        `organizationId`, `extractionCode`, a date range on the last status change
        and the `watchlist` and `isVIP` switches. Each row carries the code, status,
        source type, dates, organization name and email and the code's watchlist flag;
        an unknown `extractionCode` returns an empty page. Super admin only.
      operationId: ReportAdminController_fetchExtractionsReport
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: organizationId
        required: false
        in: query
        schema:
          type: string
      - name: extractionCode
        required: false
        in: query
        schema:
          type: string
      - name: status
        required: false
        in: query
        schema:
          default:
          - in_progress
          type: array
          items:
            type: string
            enum:
            - pending
            - in_progress
            - staged
            - completed
            - failed
      - name: startDate
        required: false
        in: query
        schema:
          type: string
      - name: endDate
        required: false
        in: query
        schema:
          type: string
      - name: isVIP
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      - name: watchlist
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/ExtractionStatusItemEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List extraction statuses (internal)
      tags:
      - admin
      x-internal: true
  "/admin/report/user-email-data-providers":
    post:
      description: Lists email accounts connected through extraction codes, one row
        per account and code, with the provider, the code and its creator's email,
        the case and organization names and the number of imported threads, filtered
        by connection date (`startDate`/`endDate`), `extractionCode` or `caseId`.
        An unknown `extractionCode` returns an empty list. Super admin only.
      operationId: ReportAdminController_fetchUserEmailDataProviderReport
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/FetchUserEmailDataProviderReportDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        createdAt:
                          type: string
                          format: date-time
                          example: '2026-01-15T10:15:00.000Z'
                        email:
                          type: string
                          nullable: false
                          example: jane.doe@example.com
                        provider:
                          type: string
                          nullable: false
                          example: gmail
                        extractioncode:
                          type: string
                          nullable: false
                          example: AB12CD
                        extractioncodeCreatorEmail:
                          type: string
                          nullable: false
                          example: paralegal@example.com
                        case:
                          type: string
                          nullable: false
                          example: Doe v. Roe
                        organization:
                          type: string
                          nullable: false
                          example: Example Firm
                        threadsCount:
                          type: number
                          example: 214
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List connected email accounts (internal)
      tags:
      - admin
      x-internal: true
  "/admin/report/phone-device-storage-logs":
    get:
      description: Lists the storage readings phones reported during extraction (calculated
        and user-reported space used and disk size, OS and hardware versions), filtered
        by `deviceId` and a date range, one page at a time. Super admin only.
      operationId: ReportAdminController_fetchPhoneDeviceStorageLogsReport
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: deviceId
        required: false
        in: query
        schema:
          type: string
      - name: startDate
        required: false
        in: query
        schema:
          type: string
      - name: endDate
        required: false
        in: query
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/PhoneDeviceStorageLogItemEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List phone storage logs (internal)
      tags:
      - admin
      x-internal: true
  "/admin/report/notifications":
    get:
      description: Lists internal notifications newest first, one page at a time,
        optionally only those whose thread belongs to `organizationId`, `caseId` or
        `extractionCodeId`, each with the device and data source it refers to. Super
        admin only.
      operationId: ReportAdminController_fetchNotificationsReport
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: organizationId
        required: false
        in: query
        schema:
          type: string
      - name: caseId
        required: false
        in: query
        schema:
          type: string
      - name: extractionCodeId
        required: false
        in: query
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/NotificationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List notifications (internal)
      tags:
      - admin
      x-internal: true
  "/admin/payment/update-latest-sub-for-org":
    post:
      description: Reads the single Stripe subscription of the organization (given
        by `orgId`, or by its owner's email `orgOwnerEmail`) and records it on the
        organization's legacy plan; nothing changes when it is already recorded. Returns
        400 when there is no subscription or several, or the owner owns several organizations,
        and 404 when the organization or owner is not found. Super admin only.
      operationId: PaymentAdminController_updateLatestSubForOrg
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateLatestSubscriptionForOrgDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Sync an organization's legacy subscription (internal)
      tags:
      - admin
      x-internal: true
  "/admin/payment/update-org-plan":
    post:
      deprecated: true
      description: Moves the organization (given by `orgId`, or by its owner's email
        `orgOwnerEmail`) to the legacy subscription product `productId`, invoicing
        the prorated difference at once, and returns the organization's details. Legacy
        billing only; scheduled for removal. Returns 404 when the organization or
        owner is not found. Super admin only.
      operationId: PaymentAdminController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateOrgPlanDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationDetailsEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Change an organization's legacy plan (internal)
      tags:
      - admin
      x-internal: true
  "/admin/tags/migrate":
    post:
      description: Queues a job that backfills the tag links (`attachedTags`) from
        the legacy tag fields of the listed `collections` (every registered collection
        when omitted), `batchSize` documents at a time (50 to 5000, 1000 by default),
        optionally as a `dryRun`. Returns the queued job, or 400 for a collection
        that is not registered. Super admin only.
      operationId: TagMigrationAdminController_trigger
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/TriggerTagMigrationDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/TagMigrationStatusEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start a tag migration (internal)
      tags:
      - admin
      x-internal: true
  "/admin/tags/migrate/{jobId}":
    get:
      description: Returns the job status and the progress of each collection (total,
        processed, skipped, errored, start and end) with the last error. Returns 404
        for an unknown job. Super admin only.
      operationId: TagMigrationAdminController_status
      parameters:
      - name: jobId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/TagMigrationStatusEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a tag migration job (internal)
      tags:
      - admin
      x-internal: true
  "/admin/tags/migrate/{jobId}/cancel":
    post:
      description: Marks the job as cancelled and returns its status. Returns 404
        for an unknown job. Super admin only.
      operationId: TagMigrationAdminController_cancel
      parameters:
      - name: jobId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/TagMigrationStatusEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Cancel a tag migration job (internal)
      tags:
      - admin
      x-internal: true
  "/admin/email/{caseId}/imported-emails/{providerId}":
    delete:
      description: Deletes every email thread, email and attachment imported into
        the case from email account `providerId`, then rolls back the case's collection
        status for that account. Runs synchronously and reports how many threads were
        deleted; returns 404 for an unknown case or account. Super admin only.
      operationId: EmailAdminController_deleteImportedEmails
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case the emails were imported into
        schema:
          type: string
      - name: providerId
        required: true
        in: path
        description: Id of the UserEmailDataProvider (the imported email account)
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a mailbox's imported emails from a case (internal)
      tags:
      - admin
      x-internal: true
  "/admin/email/backfill-tag-sources":
    post:
      description: Adds to each tag the mailboxes of the emails it is attached to,
        for one case (`caseId`) or for all cases; it only adds, so it is safe to repeat.
        Runs synchronously and reports how many tags changed. Super admin only.
      operationId: EmailAdminController_backfillTagSources
      parameters: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BackfillTagSourcesDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Backfill tag mailboxes (internal)
      tags:
      - admin
      x-internal: true
  "/admin/email/{caseId}/fix-search-tag-attribution":
    post:
      description: For emails imported into the case by a multi-keyword search, removes
        the keyword tags whose keyword does not appear in the email (sender, recipient,
        subject and manual tags stay) and recomputes the thread tags, optionally only
        for mailbox `providerId`. Safe to repeat; runs synchronously and reports how
        many emails and threads changed. Super admin only.
      operationId: EmailAdminController_fixSearchTagAttribution
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case to fix
        schema:
          type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/FixSearchTagAttributionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Fix keyword tags on search imports (internal)
      tags:
      - admin
      x-internal: true
  "/admin/case-files/{caseId}/delete":
    post:
      description: Soft-deletes files uploaded to the case through the portal or by
        custodians, either the ids in `caseFileIds` or all of them with `all`; documents
        synced from a legal integration are not touched. Deleted files leave every
        case view and the files data source counts go down. Returns how many files
        matched, already deleted ones included, and how many this call deleted, so
        a retry deletes nothing. Returns 404 for an unknown case or when any id is
        not an uploaded file on the case. Super admin only.
      operationId: CaseFileAdminController_deleteCaseFiles
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case the files were uploaded to
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeleteCaseFilesDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DeleteCaseFilesResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete uploaded case files (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/billing-profile":
    patch:
      description: Changes the tier, billing mode, billing email, card-on-file flag,
        charge trigger or price overrides of an organization already on case billing.
        Only the fields sent change; overrides merge into the existing ones, and a
        `null` SKU price removes that override. Returns the saved profile with warnings,
        for example when an override will bill a SKU the tier normally includes. Returns
        404 when the organization has no billing profile and 400 for an invalid tier,
        mode and cadence combination or any change of cadence. Super admin only.
      operationId: BillingAdminController_updateBillingProfile
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateAdminBillingProfileDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminBillingProfileResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update an organization's billing profile (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/migrate-billing":
    post:
      description: Creates the organization's billing profile with the given tier,
        billing mode and cadence, moves its legacy subscription's card onto the customer
        when it can, then cancels the legacy subscription. Returns whether a card
        is on file and whether a legacy subscription was cancelled. Returns 404 for
        an unknown organization, 409 when it is already on case billing and 400 for
        an invalid tier, mode and cadence combination. Super admin only.
      operationId: BillingAdminController_migrateOrgBilling
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/MigrateOrgBillingDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminOrgBillingMigrationResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Move an organization to case billing (internal)
      tags:
      - admin
      x-internal: true
  "/admin/cases/{caseId}/billing-items":
    post:
      description: 'Records a staff add-on on the case: `quantity` of the SKU at its
        price or at `unitAmountCentsOverride`. With `comped` the item is waived at
        once; otherwise a SKU billed immediately is charged right away and the rest
        wait for the daily sweep. Sending the same `requestId` again returns the existing
        item instead of charging twice. Returns 404 for an unknown case or when its
        organization has no billing profile and 400 when the SKU cannot be priced.
        Super admin only.'
      operationId: BillingAdminController_createBillingItem
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateAdminBillingItemDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminBillingItemResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Add a billing item to a case (internal)
      tags:
      - admin
      x-internal: true
  "/admin/cases/{caseId}/pro-upgrade":
    post:
      description: Turns on pro for the case and grants its AI tokens at once, recorded
        as a comped pro-upgrade billing item that is never charged. Returns 404 for
        an unknown case or when its organization has no billing profile, and 409 when
        the case already has a pro-upgrade item (comp that item instead). Super admin
        only.
      operationId: BillingAdminController_compedProUpgrade
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AdminProUpgradeDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminBillingItemResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Give a case a free pro upgrade (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing-items/{id}/comp":
    post:
      description: Waives a billing item that is pending, parked as a draft or failed
        to collect, so it is never charged; comping a pro-upgrade item also grants
        pro to its case. Returns 404 for an unknown item and 409 when the item has
        any other status or changed at the same time. Super admin only.
      operationId: BillingAdminController_compBillingItem
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the billing item
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AdminBillingItemActionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminBillingItemResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Comp a billing item (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing-items/{id}/void":
    post:
      description: Cancels a billing item that is pending, parked as a draft or failed
        to collect, and frees its idempotency key so the same charge can be recorded
        again. Returns 404 for an unknown item and 409 when the item has any other
        status or changed at the same time. Super admin only.
      operationId: BillingAdminController_voidBillingItem
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the billing item
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AdminBillingItemActionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminBillingItemResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Void a billing item (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing/token-grants":
    post:
      description: Adds `tokens` to the case's AI token allowance and returns the
        new allowance and usage. When the organization is on case billing the grant
        also shows on its statement as a $0 item, and a repeated `requestId` does
        not grant again. Returns 404 for an unknown case. Super admin only.
      operationId: BillingAdminController_grantTokens
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/GrantAiTokensDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminTokenGrantResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Grant AI tokens to a case (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/plan":
    post:
      description: Sets the organization's self-serve tier and cadence (pay as you
        go), with the same proration as the customer flow, or assigns its first plan
        when it has none. Cadence defaults to monthly; an annual-only tier is always
        annual and returns 400 for any other cadence. Returns the plan, the proration
        in cents and whether the organization still has to add a card, which staff
        cannot do here. Super admin only.
      operationId: BillingAdminController_changePlan
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AdminChangePlanDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminPlanChangeResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Change an organization's plan (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/billing":
    get:
      description: Returns the organization's current plan (tier, cadence, billing
        mode and card on file), or null when it is not on case billing, its use of
        seats, storage, cases and AI tokens against their caps or budget, and whether
        its base fee is overdue. Super admin only.
      operationId: BillingAdminController_getBilling
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BillingPlanCurrentResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an organization's plan and usage (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/billing/statement":
    get:
      description: Returns the organization's billing items grouped by case, with
        organization-wide charges in their own group, each group with its rows and
        the subtotal charged. Page with `limit` and `skip`. Super admin only.
      operationId: BillingAdminController_getStatement
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: limit
        required: false
        in: query
        schema:
          type: number
      - name: skip
        required: false
        in: query
        schema:
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrgReconciliationResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an organization's billing statement (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/credits":
    post:
      description: Adds `amountCents` of credit to the organization's balance without
        any payment and returns the new balance with the latest 50 ledger entries.
        A repeated `requestId` does not grant again. Returns 403 while credits are
        turned off. Super admin only.
      operationId: BillingAdminController_grantCredits
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/GrantCreditDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CreditBalanceResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Grant credit to an organization (internal)
      tags:
      - admin
      x-internal: true
    get:
      description: Returns the organization's credit balance, its deduction percentage
        and its credit ledger entries (`limit` default 50, `skip`). Returns 403 while
        credits are turned off. Super admin only.
      operationId: BillingAdminController_getCredits
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: limit
        required: false
        in: query
        schema:
          type: number
      - name: skip
        required: false
        in: query
        schema:
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CreditBalanceResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an organization's credit balance (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/prepaid":
    get:
      description: Returns the organization's prepaid settings (null when it has none),
        its remaining units per SKU and its prepaid ledger entries, newest first (`limit`
        default 20, `skip`). Super admin only.
      operationId: BillingAdminController_getPrepaid
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: limit
        required: false
        in: query
        schema:
          type: number
      - name: skip
        required: false
        in: query
        schema:
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/PrepaidStateResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an organization's prepaid units (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/prepaid/override":
    post:
      description: 'Turns the unpaid override on or off for an organization on prepaid
        billing: with `allowUnpaid` its collections go through without prepaid units,
        and `reason` is stored while the override is on. Returns the prepaid state
        with the latest 20 ledger entries. Returns 404 when the organization has no
        billing profile and 400 when it is not on prepaid billing. Super admin only.'
      operationId: BillingAdminController_setPrepaidOverride
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdatePrepaidOverrideDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/PrepaidStateResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Waive prepaid units for an organization (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing/sweep-runs":
    get:
      description: Lists the recorded daily billing sweep runs, nightly and admin-triggered,
        most recent first, with timings, status, counts and failed groups. Page with
        `limit` (default 50) and `skip`. Super admin only.
      operationId: BillingAdminController_listSweepRuns
      parameters:
      - name: limit
        required: false
        in: query
        schema:
          type: number
      - name: skip
        required: false
        in: query
        schema:
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/AdminBillingSweepRunResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List daily billing sweep runs (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing/cron/daily-sweep":
    post:
      description: Runs the daily billing sweep (base fees, recurring add-ons, then
        invoicing of pending items) as of `asOf` or now, for every organization or
        only `organizationId`, records the run and returns its summary. Not available
        in production (403). Super admin only.
      operationId: BillingAdminController_triggerDailySweep
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/TriggerBillingSweepDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      invoicesCreated:
                        type: integer
                        example: 3
                      totalCents:
                        type: integer
                        example: 45000
                      groupsFailed:
                        type: integer
                        example: 0
                      failedGroups:
                        type: array
                        description: Which group failed and why; empty on a clean
                          run.
                        items:
                          type: string
                        example: []
                      stuckReset:
                        type: integer
                        example: 0
                      baseFeeRowsCreated:
                        type: integer
                        example: 2
                      recurringAddonRowsCreated:
                        type: integer
                        example: 1
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Run the daily billing sweep now (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing/cron/ai-overage-close":
    post:
      description: 'Bills organizations on an AI tier for the AI tokens used in `yearMonth`
        (default: the current UTC month) above their budget, for every organization
        or only `organizationId`, and returns how many organizations were checked
        and how many overages were billed. Running it again for the same month bills
        nothing new. Not available in production (403). Super admin only.'
      operationId: BillingAdminController_triggerAiOverageClose
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CloseBillingMonthDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      orgsChecked:
                        type: integer
                        example: 12
                      overagesBilled:
                        type: integer
                        example: 1
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Run the monthly AI overage close now (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing/cron/storage-close":
    post:
      description: 'Checks the storage used in `yearMonth` (default: the current UTC
        month) against each organization''s storage cap, for every organization or
        only `organizationId`, and sends one alert listing the organizations over
        their cap; nothing is charged. Returns how many organizations were checked
        and how many were over. Not available in production (403). Super admin only.'
      operationId: BillingAdminController_triggerStorageClose
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CloseBillingMonthDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      orgsChecked:
                        type: integer
                        example: 12
                      breaches:
                        type: integer
                        example: 1
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Run the monthly storage check now (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing/cron/reconciliation":
    post:
      description: Compares Stripe with the billing ledger as of `asOf` or now, across
        all organizations, and returns invoice and ledger mismatches, items stuck
        as invoiced, legacy subscription drift, lost pro grants and migrated organizations
        that still have a live legacy subscription. It also voids leftover empty draft
        invoices and lists them. Not available in production (403). Super admin only.
      operationId: BillingAdminController_triggerReconciliation
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/TriggerReconciliationDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      invoiceMismatches:
                        type: array
                        items:
                          type: object
                          properties:
                            kind:
                              type: string
                              enum:
                              - orphan
                              - amount
                            invoiceId:
                              type: string
                            stripeTotalCents:
                              type: integer
                            ledgerSumCents:
                              type: integer
                              nullable: true
                      stuckInvoiced:
                        type: array
                        items:
                          type: object
                          properties:
                            billingItemId:
                              type: string
                            invoiceId:
                              type: string
                              nullable: true
                            ageMs:
                              type: integer
                      subscriptionDrift:
                        type: array
                        items:
                          type: object
                          properties:
                            kind:
                              type: string
                              enum:
                              - missing
                              - status
                            subscriptionId:
                              type: string
                            stripeStatus:
                              type: string
                            localStatus:
                              type: string
                              nullable: true
                      voidedEmptyDrafts:
                        type: array
                        items:
                          type: object
                          properties:
                            invoiceId:
                              type: string
                            ageMs:
                              type: integer
                      lostProGrants:
                        type: array
                        items:
                          type: object
                          properties:
                            billingItemId:
                              type: string
                            caseId:
                              type: string
                              nullable: true
                            invoiceId:
                              type: string
                              nullable: true
                      migratedOrgsWithLiveLegacySub:
                        type: array
                        items:
                          type: object
                          properties:
                            organizationId:
                              type: string
                            subscriptionExternalId:
                              type: string
                              nullable: true
                            status:
                              type: string
                    example:
                      invoiceMismatches: []
                      stuckInvoiced: []
                      subscriptionDrift: []
                      voidedEmptyDrafts: []
                      lostProGrants: []
                      migratedOrgsWithLiveLegacySub: []
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Run billing reconciliation now (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing/coupons":
    post:
      description: 'Creates the coupon in Stripe, keeps a local copy for search and
        returns it. Send exactly one of `percentOff` or `amountOffCents` (with `currency`);
        `durationInMonths` is required with `duration: repeating` and not allowed
        otherwise. Breaking either rule returns 400. Super admin only.'
      operationId: BillingCouponAdminController_createCoupon
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateCouponDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminCouponResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a coupon (internal)
      tags:
      - admin
      x-internal: true
    get:
      description: Lists coupons newest first with the total that match, filtered
        by `status` (active, expired or deleted, worked out from the expiry and deletion
        dates), `duration`, creation date range and a case-insensitive `search` on
        code and name. `limit` defaults to 50 (at most 200). Super admin only.
      operationId: BillingCouponAdminController_listCoupons
      parameters:
      - name: status
        required: false
        in: query
        description: active = not deleted and not past redeemBy; expired = past redeemBy;
          deleted = withdrawn in Stripe
        schema:
          type: string
          enum:
          - active
          - expired
          - deleted
      - name: search
        required: false
        in: query
        description: Case-insensitive substring over coupon code and name
        schema:
          type: string
      - name: duration
        required: false
        in: query
        schema:
          type: string
          enum:
          - once
          - repeating
          - forever
      - name: createdFrom
        required: false
        in: query
        description: Created at or after (ISO 8601)
        schema:
          format: date-time
          type: string
      - name: createdTo
        required: false
        in: query
        description: Created at or before (ISO 8601)
        schema:
          format: date-time
          type: string
      - name: limit
        required: false
        in: query
        schema:
          type: number
      - name: skip
        required: false
        in: query
        schema:
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminCouponListResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List coupons (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing/coupons/{couponId}":
    get:
      description: Returns one coupon by its code, deleted ones included. Returns
        404 for an unknown code. Super admin only.
      operationId: BillingCouponAdminController_getCoupon
      parameters:
      - name: couponId
        required: true
        in: path
        description: Stripe coupon id / redeemable code
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminCouponResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a coupon (internal)
      tags:
      - admin
      x-internal: true
    delete:
      description: Deletes the coupon in Stripe so it can no longer be applied and
        marks the local copy deleted; discounts already on a customer or an invoice
        stay. Returns the coupon, or 404 for an unknown code. Super admin only.
      operationId: BillingCouponAdminController_deleteCoupon
      parameters:
      - name: couponId
        required: true
        in: path
        description: Stripe coupon id / redeemable code
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminCouponResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete a coupon (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/coupon":
    post:
      description: Puts the coupon on top of the organization's coupon stack, making
        it the active discount on the organization's Stripe customer while earlier
        coupons stay beneath it, and returns the stack. Returns 404 when the organization
        has no Stripe customer or the coupon is unknown, and 400 when the coupon is
        expired, deleted or already on the stack. Super admin only.
      operationId: BillingCouponAdminController_applyCoupon
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ApplyCouponDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminCouponStackResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Apply a coupon to an organization (internal)
      tags:
      - admin
      x-internal: true
    delete:
      description: Removes the top coupon from the organization's stack and makes
        the next eligible coupon beneath it active, or clears the discount when none
        is left, then returns the stack. Returns 404 when the organization has no
        Stripe customer or no coupons on its stack. Super admin only.
      operationId: BillingCouponAdminController_popTopCoupon
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminCouponStackResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove the active coupon (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/coupon/{couponId}":
    delete:
      description: Removes the named coupon wherever it sits in the organization's
        stack, then makes the top eligible coupon the active discount, and returns
        the stack. Returns 404 when the organization has no Stripe customer or the
        coupon is not on its stack. Super admin only.
      operationId: BillingCouponAdminController_removeStackedCoupon
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: couponId
        required: true
        in: path
        description: Stripe coupon id / redeemable code
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminCouponStackResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove a coupon from the stack (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/coupons":
    get:
      description: Lists the coupons on the organization's stack, top first, each
        with its status, whether it could be the active discount and whether it is,
        plus the id of the active coupon. Super admin only.
      operationId: BillingCouponAdminController_getOrgCouponStack
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminCouponStackResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an organization's coupon stack (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing-items/drafts":
    get:
      description: Lists billing items parked as drafts, newest first, across all
        organizations or narrowed by `organizationId` and `caseId`, with the total.
        `limit` defaults to 20 (at most 100). Super admin only.
      operationId: BillingDraftAdminController_listDrafts
      parameters:
      - name: organizationId
        required: false
        in: query
        description: Only this organization
        schema:
          type: string
      - name: caseId
        required: false
        in: query
        description: Only this case
        schema:
          type: string
      - name: limit
        required: false
        in: query
        schema:
          maximum: 100
          default: 20
          type: number
      - name: skip
        required: false
        in: query
        schema:
          default: 0
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          "$ref": "#/components/schemas/AdminBillingItemResponseEntity"
                      total:
                        type: integer
                        example: 1
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List draft billing items (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing-items/{id}/draft":
    post:
      description: Parks a pending billing item so the invoicing sweep skips it until
        it is released; it still counts as owed on statements. Returns 404 for an
        unknown item, 400 when the item is not pending and 409 when it changed at
        the same time. Super admin only.
      operationId: BillingDraftAdminController_draftBillingItem
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the billing item
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AdminBillingItemActionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminBillingItemResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Park a billing item as a draft (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing-items/{id}/ready":
    post:
      description: Returns a draft billing item to pending so it bills on the next
        sweep. Returns 404 for an unknown item, 400 when the item is not a draft and
        409 when it changed at the same time. Super admin only.
      operationId: BillingDraftAdminController_readyBillingItem
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the billing item
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ReleaseDraftBillingItemDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminBillingItemResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Release a draft billing item (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing/report":
    post:
      description: 'Builds a billing report for the organizations in `orgIds` (default:
        every organization on case billing), narrowed by `caseIds` and the date range.
        In JSON format the report comes back in this response, with each organization''s
        charges and the grand total charged; in PDF format it is queued and this returns
        its id and status to poll. Returns 400 for invalid filters and 404 while billing
        reports are turned off. Super admin only.'
      operationId: BillingReportAdminController_createReport
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AdminBillingReportQueryDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    oneOf:
                    - "$ref": "#/components/schemas/BillingReportResponseEntity"
                    - "$ref": "#/components/schemas/BillingReportStatusResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Build a billing report across organizations (internal)
      tags:
      - admin
      x-internal: true
  "/admin/billing/report/{reportId}":
    get:
      description: Returns the status of a PDF billing report the caller requested,
        with a temporary download link once it is ready, the error when it failed
        and the organizations left out because they could not be rendered. Returns
        404 for an unknown report or while billing reports are turned off, and 403
        for a report another admin requested. Super admin only.
      operationId: BillingReportAdminController_getReportStatus
      parameters:
      - name: reportId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BillingReportStatusResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a queued billing report (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/recurring-addons":
    post:
      description: Starts a recurring add-on for the organization (kit or laptop rental
        billed monthly, live bank every six months), optionally tied to `case`, with
        periods anchored on the day of `startedAt` (now by default). Repeating the
        same organization, SKU and `externalRef` returns the existing record instead
        of starting a second one; returns 400 for a SKU that is not a recurring add-on
        or an organization without a case-billing profile. Super admin only.
      operationId: RecurringAddonAdminController_activate
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ActivateRecurringAddonDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminRecurringAddonResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start a recurring add-on (internal)
      tags:
      - admin
      x-internal: true
    get:
      description: Lists the organization's recurring add-ons, newest first, optionally
        only those with `status`. Super admin only.
      operationId: RecurringAddonAdminController_list
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: status
        required: false
        in: query
        description: Omit to return both active and canceled subscriptions
        schema:
          type: string
          enum:
          - active
          - canceled
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminRecurringAddonListResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List recurring add-ons (internal)
      tags:
      - admin
      x-internal: true
  "/admin/orgs/{orgId}/recurring-addons/{addonId}":
    delete:
      description: Stops the future periods of the add-on; the period under way is
        still billed in full, so this is not a refund. Cancelling an already cancelled
        add-on changes nothing; returns 404 when the add-on does not belong to the
        organization. Super admin only.
      operationId: RecurringAddonAdminController_cancel
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      - name: addonId
        required: true
        in: path
        description: Id of the recurring add-on record
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CancelRecurringAddonDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminRecurringAddonResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Cancel a recurring add-on (internal)
      tags:
      - admin
      x-internal: true
  "/admin/feature-flags":
    get:
      description: Lists every feature flag this build defines plus any leftover rows,
        unpaginated. `status` says whether a flag has its database row (SEEDED), is
        still waiting on its seed migration (UNSEEDED, reads as off) or is a leftover
        from a retired flag (ORPHANED). Super admin only.
      operationId: FeatureFlagAdminController_listFeatureFlags
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminFeatureFlagListResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List feature flags (internal)
      tags:
      - admin
      x-internal: true
  "/admin/feature-flags/{key}":
    patch:
      description: Sets the flag and records who changed it; the node that served
        the request applies it at once and other nodes within about 45 seconds. Sending
        the current value changes nothing. Returns 404 for a key this build does not
        define, including an ORPHANED one. Super admin only.
      operationId: FeatureFlagAdminController_updateFeatureFlag
      parameters:
      - name: key
        required: true
        in: path
        description: The flag key as defined in the FeatureFlag enum, e.g. "clio-integration"
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateFeatureFlagDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AdminFeatureFlagResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
        '404':
          description: No such feature flag in this build
      security:
      - access-token: []
      summary: Turn a feature flag on or off (internal)
      tags:
      - admin
      x-internal: true
  "/extraction/device/file-hash":
    post:
      description: 'Desktop plumbing: stores or updates the hash of each listed backup
        file for the device on this computer, and records the counts in the device
        activity log.'
      operationId: ExtractionController_addUpdateFileHash
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddUpdateDeviceFileHashDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Save device file hashes (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/device/match-file-hash":
    post:
      description: 'Desktop plumbing: checks the listed backup file hashes against
        the ones stored for the device on this computer, records the result in the
        device activity log and returns it as `matched`.'
      operationId: ExtractionController_matchFileHash
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/MatchDeviceFileHashDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MatchDeviceFileHashResultEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Match device file hashes (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/device/app-general-logs":
    post:
      description: 'Desktop plumbing: takes a log file (multipart field `file`), zips
        it with the request details, stores it in S3 as a file record and posts a
        Slack notification with its download link for the support team. An extraction
        code in the body, if valid, is added to the notification.'
      operationId: ExtractionController_uploadLogsOpen
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UploadLogOpenDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Upload app logs without a session (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/device/app-logs":
    post:
      description: 'Desktop plumbing: takes a log file (multipart field `file`), zips
        it with the request details, stores it in S3 and posts a Slack notification
        with its download link and the device, OS and backup details. Also saves an
        app log record for the caller''s extraction code and case. Returns the Slack
        message ts as `threadTs`.'
      operationId: ExtractionController_uploadAppLogs
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UploadAppLogDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UploadDeviceLogResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Upload app logs (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/device/app-general-logs/v2":
    post:
      description: 'Desktop plumbing: same as app-general-logs, but the log file is
        one the app already uploaded to S3 (`fileProviderId`); it is downloaded, zipped
        with the request details, stored again and announced on Slack.'
      operationId: ExtractionController_uploadLogsOpenV2
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UploadLogOpenDtoV2"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Upload app logs from S3 without a session (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/device/app-logs/v2":
    post:
      description: 'Desktop plumbing: same as app-logs, but the log file is one the
        app already uploaded to S3 (`fileProviderId`). Returns the Slack message ts
        as `threadTs`.'
      operationId: ExtractionController_uploadAppLogsV2
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UploadAppLogDtoV2"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UploadDeviceLogResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Upload app logs from S3 (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/device/extraction-status":
    post:
      description: 'Desktop plumbing: for each listed device, returns when it last
        finished a backup with the caller''s extraction code on that computer, and
        whether the code''s collection settings have changed since (`hasNewEcCodeConfig`).
        A device with no finished backup gets `lastBackupAt: null`.'
      operationId: ExtractionController_fetchDevicesExtractionStatus
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/FetchDevicesExtractionStatusDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DevicesExtractionStatusResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the backup status of devices (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/auth/logout":
    post:
      description: Ends the current extraction-code session by deleting the session
        of the bearer token. `data` is always an empty object.
      operationId: ExtractionController_logout
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: Always empty
                    properties: {}
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      - access-token: []
      summary: Sign out
      tags:
      - extraction
  "/extraction/auth/refresh":
    post:
      description: Issues a new access token for a refresh token and returns it with
        the same refresh token. Returns 404 for an unknown refresh token and 400 when
        it has expired. The new token does not carry the extraction code, so extraction
        routes reject it with 401; an extraction-code session signs in again instead.
      operationId: ExtractionController_refresh
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RefreshTokenDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RefreshTokenResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Refresh the access token (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/auth/login-with-extraction":
    post:
      description: Signs in with an extraction code and returns access and refresh
        tokens, the code's user profile, the extraction code with its data sources,
        its case, the saved app config (when one is sent) and whether collection events
        are on. Creates a session and records the login for collection tracking. Inactive,
        locked, unreviewed or rejected codes get 401, and a code used from the wrong
        white-label app gets 400.
      operationId: ExtractionController_loginWithExtraction
      parameters:
      - name: user-agent
        required: true
        in: header
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/LoginExtractionCodeDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LoginResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Sign in with an extraction code
      tags:
      - extraction
  "/extraction/aws/s3-user-data-upload-credentials":
    post:
      description: 'Desktop plumbing: returns temporary AWS credentials, with the
        bucket, region and API version, for uploading collected data to S3. The plan
        checked is the one of the extraction code''s organization, whatever organization
        the request names.'
      operationId: ExtractionController_getTemporaryUserDataUploadToken
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AwsTemporaryS3CredentialEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get temporary S3 upload credentials (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/conversation/bulk-import":
    post:
      description: 'Desktop plumbing: queues the uploaded conversations for background
        import into the caller''s case. When search data is sent, it is also saved
        as a search-tracking record for the extraction code.'
      operationId: ExtractionController_bulkUpload
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        description: Bulk upload conversation
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UploadBulkConversationDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Import uploaded conversations (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/conversation/search-uploaded-conversations":
    get:
      description: 'Desktop plumbing: lists the conversations already uploaded to
        the extraction code''s case, newest first, optionally only one device''s (`deviceExternalId`).
        The case is always the extraction code''s own; a case in the query is ignored.
        At most 100 per page.'
      operationId: ExtractionController_searchUploadedConversation
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: case
        required: true
        in: query
        schema:
          type: string
      - name: deviceExternalId
        required: false
        in: query
        schema:
          type: string
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UploadedConversation"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List uploaded conversations (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/device-activity-log":
    post:
      description: 'Desktop plumbing: records a device activity event (backup, conversation
        upload, plug-in or resync started/completed/failed). A repeat of the same
        event for the same device and computer within 10 seconds is ignored and answered
        with the message `Already added`.'
      operationId: ExtractionController_addActivityLog
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddDeviceActivityLogDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Record device activity (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/device/backup-update":
    post:
      description: 'Desktop plumbing: records a device backup lifecycle transition
        (started, completed, failed or cancelled). On `completed`, charges the collection
        when the organization is on backup-time charging. Always 200 once handled;
        `billingOutcome` says what the charge dispatch did, and a declined charge
        is a normal outcome, never an error.'
      operationId: ExtractionController_deviceBackupUpdate
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeviceBackupUpdateDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DeviceBackupUpdateResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Report a device backup update (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/device/storage-updates":
    post:
      description: 'Desktop plumbing: saves the storage figures the app calculated
        for a device, keyed by its device id. If the device already has a storage
        log, the previous figures are kept in its history and the user-reported figures
        are cleared.'
      operationId: ExtractionController_updateDeviceStorage
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateDeviceStorageDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Report device storage usage (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/device/storage-feedback":
    post:
      description: 'Desktop plumbing: saves the storage figures the user reported
        on the device''s storage log. Returns `isSuccess: false` when the device has
        no storage log yet.'
      operationId: ExtractionController_recordDeviceStorageFeedback
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeviceStorageFeedbackDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Record user-reported device storage (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/device/add-update-device":
    post:
      description: 'Desktop plumbing: creates the device from the app''s device metadata,
        or updates it, links it to the caller''s extraction code, organization and
        case, and creates or updates the case''s data-source status for it. Returns
        the device without the lists of organizations and extraction codes it is linked
        to.'
      operationId: ExtractionController_addUpdateDevice
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddUpdateDeviceDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AddUpdateDeviceResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Register or update a device (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/user/profile":
    get:
      description: Returns the user profile of the caller's extraction code, including
        its organization.
      operationId: ExtractionController_getProfile
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the signed-in user's profile
      tags:
      - extraction
    patch:
      description: Updates the profile of the caller's extraction-code user with the
        given fields and returns the updated profile.
      operationId: ExtractionController_updateProfile
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateUserDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update the signed-in user's profile
      tags:
      - extraction
  "/extraction/case/metadata":
    patch:
      description: Sets the given keys in the metadata of the caller's extraction
        code (other keys are kept) and returns the updated extraction code.
      operationId: ExtractionController_updateExtractionCodeMetadata
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateExtractionCodeMetadataDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExtractionCodeEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update extraction code metadata
      tags:
      - extraction
  "/extraction/organization/{orgId}/extractor-usages-stat":
    get:
      description: 'Desktop plumbing: returns plan usage for the organization of the
        caller''s extraction code (seats, devices and storage used, plan flags), whether
        the device in the query is new to the organization, and the client payment
        details. The orgId path parameter is not used.'
      operationId: ExtractionController_getExtractorOrgUsagesStat
      parameters:
      - name: deviceId
        required: false
        in: query
        schema:
          type: string
      - name: deviceType
        required: false
        in: query
        schema:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/OrganizationUsagesStatEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the organization's plan usage (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/organization/{orgId}/contact-admin":
    post:
      description: 'Desktop plumbing: queues an email to the admins of the organization
        that owns the caller''s extraction code, asking for more device capacity,
        with the caller''s message or a default text. The organization id in the path
        is ignored.'
      operationId: ExtractionController_contactAdminForDevice
      parameters:
      - name: orgId
        required: true
        in: path
        description: Id of the organization
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeviceRequestContactAdminDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Ask the organization's admins for more devices (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/notifications/update-notification":
    post:
      description: 'Desktop plumbing: posts the given messages to the internal Slack
        thread of the device''s backup and saves them as a notification record. Returns
        the Slack message `ts`; `data` is null or missing when the post failed or
        notifications are blocked for this extraction code and device.'
      operationId: ExtractionController_postUpdateNotification
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/PostUpdateNotificationDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/SlackNotificationResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Post device messages to Slack (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/notifications/tracking-notification":
    post:
      description: 'Desktop plumbing: posts the given messages, with OS and extraction-code
        details, to the internal Slack thread for this computer and saves them as
        a notification record. Returns the Slack message `ts`; `data` is null or missing
        when the post failed.'
      operationId: ExtractionController_postTrackingNotification
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/PostTrackingNotificationDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/SlackNotificationResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Post tracking messages to Slack (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/notifications/conversation-uploads":
    post:
      description: 'Desktop plumbing: posts a conversation upload status (message,
        conversation count, app version and device) to the internal Slack thread of
        the device''s backup, and saves it, with any message selection report, as
        a notification record. Returns the Slack message `ts`; `data` is null or missing
        when the post failed.'
      operationId: ExtractionController_postConversationUploadNotifications
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UploadConversationsNotificationsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/SlackNotificationResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Post a conversation upload status to Slack (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/notifications/debug-logs-upload":
    post:
      description: 'Desktop plumbing: creates a file record for a debug bundle the
        app already uploaded to S3 (`logPath`) and posts a Slack notification with
        its download link. Returns 404 if the file is not in S3.'
      operationId: ExtractionController_postDebugLogNotifications
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/PostDebugLogNotificationDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Register an uploaded debug bundle (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/app-settings/status":
    get:
      description: 'Desktop plumbing: returns whether local conversion, local compression
        and collection events are turned on.'
      operationId: ExtractionController_getAppSettingsStatus
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AppSettingsResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get desktop app settings (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/case/import-files":
    post:
      description: Adds files the caller already uploaded to S3 to the extraction
        code's case, with an optional category; keys not found in S3 are skipped.
        Marks the case's files data source as completed and reports the collection
        to billing.
      operationId: ExtractionController_importCaseFiles
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddCaseFilesDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Add uploaded files to the case
      tags:
      - extraction
  "/extraction/case/import-media":
    post:
      description: 'Desktop plumbing: registers the device on the caller''s case and
        stores a record for each media file, linking its original and thumbnail already
        uploaded to S3. Returns how many records were imported.'
      operationId: ExtractionController_importCaseMedia
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ImportCaseMediaDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Import device media records (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/case/import-notes":
    post:
      description: 'Desktop plumbing: registers the device on the caller''s case and
        stores its notes, linking any attachments already uploaded to S3. Returns
        how many notes were imported.'
      operationId: ExtractionController_importCaseNotes
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ImportCaseNotesDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Import device notes (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/case/import-browser-history":
    post:
      description: 'Desktop plumbing: registers the device on the caller''s case,
        downloads the Safari History.db the app uploaded to S3 and imports its browsing
        history. The file key must be under the device''s own path, otherwise 400.'
      operationId: ExtractionController_importBrowserHistory
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ImportCaseBrowserHistoryDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Import Safari browser history (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/case/import-call-logs":
    post:
      description: 'Desktop plumbing: registers the device on the caller''s case and
        stores the given call-log conversations and their calls. Returns how many
        conversations and messages were imported.'
      operationId: ExtractionController_importCallLogs
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ImportCaseCallLogsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Import call logs (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/case/check-imported-files":
    get:
      description: 'Desktop plumbing: returns how many case files were uploaded with
        the caller''s extraction code and their total size in bytes.'
      operationId: ExtractionController_checkImportedFiles
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseFilesImportedCountEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Count imported files (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/case/search-files-from-extraction-code":
    get:
      description: Lists the case files uploaded with the caller's extraction code,
        newest first by default, with paging. The firm's tags are not included.
      operationId: ExtractionController_searchFilesFromExtractionCode
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: true
        in: query
        schema:
          type: string
      - name: extractionCode
        required: false
        in: query
        schema:
          type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: createdAt
          type: string
          enum:
          - createdAt
          - name
          - type
          - size
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseFileEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List files uploaded with the extraction code
      tags:
      - extraction
  "/extraction/case/email-providers":
    get:
      description: Lists the mailboxes linked to the caller's extraction code. OAuth
        tokens and IMAP credentials are never returned.
      operationId: ExtractionController_searchEmailProviders
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/UserEmailDataProviderEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List the extraction code's mailboxes
      tags:
      - extraction
  "/extraction/case/email-providers/{providerId}/folders":
    post:
      description: Fetches the folders of a mailbox linked to the extraction code's
        case live from the email provider, saves them on the mailbox and returns them.
        Returns 404 for a mailbox that is not linked to the caller's extraction code.
      operationId: ExtractionController_fetchEmailProviderFolders
      parameters:
      - name: providerId
        required: true
        in: path
        description: Id of the email provider (UserEmailDataProvider)
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: INBOX
                        name:
                          type: string
                          example: Inbox
                        isSystemFolder:
                          type: boolean
                          example: true
                        backgroundColor:
                          type: string
                          nullable: true
                        metadata:
                          type: object
                          description: The folder as the provider returned it
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a mailbox's folders
      tags:
      - extraction
  "/extraction/case/email-threads":
    get:
      description: Lists the imported email threads from the mailboxes linked to the
        caller's extraction code. Asking for a mailbox outside that set returns 403.
        The firm's tags are not included.
      operationId: ExtractionController_listEmailThreads
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: folders
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: domains
        required: false
        in: query
        description: Filter by participant domain(s) (part after @), any-of. Case-insensitive.
        schema:
          type: array
          items:
            type: string
      - name: excludeDomains
        required: false
        in: query
        description: Exclude items whose domain(s) match any of these (any-of). Case-insensitive.
          Must not overlap `domains`.
        schema:
          type: array
          items:
            type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: excludeTags
        required: false
        in: query
        description: Exclude items carrying any of these tag ids. Must not overlap
          `tags`.
        schema:
          type: array
          items:
            type: string
      - name: participants
        required: false
        in: query
        description: Filter by participant email address(es), any-of.
        schema:
          type: array
          items:
            type: string
      - name: excludeEmails
        required: false
        in: query
        description: Exclude items whose participant address matches any of these
          (any-of). Must not overlap `participants`.
        schema:
          type: array
          items:
            type: string
      - name: starred
        required: false
        in: query
        schema:
          type: boolean
      - name: unread
        required: false
        in: query
        schema:
          type: boolean
      - name: untagged
        required: false
        in: query
        description: When truthy (1/true), return only threads with no tags attached.
          Takes precedence over the `tags` filter.
        schema:
          type: boolean
      - name: startDate
        required: false
        in: query
        description: Only return threads whose `latestMessageReceivedDate` is at/after
          this instant (inclusive). Accepts an ISO-8601 date-time.
        schema:
          format: date-time
          example: '2024-01-01T00:00:00.000Z'
          type: string
      - name: endDate
        required: false
        in: query
        description: Only return threads whose `latestMessageReceivedDate` is at/before
          this instant (inclusive). Accepts an ISO-8601 date-time.
        schema:
          format: date-time
          example: '2024-12-31T23:59:59.999Z'
          type: string
      - name: sourceEmails
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: latestMessageReceivedDate
          type: string
          enum:
          - subject
          - latestMessageSentDate
          - latestMessageReceivedDate
      - name: includeLastMessage
        required: false
        in: query
        description: When truthy (1/true), populate lastMessageOrDraft (id/subject);
          otherwise it is returned as null. The message body is never included — use
          the thread `snippet` for previews, or fetch the body from the thread-emails
          endpoint.
        schema:
          type: boolean
      - name: includeHidden
        required: false
        in: query
        description: 'When true, include hidden threads (hiddenAt != null). Default
          false: hidden threads are excluded from the listing.'
        schema:
          default: false
          type: boolean
      - name: sharedStatus
        required: false
        in: query
        description: Opposing-council only. Narrows to threads in one sharing state;
          ignored for OL/OG callers.
        schema:
          type: string
          enum:
          - shared
          - pending
          - not_gated
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UserEmailDataThreadEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List email threads
      tags:
      - extraction
  "/extraction/case/email-threads/{threadId}/emails":
    get:
      description: Returns the emails of one thread, with their bodies. Returns 404
        unless the thread is in a mailbox linked to the caller's extraction code.
        The firm's tags are not included.
      operationId: ExtractionController_getThreadEmails
      parameters:
      - name: threadId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: folders
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: domains
        required: false
        in: query
        description: Filter by participant domain(s) (part after @), any-of. Case-insensitive.
        schema:
          type: array
          items:
            type: string
      - name: excludeDomains
        required: false
        in: query
        description: Exclude emails whose domain(s) match any of these (any-of). Case-insensitive.
          Must not overlap `domains`.
        schema:
          type: array
          items:
            type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: excludeTags
        required: false
        in: query
        description: Exclude items carrying any of these tag ids. Must not overlap
          `tags`.
        schema:
          type: array
          items:
            type: string
      - name: excludeEmails
        required: false
        in: query
        description: Exclude emails whose from/to/cc/bcc address matches any of these
          (any-of).
        schema:
          type: array
          items:
            type: string
      - name: starred
        required: false
        in: query
        schema:
          type: boolean
      - name: unread
        required: false
        in: query
        schema:
          type: boolean
      - name: untagged
        required: false
        in: query
        description: When truthy (1/true), return only emails with no tags attached.
          Takes precedence over the `tags` filter.
        schema:
          type: boolean
      - name: sortBy
        required: false
        in: query
        schema:
          default: date
          type: string
          enum:
          - date
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UserEmailDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List the emails in a thread
      tags:
      - extraction
  "/extraction/case/emails/search":
    get:
      description: Searches the emails in the mailboxes linked to the caller's extraction
        code. Bodies are not included. Asking for a mailbox outside that set returns
        403. The firm's tags are not included.
      operationId: ExtractionController_searchEmails
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Text search applied to all indexed fields (subject, body, snippet,
          from/to/cc/bcc). Supports the same AND / OR / NOT / -prefix, parentheses,
          "quoted phrases" and wildcards as the boolean query parser.
        schema:
          example: (discriminate OR racist) AND ("Larry Graham" OR Robinson)
          type: string
      - name: folders
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: domains
        required: false
        in: query
        description: Filter by participant domain(s) (part after @), any-of. Case-insensitive.
          Requires the `domains` field on the emailtext Atlas index.
        schema:
          type: array
          items:
            type: string
      - name: excludeDomains
        required: false
        in: query
        description: Exclude items whose domain(s) match any of these (any-of). Case-insensitive.
          Must not overlap `domains`.
        schema:
          type: array
          items:
            type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: excludeTags
        required: false
        in: query
        description: Exclude items carrying any of these tag ids. Must not overlap
          `tags`.
        schema:
          type: array
          items:
            type: string
      - name: untagged
        required: false
        in: query
        description: When truthy (1/true), return only emails with no tags attached.
          Takes precedence over the `tags` filter.
        schema:
          type: boolean
      - name: participants
        required: false
        in: query
        description: Filter by participant email address(es), any-of.
        schema:
          type: array
          items:
            type: string
      - name: excludeEmails
        required: false
        in: query
        description: Exclude items whose participant address matches any of these
          (any-of). Must not overlap `participants`.
        schema:
          type: array
          items:
            type: string
      - name: startDate
        required: false
        in: query
        description: Only return emails whose `date` is at/after this instant (inclusive).
          Accepts an ISO-8601 date-time.
        schema:
          format: date-time
          example: '2024-01-01T00:00:00.000Z'
          type: string
      - name: endDate
        required: false
        in: query
        description: Only return emails whose `date` is at/before this instant (inclusive).
          Accepts an ISO-8601 date-time.
        schema:
          format: date-time
          example: '2024-12-31T23:59:59.999Z'
          type: string
      - name: sourceEmails
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: date
          type: string
          enum:
          - date
          - subject
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UserEmailDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search emails
      tags:
      - extraction
  "/extraction/case/email-domains":
    get:
      description: Lists the domains in the from, to, cc and bcc addresses of the
        emails in the mailboxes linked to the caller's extraction code, with how many
        emails each appears in.
      operationId: ExtractionController_getEmailDomains
      parameters:
      - name: sourceEmails
        required: false
        in: query
        description: Restrict to these source mailboxes (provider emails). Omit to
          cover every mailbox in the case.
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/EmailDomainsResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List sender and recipient domains
      tags:
      - extraction
  "/extraction/case/emails/{emailId}/thread":
    get:
      description: Returns the thread an email belongs to and the ids of all its emails.
        Returns 404 unless the email is in a mailbox linked to the caller's extraction
        code. The firm's tags are not included.
      operationId: ExtractionController_getThreadByEmailId
      parameters:
      - name: emailId
        required: true
        in: path
        schema:
          type: string
      - name: includeLastMessage
        required: false
        in: query
        description: When truthy (1/true), populate lastMessageOrDraft (id/subject)
          on the thread; otherwise it is returned as null. The message body is never
          included — fetch it from the thread-emails endpoint.
        schema:
          type: boolean
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ThreadByEmailResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the thread of an email
      tags:
      - extraction
  "/extraction/case/data-status-tree":
    get:
      description: Returns the data-collection status of the extraction code's case
        as a tree, limited to data collected with the caller's extraction code.
      operationId: ExtractionController_fetchCaseDataStatusTree
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseDataStatusListTreeResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the collection status tree
      tags:
      - extraction
  "/extraction/case/data-status-v2":
    get:
      description: Returns the data-collection status of the extraction code's case
        grouped by data source, with item counts, limited to data collected with the
        caller's extraction code.
      operationId: ExtractionController_fetchCaseDataStatusV2
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CaseDataStatusV2ResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the collection status (v2)
      tags:
      - extraction
  "/extraction/financial-data/link/token/create":
    post:
      description: Creates a Plaid Link token for the caller's extraction code, used
        to open Plaid Link on the client. Returns 409 while the organization's Plaid
        onboarding is not complete.
      operationId: ExtractionController_createLinkToken
      parameters:
      - name: extraction-code
        in: header
        description: Extraction code associated with the case
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateLinkTokenDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LinkTokenResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a Plaid Link token
      tags:
      - extraction
  "/extraction/financial-data/item/public_token/exchange":
    post:
      description: Exchanges the public token from Plaid Link, saves the bank connection
        for the caller's extraction code, updates the case's data-source status and
        queues fetching the accounts. The Plaid access token is never returned. Returns
        409 while the organization's Plaid onboarding is not complete.
      operationId: ExtractionController_exchangePublicToken
      parameters:
      - name: extraction-code
        in: header
        description: Extraction code associated with the case
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExchangePublicTokenDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ExchangePublicTokenResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Connect a bank account through Plaid
      tags:
      - extraction
  "/extraction/ai-chat/import":
    post:
      description: Creates an import job in the extraction code's case for an uploaded
        AI chat export (Claude, ChatGPT or Gemini), marks the data source as pending
        and queues the import. Returns 400 if the file was already imported.
      operationId: ExtractionController_importAiChatData
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ImportAiChatDataDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AiChatImportJobEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Import an AI chat export
      tags:
      - extraction
  "/extraction/linkedin-data/{caseId}/import":
    post:
      description: 'Desktop plumbing: registers the uploaded LinkedIn export zip by
        its S3 key, creates an import job for it in the extraction code''s case (the
        caseId must be that case, otherwise 403), marks the data source as pending
        and queues the import. If the file was already imported, returns 400, or,
        when incremental re-import is on and nothing new would be added, `imported:
        false` with no job created (also 201).'
      operationId: ExtractionController_importLinkedInData
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ImportLinkedInDataExtractionDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    oneOf:
                    - "$ref": "#/components/schemas/LinkedInImportJobEntity"
                    - "$ref": "#/components/schemas/LinkedInImportSkippedResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Import a LinkedIn export (internal)
      tags:
      - extraction
      x-internal: true
  "/extraction/file/register":
    post:
      description: Creates a file record for a file already uploaded to S3, by its
        key, so it can then be imported (for example an AI chat export). If a record
        for that key already exists, it is returned instead. Returns 404 if the key
        is not in S3. Only keys under ai-chat/, linkedin-imports/ or case-files/<the
        caller's code>/ can be registered (403 otherwise).
      operationId: ExtractionController_registerUploadedFile
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fileKey:
                  type: string
                  description: S3 file key (path in bucket)
                type:
                  type: string
                  description: Override content type. If not provided, uses S3 metadata.
                  nullable: true
              required:
              - fileKey
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserFileEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Register an uploaded file
      tags:
      - extraction
  "/clients/fetch-client-list":
    get:
      description: 'Super admin only: lists desktop app installs, filterable by system
        id, extraction code and online status, with their extraction codes (and cases)
        and connected devices; skip/limit or page/perPage pagination.'
      operationId: ClientController_fetchClientList
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: systemId
        required: false
        in: query
        description: systemId
        schema:
          type: string
      - name: ecCode
        required: false
        in: query
        description: ecCode
        schema:
          type: string
      - name: isOnline
        required: false
        in: query
        description: 0 false, 1 true
        schema:
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DesktopClientEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List connected desktop apps (internal)
      tags:
      - clients
      x-internal: true
  "/clients/fetch-client-logs":
    post:
      description: 'Super admin only: asks an online desktop app to upload its app
        log, or a device log for `deviceId`/`deviceType`. Returns 404 for an unknown
        client and 400 when it is offline.'
      operationId: ClientController_fetchClientLogs
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/FetchClientLogDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Ask a desktop app for its logs (internal)
      tags:
      - clients
      x-internal: true
  "/clients/send-intercom-signal":
    post:
      description: 'Super admin only: tells every online desktop app signed in with
        the extraction code to open the given support conversation. Returns 404 when
        no app on that code is online.'
      operationId: ClientController_sendIntercomSignal
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SendIntercomSignalDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Open a support conversation in the desktop app (internal)
      tags:
      - clients
      x-internal: true
  "/email-data/{caseId}/create-oauth-url":
    post:
      description: 'Inbox-connect plumbing: returns the email provider''s OAuth sign-in
        URL for linking a mailbox to the case. The caller opens it and sends the returned
        code to add-email-provider.'
      operationId: EmailDataController_createOAuthUrl
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateOAuthUrlDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CreateOAuthUrlResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start linking a mailbox (internal)
      tags:
      - email-data
      x-internal: true
  "/email-data/{caseId}/add-email-provider":
    post:
      description: 'Inbox-connect plumbing: exchanges the OAuth code for a mailbox
        grant, links the mailbox to the case and returns it. Tokens are never returned.'
      operationId: EmailDataController_createEmailProvider
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateEmailProviderDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserEmailDataProviderEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Link an OAuth mailbox (internal)
      tags:
      - email-data
      x-internal: true
  "/email-data/{caseId}/add-imap-email-provider":
    post:
      description: 'Inbox-connect plumbing: connects to the IMAP server to check the
        host and credentials, links the mailbox to the case and returns it. The credentials
        are stored encrypted and never returned.'
      operationId: EmailDataController_createImapEmailProvider
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateImapEmailProviderDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserEmailDataProviderEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Link an IMAP mailbox (internal)
      tags:
      - email-data
      x-internal: true
  "/email-data/{caseId}/search-remote-emails":
    post:
      description: 'Inbox-connect plumbing: searches the emails in a linked OAuth
        mailbox at the provider, before anything is imported. Pass nextPageToken back
        as pageToken for the next page. An extraction-code user may only name a mailbox
        linked to their own code (404 otherwise).'
      operationId: EmailDataController_searchRemoteEmails
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SearchRemoteEmailsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RemoteEmailSearchResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search a linked mailbox's emails (internal)
      tags:
      - email-data
      x-internal: true
  "/email-data/{caseId}/fetch-folders-for-email-provider/{providerId}":
    post:
      description: 'Inbox-connect plumbing: fetches the folders of a linked mailbox
        from the provider, saves them on the mailbox and returns them. Folder ids
        can be passed as folderIds when starting an import. An extraction-code user
        may only name a mailbox linked to their own code (404 otherwise).'
      operationId: EmailDataController_fetchFoldersForEmailProvider
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: providerId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: INBOX
                        name:
                          type: string
                          example: Inbox
                        isSystemFolder:
                          type: boolean
                          example: true
                        backgroundColor:
                          type: string
                          nullable: true
                        metadata:
                          type: object
                          description: The folder as the provider returned it
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a linked mailbox's folders (internal)
      tags:
      - email-data
      x-internal: true
  "/email-data/{caseId}/search-remote-email-threads":
    post:
      description: 'Inbox-connect plumbing: searches the threads in a linked OAuth
        mailbox at the provider and marks which ones are already imported into the
        case. An extraction-code user may only name a mailbox linked to their own
        code (404 otherwise).'
      operationId: EmailDataController_searchRemoteEmailThreads
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SearchRemoteEmailsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RemoteEmailThreadSearchResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search a linked mailbox's threads (internal)
      tags:
      - email-data
      x-internal: true
  "/email-data/{caseId}/search-remote-imap-email-threads":
    post:
      description: 'Inbox-connect plumbing: searches the threads in a linked IMAP
        mailbox and marks which ones are already imported into the case. An extraction-code
        user may only name a mailbox linked to their own code (404 otherwise).'
      operationId: EmailDataController_searchRemoteImapEmailThreads
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SearchRemoteEmailsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RemoteEmailThreadSearchResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search an IMAP mailbox's threads (internal)
      tags:
      - email-data
      x-internal: true
  "/email-data/{caseId}/start-import-emails":
    post:
      description: 'Queues a job that imports emails from a linked mailbox into the
        case: chosen threads, the results of one or several searches, or everything
        (optionally limited to some folders). Returns the job; poll GET import-jobs/:jobId
        for progress. An extraction-code user may only name a mailbox linked to their
        own code (404 otherwise).'
      operationId: EmailDataController_startImportEmails
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/StartImportEmailsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserEmailImportJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start an email import
      tags:
      - email-data
  "/email-data/{caseId}/resume-import-job/{jobId}":
    post:
      description: 'Operator plumbing: re-queues a failed import job. It carries on
        after the emails already imported.'
      operationId: EmailDataController_resumeImportJob
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: jobId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Resume a failed import job (internal)
      tags:
      - email-data
      x-internal: true
  "/email-data/{caseId}/cancel-import-job/{jobId}":
    post:
      description: Stops an import job that has not finished. Emails already imported
        stay in the case.
      operationId: EmailDataController_cancelImportJob
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: jobId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Cancel an import job
      tags:
      - email-data
  "/email-data/{caseId}/import-jobs":
    get:
      description: Lists the email import jobs of a case, newest first by default,
        with their status and imported counts.
      operationId: EmailDataController_searchImportJobs
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: status
        required: false
        in: query
        description: Filter by job status
        schema:
          type: string
          enum:
          - not_started
          - in_progress
          - completed
          - failed
      - name: providerId
        required: false
        in: query
        description: Filter by email provider ID
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UserEmailImportJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List email import jobs
      tags:
      - email-data
  "/email-data/{caseId}/import-jobs/{jobId}":
    get:
      description: Returns one import job with its status and imported counts. Clients
        poll this while an import runs.
      operationId: EmailDataController_getImportJob
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: jobId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserEmailImportJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an email import job
      tags:
      - email-data
  "/email-data/{caseId}/email-providers":
    get:
      description: 'Inbox-connect plumbing: lists the mailboxes linked to the case.
        Tokens and IMAP credentials are never returned.'
      operationId: EmailDataController_searchEmailProviders
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/UserEmailDataProviderEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List linked mailboxes (internal)
      tags:
      - email-data
      x-internal: true
  "/local-emails/{caseId}/threads":
    get:
      description: Lists the imported email threads of a case with subject, participants,
        dates and tags, optionally filtered by mailbox, folder, domain, participant,
        tag or date. Hidden threads are left out unless includeHidden is set. On a
        case shared with opposing counsel, each side sees only the threads it may
        see.
      operationId: LocalEmailDataController_listThreads
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: folders
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: domains
        required: false
        in: query
        description: Filter by participant domain(s) (part after @), any-of. Case-insensitive.
        schema:
          type: array
          items:
            type: string
      - name: excludeDomains
        required: false
        in: query
        description: Exclude items whose domain(s) match any of these (any-of). Case-insensitive.
          Must not overlap `domains`.
        schema:
          type: array
          items:
            type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: excludeTags
        required: false
        in: query
        description: Exclude items carrying any of these tag ids. Must not overlap
          `tags`.
        schema:
          type: array
          items:
            type: string
      - name: participants
        required: false
        in: query
        description: Filter by participant email address(es), any-of.
        schema:
          type: array
          items:
            type: string
      - name: excludeEmails
        required: false
        in: query
        description: Exclude items whose participant address matches any of these
          (any-of). Must not overlap `participants`.
        schema:
          type: array
          items:
            type: string
      - name: starred
        required: false
        in: query
        schema:
          type: boolean
      - name: unread
        required: false
        in: query
        schema:
          type: boolean
      - name: untagged
        required: false
        in: query
        description: When truthy (1/true), return only threads with no tags attached.
          Takes precedence over the `tags` filter.
        schema:
          type: boolean
      - name: startDate
        required: false
        in: query
        description: Only return threads whose `latestMessageReceivedDate` is at/after
          this instant (inclusive). Accepts an ISO-8601 date-time.
        schema:
          format: date-time
          example: '2024-01-01T00:00:00.000Z'
          type: string
      - name: endDate
        required: false
        in: query
        description: Only return threads whose `latestMessageReceivedDate` is at/before
          this instant (inclusive). Accepts an ISO-8601 date-time.
        schema:
          format: date-time
          example: '2024-12-31T23:59:59.999Z'
          type: string
      - name: sourceEmails
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: latestMessageReceivedDate
          type: string
          enum:
          - subject
          - latestMessageSentDate
          - latestMessageReceivedDate
      - name: includeLastMessage
        required: false
        in: query
        description: When truthy (1/true), populate lastMessageOrDraft (id/subject);
          otherwise it is returned as null. The message body is never included — use
          the thread `snippet` for previews, or fetch the body from the thread-emails
          endpoint.
        schema:
          type: boolean
      - name: includeHidden
        required: false
        in: query
        description: 'When true, include hidden threads (hiddenAt != null). Default
          false: hidden threads are excluded from the listing.'
        schema:
          default: false
          type: boolean
      - name: sharedStatus
        required: false
        in: query
        description: Opposing-council only. Narrows to threads in one sharing state;
          ignored for OL/OG callers.
        schema:
          type: string
          enum:
          - shared
          - pending
          - not_gated
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UserEmailDataThreadEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List email threads
      tags:
      - local-emails
    delete:
      description: Permanently deletes threads from the case, with their emails and
        attachments, and updates the case's data counts.
      operationId: LocalEmailMutationsController_deleteEmailThreads
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeleteEmailThreadsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Delete email threads
      tags:
      - local-emails
  "/local-emails/{caseId}/threads/{threadId}/emails":
    get:
      description: Returns the emails of one thread with their bodies, recipients
        and attachments.
      operationId: LocalEmailDataController_getThreadEmails
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: threadId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: folders
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: domains
        required: false
        in: query
        description: Filter by participant domain(s) (part after @), any-of. Case-insensitive.
        schema:
          type: array
          items:
            type: string
      - name: excludeDomains
        required: false
        in: query
        description: Exclude emails whose domain(s) match any of these (any-of). Case-insensitive.
          Must not overlap `domains`.
        schema:
          type: array
          items:
            type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: excludeTags
        required: false
        in: query
        description: Exclude items carrying any of these tag ids. Must not overlap
          `tags`.
        schema:
          type: array
          items:
            type: string
      - name: excludeEmails
        required: false
        in: query
        description: Exclude emails whose from/to/cc/bcc address matches any of these
          (any-of).
        schema:
          type: array
          items:
            type: string
      - name: starred
        required: false
        in: query
        schema:
          type: boolean
      - name: unread
        required: false
        in: query
        schema:
          type: boolean
      - name: untagged
        required: false
        in: query
        description: When truthy (1/true), return only emails with no tags attached.
          Takes precedence over the `tags` filter.
        schema:
          type: boolean
      - name: sortBy
        required: false
        in: query
        schema:
          default: date
          type: string
          enum:
          - date
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UserEmailDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List the emails in a thread
      tags:
      - local-emails
  "/local-emails/{caseId}/emails/search":
    get:
      description: Finds emails in a case by text, folder, domain, participant, tag
        or date. Bodies are not included; fetch the thread's emails for those.
      operationId: LocalEmailDataController_searchEmails
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Text search applied to all indexed fields (subject, body, snippet,
          from/to/cc/bcc). Supports the same AND / OR / NOT / -prefix, parentheses,
          "quoted phrases" and wildcards as the boolean query parser.
        schema:
          example: (discriminate OR racist) AND ("Larry Graham" OR Robinson)
          type: string
      - name: folders
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: domains
        required: false
        in: query
        description: Filter by participant domain(s) (part after @), any-of. Case-insensitive.
          Requires the `domains` field on the emailtext Atlas index.
        schema:
          type: array
          items:
            type: string
      - name: excludeDomains
        required: false
        in: query
        description: Exclude items whose domain(s) match any of these (any-of). Case-insensitive.
          Must not overlap `domains`.
        schema:
          type: array
          items:
            type: string
      - name: tags
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: excludeTags
        required: false
        in: query
        description: Exclude items carrying any of these tag ids. Must not overlap
          `tags`.
        schema:
          type: array
          items:
            type: string
      - name: untagged
        required: false
        in: query
        description: When truthy (1/true), return only emails with no tags attached.
          Takes precedence over the `tags` filter.
        schema:
          type: boolean
      - name: participants
        required: false
        in: query
        description: Filter by participant email address(es), any-of.
        schema:
          type: array
          items:
            type: string
      - name: excludeEmails
        required: false
        in: query
        description: Exclude items whose participant address matches any of these
          (any-of). Must not overlap `participants`.
        schema:
          type: array
          items:
            type: string
      - name: startDate
        required: false
        in: query
        description: Only return emails whose `date` is at/after this instant (inclusive).
          Accepts an ISO-8601 date-time.
        schema:
          format: date-time
          example: '2024-01-01T00:00:00.000Z'
          type: string
      - name: endDate
        required: false
        in: query
        description: Only return emails whose `date` is at/before this instant (inclusive).
          Accepts an ISO-8601 date-time.
        schema:
          format: date-time
          example: '2024-12-31T23:59:59.999Z'
          type: string
      - name: sourceEmails
        required: false
        in: query
        schema:
          type: array
          items:
            type: string
      - name: sortBy
        required: false
        in: query
        schema:
          default: date
          type: string
          enum:
          - date
          - subject
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/UserEmailDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search emails
      tags:
      - local-emails
  "/local-emails/{caseId}/tags-count":
    get:
      description: Returns how many emails in the case carry each tag, for the given
        filters. On a case shared with opposing counsel, each side counts only its
        own tags.
      operationId: LocalEmailDataController_getEmailTagsCount
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: sourceEmail
        required: true
        in: query
        description: Source mailbox email to scope the counts to (the provider email).
        schema:
          type: string
      - name: tagOrigin
        required: false
        in: query
        description: Filter tags by how they were created; omit for all origins.
        schema:
          type: string
          enum:
          - manual
          - auto-import
      - name: sortBy
        required: false
        in: query
        description: Sort by email count or alphabetically by tag name. Default order
          is desc for count and asc for name; override with sortOrder.
        schema:
          default: count
          type: string
          enum:
          - count
          - name
      - name: searchText
        required: false
        in: query
        description: Case-insensitive substring match on tag name.
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/EmailTagsCountResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Count emails per tag
      tags:
      - local-emails
  "/local-emails/{caseId}/search-counts":
    post:
      description: For each boolean search query, and each email address, returns
        how many emails in the case match (only in one mailbox when providerId is
        given; on a case shared with opposing counsel, only in threads the caller
        may see). A malformed query gets an error on its own row instead of failing
        the whole request.
      operationId: LocalEmailDataController_getSearchCounts
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/EmailSearchCountsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/EmailSearchCountsResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Count matches for several searches
      tags:
      - local-emails
  "/local-emails/{caseId}/domains":
    get:
      description: Lists the email domains that appear in the case's emails, with
        how many emails each appears in.
      operationId: LocalEmailDataController_getEmailDomains
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: sourceEmails
        required: false
        in: query
        description: Restrict to these source mailboxes (provider emails). Omit to
          cover every mailbox in the case.
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/EmailDomainsResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List sender and recipient domains
      tags:
      - local-emails
  "/local-emails/{caseId}/domains/{domain}/addresses":
    get:
      description: Lists the email addresses of one domain that appear in the case's
        emails, with how many emails each appears in.
      operationId: LocalEmailDataController_getEmailDomainAddresses
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: domain
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sourceEmails
        required: false
        in: query
        description: Restrict to these source mailboxes (provider emails). Omit to
          cover every mailbox in the case.
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/EmailDomainAddressesResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List addresses in a domain
      tags:
      - local-emails
  "/local-emails/{caseId}/emails/{emailId}/thread":
    get:
      description: Returns the thread an email belongs to and the ids of all its emails,
        newest first. An email the caller may not see is reported as not found.
      operationId: LocalEmailDataController_getThreadByEmailId
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: emailId
        required: true
        in: path
        schema:
          type: string
      - name: includeLastMessage
        required: false
        in: query
        description: When truthy (1/true), populate lastMessageOrDraft (id/subject)
          on the thread; otherwise it is returned as null. The message body is never
          included — fetch it from the thread-emails endpoint.
        schema:
          type: boolean
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ThreadByEmailResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the thread of an email
      tags:
      - local-emails
  "/local-emails/{caseId}/emails/tags":
    post:
      description: Adds tags to emails chosen by id, or to every email matching a
        search. By id, the tags are added right away; by search, a background job
        is queued and its id returned (see bulk-tag-jobs).
      operationId: LocalEmailMutationsController_bulkAddEmailTags
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BulkTagEmailsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BulkTagResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag many emails
      tags:
      - local-emails
  "/local-emails/{caseId}/threads/tags":
    post:
      description: Adds tags to threads chosen by id, or to every thread matching
        a search, optionally tagging each thread's emails too. By search, a background
        job is queued and its id returned.
      operationId: LocalEmailMutationsController_bulkAddThreadTags
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BulkTagThreadsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BulkTagResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag many threads
      tags:
      - local-emails
  "/local-emails/{caseId}/threads/visibility":
    post:
      description: Hides threads from the thread list, or brings hidden threads back.
        Nothing is deleted.
      operationId: LocalEmailMutationsController_setThreadVisibility
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BulkThreadVisibilityDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BulkThreadVisibilityResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Hide or unhide threads
      tags:
      - local-emails
  "/local-emails/{caseId}/bulk-tag-jobs":
    get:
      description: Lists the background tagging jobs of a case (the ones started by
        tagging a search), newest first by default.
      operationId: LocalEmailMutationsController_listBulkTagJobs
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: status
        required: false
        in: query
        schema:
          type: string
          enum:
          - queued
          - in_progress
          - completed
          - failed
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          "$ref": "#/components/schemas/BulkTagJobEntity"
                      total:
                        type: number
                        example: 3
                      skip:
                        type: number
                        example: 0
                      limit:
                        type: number
                        example: 20
                      page:
                        type: number
                        example: 1
                      perPage:
                        type: number
                        example: 20
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List bulk-tag jobs
      tags:
      - local-emails
  "/local-emails/{caseId}/bulk-tag-jobs/{jobId}":
    get:
      description: Returns one background tagging job with its status and progress.
      operationId: LocalEmailMutationsController_getBulkTagJob
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: jobId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BulkTagJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a bulk-tag job
      tags:
      - local-emails
  "/local-emails/{userEmailDataId}/tags":
    patch:
      description: Replaces the email's tags with the given list (an empty list removes
        them) and returns the email. On a case shared with opposing counsel, the response
        shows only the caller's own tags.
      operationId: LocalEmailMutationsController_updateUserEmailDataTags
      parameters:
      - name: userEmailDataId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateUserEmailDataTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserEmailDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Set an email's tags
      tags:
      - local-emails
  "/local-emails/{caseId}/thread/{threadId}/tags":
    patch:
      description: Replaces the thread's tags with the given list and returns the
        thread. On a case shared with opposing counsel, the response shows only the
        caller's own tags.
      operationId: LocalEmailMutationsController_updateEmailThreadTags
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: threadId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateEmailThreadTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserEmailDataThreadEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Set a thread's tags
      tags:
      - local-emails
  "/discord/{caseId}":
    get:
      description: Returns one page of Discord profiles linked to the case, excluding
        archived ones; search by username, global name or email, or filter by extraction
        code. On a case shared with opposing counsel, profiles the caller may not
        see are excluded; ocSharedStatus (the profile's sharing status) is filled
        in for opposing counsel, who can also filter by it, and is null for everyone
        else.
      operationId: DiscordDataController_searchProfiles
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: extractionCode
        required: false
        in: query
        description: Filter by extraction code
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        description: Search username, globalName, email
        schema:
          type: string
      - name: sharedStatus
        required: false
        in: query
        description: Opposing-council only. Narrows to providers in one sharing state;
          ignored for OL/OG callers.
        schema:
          type: string
          enum:
          - shared
          - pending
          - not_gated
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DiscordUserProviderEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Discord profiles
      tags:
      - discord
    delete:
      description: Archives (soft-deletes) a Discord profile linked to the case so
        it no longer appears in listings, and resets the case's Discord data source
        status to pending. Returns 404 if the profile is not linked or already archived.
        Not available to opposing counsel.
      operationId: DiscordDataController_archiveDiscordProfile
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeleteDiscordProfileDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Archive a Discord profile
      tags:
      - discord
  "/discord/{caseId}/profile/{discordId}":
    get:
      description: Returns one Discord profile linked to the case; returns 404 if
        it is archived or not linked. On a case shared with opposing counsel, a profile
        the caller may not see returns 404.
      operationId: DiscordDataController_getProfile
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: discordId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DiscordUserProviderEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a Discord profile
      tags:
      - discord
  "/discord/{caseId}/profile/{discordId}/conversations":
    get:
      description: Returns one page of Discord conversations for a profile in the
        case, with message counts and last-message details; search by name, display
        name or channel id. On a case shared with opposing counsel, a profile the
        caller may not see returns an empty page.
      operationId: DiscordDataController_searchConversations
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: discordId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search name, displayName, channelId
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DiscordConversationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Discord conversations
      tags:
      - discord
  "/discord/{caseId}/profile/{discordId}/conversation/{conversationId}/messages":
    get:
      description: Returns one page of messages in a Discord conversation for a profile,
        sorted by date; searchText filters by message text. On a case shared with
        opposing counsel, a profile the caller may not see returns an empty page.
        Returns 404 when the profile is not linked to the case.
      operationId: DiscordDataController_searchMessages
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: discordId
        required: true
        in: path
        schema:
          type: string
      - name: conversationId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search message text
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DiscordMessageEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Discord messages
      tags:
      - discord
  "/discord/{caseId}/import":
    get:
      description: Returns one page of Discord import jobs for the case, optionally
        filtered by status.
      operationId: DiscordDataController_searchImportJobs
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: status
        required: false
        in: query
        description: Filter by job status
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DiscordImportJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Discord import jobs
      tags:
      - discord
    post:
      description: Creates an import job for an uploaded Discord data export file,
        marks the case's Discord data source as pending, and queues the import. Returns
        400 if the file was already imported successfully, and 404 if the file or
        extraction code is not found. Not available to opposing counsel.
      operationId: DiscordDataController_importDiscordData
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ImportDiscordDataDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DiscordImportJobEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start a Discord import (internal)
      tags:
      - discord
      x-internal: true
  "/discord/{caseId}/import/{importId}":
    get:
      description: Returns one Discord import job for the case, including its status
        and error message. Returns 404 if the job does not belong to the case.
      operationId: DiscordDataController_getImportJob
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: importId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DiscordImportJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a Discord import job
      tags:
      - discord
  "/discord/{caseId}/profile/{discordId}/activity":
    get:
      description: Returns one page of activity events parsed from the Activity folder
        of a profile's Discord export; filter by event source, or search by event
        type. On a case shared with opposing counsel, a profile the caller may not
        see returns an empty page. Returns 404 when the profile is not linked to the
        case.
      operationId: DiscordDataController_searchActivities
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: discordId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search eventType
        schema:
          type: string
      - name: eventSource
        required: false
        in: query
        description: Filter by event source (analytics, modeling, reporting, tns)
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DiscordActivityEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Discord activity events
      tags:
      - discord
  "/discord/{caseId}/profile/{discordId}/support-tickets":
    get:
      description: Returns one page of Discord support tickets for a profile in the
        case, with their comments; search by subject or comment text. On a case shared
        with opposing counsel, a profile the caller may not see returns an empty page.
      operationId: DiscordDataController_searchSupportTickets
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: discordId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search subject, comments.comment
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/DiscordSupportTicketEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Discord support tickets
      tags:
      - discord
  "/reddit/{caseId}":
    get:
      description: Returns one page of Reddit profiles linked to the case, excluding
        archived ones; search by Reddit user id or email, or filter by extraction
        code. On a case shared with opposing counsel, profiles the caller may not
        see are excluded; ocSharedStatus (the profile's sharing status) is filled
        in for opposing counsel, who can also filter by it, and is null for everyone
        else.
      operationId: RedditDataController_searchProfiles
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: extractionCode
        required: false
        in: query
        description: Filter by extraction code
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        description: Search username, email
        schema:
          type: string
      - name: sharedStatus
        required: false
        in: query
        description: Opposing-council only. Narrows to providers in one sharing state;
          ignored for OL/OG callers.
        schema:
          type: string
          enum:
          - shared
          - pending
          - not_gated
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/RedditUserProviderEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Reddit profiles
      tags:
      - reddit
    delete:
      description: Archives (soft-deletes) a Reddit profile linked to the case so
        it no longer appears in listings, and resets the case's Reddit data source
        status to pending. Returns 404 if the profile is not linked or already archived.
        Not available to opposing counsel.
      operationId: RedditDataController_archiveRedditProfile
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeleteRedditProfileDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Archive a Reddit profile
      tags:
      - reddit
  "/reddit/{caseId}/profile/{redditId}":
    get:
      description: Returns one Reddit profile linked to the case; returns 404 if it
        is archived or not linked. On a case shared with opposing counsel, a profile
        the caller may not see returns 404.
      operationId: RedditDataController_getProfile
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: redditId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RedditUserProviderEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a Reddit profile
      tags:
      - reddit
  "/reddit/{caseId}/profile/{redditId}/posts":
    get:
      description: Returns one page of posts made by a Reddit profile in the case,
        sorted by date; search by title, body or subreddit. On a case shared with
        opposing counsel, a profile the caller may not see returns an empty page.
      operationId: RedditDataController_searchPosts
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: redditId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search title, body, subreddit
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/RedditPostEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Reddit posts
      tags:
      - reddit
  "/reddit/{caseId}/profile/{redditId}/comments":
    get:
      description: Returns one page of comments made by a Reddit profile in the case,
        sorted by date; search by body or subreddit. On a case shared with opposing
        counsel, a profile the caller may not see returns an empty page.
      operationId: RedditDataController_searchComments
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: redditId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search body, subreddit
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/RedditCommentEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Reddit comments
      tags:
      - reddit
  "/reddit/{caseId}/profile/{redditId}/messages":
    get:
      description: Returns one page of private messages for a Reddit profile in the
        case, sorted by date; search by subject, body, sender or recipient. On a case
        shared with opposing counsel, a profile the caller may not see returns an
        empty page.
      operationId: RedditDataController_searchMessages
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: redditId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search subject, body, from, to
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/RedditMessageEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Reddit private messages
      tags:
      - reddit
  "/reddit/{caseId}/profile/{redditId}/chat-messages":
    get:
      description: Returns one page of Reddit chat messages for a profile in the case,
        sorted by when they were sent; search by message text or username. On a case
        shared with opposing counsel, a profile the caller may not see returns an
        empty page.
      operationId: RedditDataController_searchChatMessages
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: redditId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search message, username
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/RedditChatMessageEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Reddit chat messages
      tags:
      - reddit
  "/reddit/{caseId}/profile/{redditId}/votes":
    get:
      description: Returns one page of upvotes and downvotes cast by a Reddit profile;
        filter by vote type (post or comment). On a case shared with opposing counsel,
        a profile the caller may not see returns an empty page. Returns 404 when the
        profile is not linked to the case.
      operationId: RedditDataController_searchVotes
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: redditId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: voteType
        required: false
        in: query
        description: 'Filter by vote type: comment or post'
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/RedditVoteEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Reddit votes
      tags:
      - reddit
  "/reddit/{caseId}/import":
    get:
      description: Returns one page of Reddit import jobs for the case, optionally
        filtered by status.
      operationId: RedditDataController_searchImportJobs
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: status
        required: false
        in: query
        description: Filter by job status
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/RedditImportJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List Reddit import jobs
      tags:
      - reddit
    post:
      description: Creates an import job for an uploaded Reddit data export file,
        marks the case's Reddit data source as pending, and queues the import. Returns
        400 if the file was already imported successfully, and 404 if the file or
        extraction code is not found. Not available to opposing counsel.
      operationId: RedditDataController_importRedditData
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ImportRedditDataDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RedditImportJobEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start a Reddit import (internal)
      tags:
      - reddit
      x-internal: true
  "/reddit/{caseId}/import/{importId}":
    get:
      description: Returns one Reddit import job for the case, including its status
        and error message. Returns 404 if the job does not belong to the case.
      operationId: RedditDataController_getImportJob
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: importId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RedditImportJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a Reddit import job
      tags:
      - reddit
  "/linkedin/{caseId}":
    get:
      description: Returns one page of LinkedIn profiles linked to the case, excluding
        archived ones; search by first name, last name, email or headline, or filter
        by extraction code. On a case shared with opposing counsel, profiles the caller
        may not see are excluded; ocSharedStatus (the profile's sharing status) is
        filled in for opposing counsel, who can also filter by it, and is null for
        everyone else.
      operationId: LinkedInDataController_searchProfiles
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search firstName, lastName, email, headline
        schema:
          type: string
      - name: extractionCode
        required: false
        in: query
        description: Filter by extraction code
        schema:
          type: string
      - name: sharedStatus
        required: false
        in: query
        description: Opposing-council only. Narrows to providers in one sharing state;
          ignored for OL/OG callers.
        schema:
          type: string
          enum:
          - shared
          - pending
          - not_gated
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInUserProviderEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn profiles
      tags:
      - linkedin
  "/linkedin/{caseId}/import-jobs":
    get:
      description: Returns one page of LinkedIn import jobs for the case, optionally
        filtered by status.
      operationId: LinkedInDataController_searchImportJobs
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: status
        required: false
        in: query
        description: Filter by job status
        schema:
          type: string
          enum:
          - not_started
          - in_progress
          - completed
          - failed
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInImportJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn import jobs
      tags:
      - linkedin
  "/linkedin/{caseId}/import-jobs/coverage":
    get:
      description: 'Extraction plumbing: reports which data types and conversations
        of an uploaded LinkedIn export file have already been imported into this case,
        combined across its completed import jobs. Returns 404 if the file is not
        found.'
      operationId: LinkedInDataController_getImportCoverage
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: fileId
        required: true
        in: query
        description: UserFile ID of the uploaded zip to report coverage for
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LinkedInImportCoverageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get LinkedIn import coverage for a file (internal)
      tags:
      - linkedin
      x-internal: true
  "/linkedin/{caseId}/import-jobs/{importJobId}":
    get:
      description: Returns one LinkedIn import job for the case, including its status,
        selected data types and error message. Returns 404 if the job does not belong
        to the case.
      operationId: LinkedInDataController_getImportJob
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: importJobId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LinkedInImportJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a LinkedIn import job
      tags:
      - linkedin
  "/linkedin/{caseId}/import":
    post:
      description: Creates an import job for an uploaded LinkedIn data export file
        (optionally limited to some data types or conversations), marks the case's
        LinkedIn data source as pending, and queues the import (201). If the file
        was already imported into this case, the request either fails with 400 or,
        when incremental re-import is enabled and it would add nothing new, returns
        200 with imported set to false and no job created. Not available to opposing
        counsel.
      operationId: LinkedInDataController_importLinkedInData
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ImportLinkedInDataDto"
      responses:
        '200':
          description: The selected data has already been imported; no job was created
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LinkedInImportSkippedResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
        '201':
          description: Import job created and queued
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LinkedInImportJobEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start a LinkedIn import (internal)
      tags:
      - linkedin
      x-internal: true
  "/linkedin/{caseId}/{linkedInId}/export":
    post:
      description: Creates a PDF export job for one type of LinkedIn data (conversations,
        connections, job applications and so on) from a profile in the case, queues
        it, and returns the job id; poll the export status route for the result. Returns
        404 if the profile is not linked to the case. On a case shared with opposing
        counsel, a profile the caller may not see also returns 404.
      operationId: LinkedInDataController_exportLinkedInData
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportLinkedInDataDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - jobId
                    properties:
                      jobId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Request a LinkedIn PDF export
      tags:
      - linkedin
  "/linkedin/{caseId}/exports":
    get:
      description: Returns every LinkedIn export record for the case, newest first,
        with the generated file (once finished) and the requesting user. On a case
        shared with opposing counsel, exports of a profile the caller may not see
        are excluded.
      operationId: LinkedInDataController_listExports
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      description: A stored LinkedIn export record. Every stored field
                        of the record is returned; the common ones are listed here.
                      properties:
                        source:
                          type: string
                          example: linkedin
                        type:
                          type: string
                          example: pdf
                        status:
                          type: string
                          enum:
                          - not-started
                          - inprogress
                          - completed
                          - failed
                          - cancelled
                        sortBy:
                          type: string
                          enum:
                          - old-to-new
                          - new-to-old
                        exportConfig:
                          type: array
                          items:
                            type: string
                        metadata:
                          type: object
                          properties:
                            linkedInProviderId:
                              type: string
                            linkedInExportType:
                              type: string
                              enum:
                              - conversations
                              - connections
                              - job-applications
                              - rich-media
                              - saved-jobs
                              - saved-job-alerts
                              - saved-items
                            custodianName:
                              type: string
                              nullable: true
                            conversationIds:
                              type: array
                              items:
                                type: string
                              nullable: true
                        file:
                          type: object
                          nullable: true
                          description: The generated PDF once the export has finished
                            (public file fields only).
                          properties:
                            name:
                              type: string
                            mimeType:
                              type: string
                            size:
                              type: number
                        exportedBy:
                          type: object
                          description: The user who requested the export.
                          properties:
                            name:
                              type: string
                            email:
                              type: string
                        errorMessage:
                          type: string
                          nullable: true
                        createdAt:
                          type: string
                          format: date-time
                        updatedAt:
                          type: string
                          format: date-time
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn exports
      tags:
      - linkedin
  "/linkedin/{caseId}/exports/{jobId}":
    get:
      description: Returns one LinkedIn export record for the case with its status,
        the generated file once finished, and the requesting user. Returns 404 if
        the export does not belong to the case. On a case shared with opposing counsel,
        an export of a profile the caller may not see also returns 404.
      operationId: LinkedInDataController_getExportStatus
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: jobId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: A stored LinkedIn export record. Every stored field
                      of the record is returned; the common ones are listed here.
                    properties:
                      source:
                        type: string
                        example: linkedin
                      type:
                        type: string
                        example: pdf
                      status:
                        type: string
                        enum:
                        - not-started
                        - inprogress
                        - completed
                        - failed
                        - cancelled
                      sortBy:
                        type: string
                        enum:
                        - old-to-new
                        - new-to-old
                      exportConfig:
                        type: array
                        items:
                          type: string
                      metadata:
                        type: object
                        properties:
                          linkedInProviderId:
                            type: string
                          linkedInExportType:
                            type: string
                            enum:
                            - conversations
                            - connections
                            - job-applications
                            - rich-media
                            - saved-jobs
                            - saved-job-alerts
                            - saved-items
                          custodianName:
                            type: string
                            nullable: true
                          conversationIds:
                            type: array
                            items:
                              type: string
                            nullable: true
                      file:
                        type: object
                        nullable: true
                        description: The generated PDF once the export has finished
                          (public file fields only).
                        properties:
                          name:
                            type: string
                          mimeType:
                            type: string
                          size:
                            type: number
                      exportedBy:
                        type: object
                        description: The user who requested the export.
                        properties:
                          name:
                            type: string
                          email:
                            type: string
                      errorMessage:
                        type: string
                        nullable: true
                      createdAt:
                        type: string
                        format: date-time
                      updatedAt:
                        type: string
                        format: date-time
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a LinkedIn export
      tags:
      - linkedin
  "/linkedin/{caseId}/search-messages":
    get:
      description: Searches message text and subjects across every LinkedIn conversation
        in the case and returns one page of matches, newest first, each with its conversation
        title and participant names; optionally limit to conversations carrying all
        of the given tags. On a case shared with opposing counsel, messages from profiles
        the caller may not see are excluded.
      operationId: LinkedInDataController_searchMessageContent
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: true
        in: query
        description: Search text for message content and subject
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Filter by conversation tag IDs (comma-separated)
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInMessageSearchResultEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search LinkedIn messages in a case
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}":
    get:
      description: Returns one LinkedIn profile linked to the case; returns 404 if
        it is archived or not linked. On a case shared with opposing counsel, a profile
        the caller may not see returns 404.
      operationId: LinkedInDataController_getProfile
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LinkedInUserProviderEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a LinkedIn profile
      tags:
      - linkedin
    delete:
      description: With action archive, soft-deletes the LinkedIn profile so it no
        longer appears in listings; with action delete, permanently deletes the profile
        and all of its imported LinkedIn data (conversations, messages, connections
        and the rest). Either way the case's LinkedIn data source status is reset
        to pending. Returns 404 if the profile is not linked to the case. Not available
        to opposing counsel.
      operationId: LinkedInDataController_archiveOrDeleteProfile
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeleteLinkedInProfileDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Archive or delete a LinkedIn profile
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/conversations":
    get:
      description: Returns one page of LinkedIn message conversations for a profile
        in the case, with participants and attachedTags; search by title or participant
        name, or filter to conversations carrying all of the given tags. On a case
        shared with opposing counsel, a profile the caller may not see returns an
        empty page. attachedTags leave out tags the other side added by hand.
      operationId: LinkedInDataController_searchConversations
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search title, participantNames
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Filter by tag IDs
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInConversationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn conversations
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/conversations/{conversationId}":
    get:
      description: Returns one LinkedIn conversation for a profile in the case, with
        participants and attachedTags. Returns 404 if the conversation does not belong
        to the profile and case. On a case shared with opposing counsel, a profile
        the caller may not see returns 404. attachedTags leave out tags the other
        side added by hand.
      operationId: LinkedInDataController_getConversation
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      - name: conversationId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LinkedInConversationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a LinkedIn conversation
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/conversations/{conversationId}/messages":
    get:
      description: Returns one page of messages in a LinkedIn conversation, sorted
        by date; searchText filters by message text. Returns 404 if the conversation
        does not belong to the profile and case. On a case shared with opposing counsel,
        a profile the caller may not see returns an empty page.
      operationId: LinkedInDataController_searchMessages
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      - name: conversationId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search message contentText
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInMessageEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn messages
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/connections":
    get:
      description: Returns one page of a profile's LinkedIn connections in the case,
        with attachedTags; search by name, company or position, or filter to connections
        carrying all of the given tags. On a case shared with opposing counsel, a
        profile the caller may not see returns an empty page. attachedTags leave out
        tags the other side added by hand.
      operationId: LinkedInDataController_searchConnections
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search firstName, lastName, company, position
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Filter by tag IDs
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInConnectionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn connections
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/comments":
    get:
      description: Returns one page of LinkedIn comments from the profile's export,
        sorted by date; search by comment text or post link, or filter by tags. On
        a case shared with opposing counsel, a profile the caller may not see returns
        an empty page.
      operationId: LinkedInDataController_searchComments
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search message, postLink
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Filter by tag IDs
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInCommentEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn comments
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/rich-media":
    get:
      description: Returns one page of rich media entries (media type, link and description)
        from the profile's LinkedIn export, sorted by date; search by description,
        or filter by tags. On a case shared with opposing counsel, a profile the caller
        may not see returns an empty page.
      operationId: LinkedInDataController_searchRichMedia
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search description
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Filter by tag IDs
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInRichMediaEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn rich media
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/saved-jobs":
    get:
      description: Returns one page of jobs the profile saved on LinkedIn, sorted
        by saved date; search by job title or company, or filter by tags. On a case
        shared with opposing counsel, a profile the caller may not see returns an
        empty page.
      operationId: LinkedInDataController_searchSavedJobs
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search jobTitle, companyName
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Filter by tag IDs
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInSavedJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn saved jobs
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/saved-items":
    get:
      description: Returns one page of items the profile saved on LinkedIn, sorted
        by when they were saved; search by URL. On a case shared with opposing counsel,
        a profile the caller may not see returns an empty page.
      operationId: LinkedInDataController_searchSavedItems
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search url
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInSavedItemEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn saved items
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/job-applications":
    get:
      description: Returns one page of the profile's LinkedIn job applications, sorted
        by application date; search by job title or company, or filter by tags. On
        a case shared with opposing counsel, a profile the caller may not see returns
        an empty page.
      operationId: LinkedInDataController_searchJobApplications
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search jobTitle, companyName
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Filter by tag IDs
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInJobApplicationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn job applications
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/saved-job-alerts":
    get:
      description: Returns all of the profile's saved LinkedIn job alerts in the case,
        newest first, in a single unpaginated list. On a case shared with opposing
        counsel, a profile the caller may not see returns an empty list.
      operationId: LinkedInDataController_searchSavedJobAlerts
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/LinkedInSavedJobAlertEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List LinkedIn saved job alerts
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/conversations/{conversationId}/tags":
    patch:
      description: Adds the given tag to a LinkedIn conversation, or removes it if
        it is already applied, and returns the updated conversation. Returns 404 if
        the conversation does not belong to the profile and case. In the result, attachedTags
        leave out tags the other side added by hand.
      operationId: LinkedInDataController_updateConversationTags
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      - name: conversationId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateLinkedInTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LinkedInConversationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Toggle a tag on a LinkedIn conversation
      tags:
      - linkedin
  "/linkedin/{caseId}/{linkedInId}/connections/{connectionId}/tags":
    patch:
      description: Adds the given tag to a LinkedIn connection, or removes it if it
        is already applied, and returns the updated connection. Returns 404 if the
        connection does not belong to the profile and case. In the result, attachedTags
        leave out tags the other side added by hand.
      operationId: LinkedInDataController_updateConnectionTags
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: linkedInId
        required: true
        in: path
        schema:
          type: string
      - name: connectionId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateLinkedInTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LinkedInConnectionEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Toggle a tag on a LinkedIn connection
      tags:
      - linkedin
  "/ai-chat/{caseId}":
    get:
      description: Returns one page of AI chat profiles (ChatGPT, Claude or Gemini
        accounts) linked to the case, excluding archived ones; search by display name,
        email or account id, or filter by extraction code. On a case shared with opposing
        counsel, profiles the caller may not see are excluded; ocSharedStatus (the
        profile's sharing status) is filled in for opposing counsel, who can also
        filter by it, and is null for everyone else.
      operationId: AiChatDataController_searchProfiles
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: extractionCode
        required: false
        in: query
        description: Filter by extraction code
        schema:
          type: string
      - name: searchText
        required: false
        in: query
        description: Search displayName, email
        schema:
          type: string
      - name: sharedStatus
        required: false
        in: query
        description: Opposing-council only. Narrows to providers in one sharing state;
          ignored for OL/OG callers.
        schema:
          type: string
          enum:
          - shared
          - pending
          - not_gated
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/AiChatProviderEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List AI chat profiles
      tags:
      - ai-chat
    delete:
      description: Archives (soft-deletes) an AI chat profile linked to the case so
        it no longer appears in listings, and resets the case's data source status
        for that platform to pending. Returns 404 if the profile is not linked or
        already archived. Not available to opposing counsel.
      operationId: AiChatDataController_archiveProfile
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DeleteAiChatProfileDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/MessageResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Archive an AI chat profile
      tags:
      - ai-chat
  "/ai-chat/{caseId}/search-messages":
    get:
      description: Searches message text across every AI chat conversation in the
        case and returns one page of matches, each with its conversation title and
        a highlighted copy of the text; optionally limit to conversations carrying
        all of the given tags. Archived profiles are skipped. On a case shared with
        opposing counsel, messages from profiles the caller may not see are excluded.
      operationId: AiChatDataController_searchMessageContent
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: true
        in: query
        description: Search text for message content
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Filter by conversation tag IDs (comma-separated)
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/AiChatMessageSearchResultEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search AI chat messages in a case
      tags:
      - ai-chat
  "/ai-chat/{caseId}/profile/{providerId}":
    get:
      description: Returns one AI chat profile linked to the case; returns 404 if
        it is archived or not linked. On a case shared with opposing counsel, a profile
        the caller may not see returns 404.
      operationId: AiChatDataController_getProfile
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: providerId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AiChatProviderEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an AI chat profile
      tags:
      - ai-chat
  "/ai-chat/{caseId}/profile/{providerId}/conversations":
    get:
      description: Returns one page of conversations for an AI chat profile in the
        case, with tag names and attachedTags; search by title or conversation id,
        or filter to conversations carrying all of the given tags. On a case shared
        with opposing counsel, a profile the caller may not see returns an empty page.
        attachedTags leave out tags the other side added by hand.
      operationId: AiChatDataController_searchConversations
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: providerId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search conversation title or content
        schema:
          type: string
      - name: tags
        required: false
        in: query
        description: Filter by tag IDs
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/AiChatConversationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List AI chat conversations
      tags:
      - ai-chat
  "/ai-chat/{caseId}/profile/{providerId}/conversation/{conversationId}/messages":
    get:
      description: Returns one page of messages in an AI chat conversation, including
        tool uses and attachments; when searchText is given, only matching messages
        are returned, with highlightedText marking the matches. Returns 404 if the
        conversation does not belong to the profile and case. On a case shared with
        opposing counsel, a profile the caller may not see returns an empty page.
      operationId: AiChatDataController_searchMessages
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: providerId
        required: true
        in: path
        schema:
          type: string
      - name: conversationId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: searchText
        required: false
        in: query
        description: Search message text
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/AiChatMessageEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List AI chat messages
      tags:
      - ai-chat
  "/ai-chat/{caseId}/profile/{providerId}/conversation/{conversationId}/export-pdf":
    post:
      description: Renders the whole conversation to a PDF while the request waits,
        stores it, and returns the stored file record; export started, finished or
        failed events are recorded on the case. Returns 404 if the conversation is
        not linked to the case.
      operationId: AiChatDataController_exportConversationToPdf
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: providerId
        required: true
        in: path
        schema:
          type: string
      - name: conversationId
        required: true
        in: path
        schema:
          type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UserFileResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export an AI chat conversation to PDF
      tags:
      - ai-chat
  "/ai-chat/{caseId}/import":
    get:
      description: Returns one page of AI chat import jobs for the case, newest first
        by default, optionally filtered by status.
      operationId: AiChatDataController_searchImportJobs
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: status
        required: false
        in: query
        description: Filter by job status
        schema:
          type: string
          enum:
          - not_started
          - in_progress
          - completed
          - failed
      - name: searchText
        required: false
        in: query
        description: Search import jobs
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/AiChatImportJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List AI chat import jobs
      tags:
      - ai-chat
    post:
      description: 'Desktop plumbing: creates an import job for an uploaded ChatGPT,
        Claude or Gemini export file, marks the case''s AI chat data source as pending,
        and queues the import. Returns 400 if the file was already imported successfully,
        and 404 if the file or extraction code is not found. Not available to opposing
        counsel.'
      operationId: AiChatDataController_importAiChatData
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ImportAiChatDataDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AiChatImportJobEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Start an AI chat import (internal)
      tags:
      - ai-chat
      x-internal: true
  "/ai-chat/{caseId}/import/{importId}":
    get:
      description: 'Desktop plumbing: returns one AI chat import job for the case,
        including its status, error message and import counts, so the uploader can
        poll progress. Returns 404 if the job does not belong to the case.'
      operationId: AiChatDataController_getImportJob
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: importId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AiChatImportJobEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get an AI chat import job (internal)
      tags:
      - ai-chat
      x-internal: true
  "/ai-chat/{caseId}/profile/{providerId}/conversation/{conversationId}/tags":
    patch:
      description: Replaces an AI chat conversation's tags with the given list of
        tag ids or names; names that do not exist yet are created as case tags. Returns
        the updated conversation, where attachedTags leave out tags the other side
        added by hand.
      operationId: AiChatDataController_updateConversationTags
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: providerId
        required: true
        in: path
        schema:
          type: string
      - name: conversationId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateAiChatConversationTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AiChatConversationEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Set tags on an AI chat conversation
      tags:
      - ai-chat
  "/ai/admin/ping/embedding":
    post:
      description: 'Embeds a fixed test string through Bedrock and reports the model,
        latency and, in `sample`, the vector size and its first three values. A Bedrock
        failure still answers 200, with `ok: false`, `model: unknown` and the error
        class and message. Super admins only.'
      operationId: AiAdminController_pingEmbedding
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      ok:
                        type: boolean
                        example: true
                      purpose:
                        type: string
                        example: admin_ping
                      model:
                        type: string
                        description: unknown when the call failed
                      latencyMs:
                        type: number
                        example: 412
                      sample:
                        type: string
                        description: Present when ok is true.
                      errorClass:
                        type: string
                        description: Present when ok is false.
                      errorMessage:
                        type: string
                        description: Present when ok is false.
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Smoke-test Bedrock embeddings (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/ping/claude":
    post:
      description: 'Sends a fixed ping prompt to the Claude haiku model through Bedrock
        and reports the model, latency and, in `sample`, the first 64 characters of
        the reply. A Bedrock failure still answers 200, with `ok: false`, `model:
        unknown` and the error class and message. Super admins only.'
      operationId: AiAdminController_pingClaude
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      ok:
                        type: boolean
                        example: true
                      purpose:
                        type: string
                        example: admin_ping
                      model:
                        type: string
                        description: unknown when the call failed
                      latencyMs:
                        type: number
                        example: 412
                      sample:
                        type: string
                        description: Present when ok is true.
                      errorClass:
                        type: string
                        description: Present when ok is false.
                      errorMessage:
                        type: string
                        description: Present when ok is false.
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Smoke-test Bedrock Claude (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/ping/rerank":
    post:
      description: 'Reranks three fixed documents against a fixed query through Bedrock
        and reports the model, latency and, in `sample`, each result index with its
        score. A Bedrock failure still answers 200, with `ok: false`, `model: unknown`
        and the error class and message. Super admins only.'
      operationId: AiAdminController_pingRerank
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      ok:
                        type: boolean
                        example: true
                      purpose:
                        type: string
                        example: admin_ping
                      model:
                        type: string
                        description: unknown when the call failed
                      latencyMs:
                        type: number
                        example: 412
                      sample:
                        type: string
                        description: Present when ok is true.
                      errorClass:
                        type: string
                        description: Present when ok is false.
                      errorMessage:
                        type: string
                        description: Present when ok is false.
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Smoke-test Bedrock rerank (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/backfill/device-messages":
    post:
      description: Queues AI ingestion of device messages for the cases in `caseIds`,
        else every case of the organizations in `organizationIds`, else every active
        case when `confirmUnrestricted` is true (otherwise 500); an unrestricted run
        over more than 5 million items also needs `force`. Archived cases are skipped.
        `dryRun` only counts; `force` first clears existing embeddings in scope so
        they are rebuilt, even on a dry run. Super admins only.
      operationId: AiAdminController_backfillDeviceMessages
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiBackfillRequestDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      casesScanned:
                        type: number
                        example: 12
                      casesEnqueued:
                        type: number
                        example: 12
                        description: 0 on a dry run.
                      estimatedMessageScope:
                        type: number
                        example: 48000
                      forced:
                        type: boolean
                        example: false
                      dryRun:
                        type: boolean
                        example: false
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Backfill AI ingestion for device messages (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/backfill/linkedin":
    post:
      description: Queues AI ingestion of LinkedIn data for the cases in `caseIds`,
        else every case of the organizations in `organizationIds`, else every active
        case when `confirmUnrestricted` is true (otherwise 500); an unrestricted run
        over more than 5 million items also needs `force`. Archived cases are skipped.
        `dryRun` only counts; `force` first clears existing embeddings in scope so
        they are rebuilt, even on a dry run. Super admins only.
      operationId: AiAdminController_backfillLinkedIn
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiBackfillRequestDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      casesScanned:
                        type: number
                        example: 12
                      casesEnqueued:
                        type: number
                        example: 12
                        description: 0 on a dry run.
                      estimatedMessageScope:
                        type: number
                        example: 48000
                      forced:
                        type: boolean
                        example: false
                      dryRun:
                        type: boolean
                        example: false
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Backfill AI ingestion for LinkedIn (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/backfill/discord":
    post:
      description: Queues AI ingestion of Discord data for the cases in `caseIds`,
        else every case of the organizations in `organizationIds`, else every active
        case when `confirmUnrestricted` is true (otherwise 500); an unrestricted run
        over more than 5 million items also needs `force`. Archived cases are skipped.
        `dryRun` only counts; `force` first clears existing embeddings in scope so
        they are rebuilt, even on a dry run. Super admins only.
      operationId: AiAdminController_backfillDiscord
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiBackfillRequestDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      casesScanned:
                        type: number
                        example: 12
                      casesEnqueued:
                        type: number
                        example: 12
                        description: 0 on a dry run.
                      estimatedMessageScope:
                        type: number
                        example: 48000
                      forced:
                        type: boolean
                        example: false
                      dryRun:
                        type: boolean
                        example: false
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Backfill AI ingestion for Discord (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/backfill/email":
    post:
      description: Queues AI ingestion of email for the cases in `caseIds`, else every
        case of the organizations in `organizationIds`, else every active case when
        `confirmUnrestricted` is true (otherwise 500); an unrestricted run over more
        than 5 million items also needs `force`. Archived cases are skipped. `dryRun`
        only counts; `force` first clears existing embeddings in scope so they are
        rebuilt, even on a dry run. Super admins only.
      operationId: AiAdminController_backfillEmail
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiBackfillRequestDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      casesScanned:
                        type: number
                        example: 12
                      casesEnqueued:
                        type: number
                        example: 12
                        description: 0 on a dry run.
                      estimatedMessageScope:
                        type: number
                        example: 48000
                      forced:
                        type: boolean
                        example: false
                      dryRun:
                        type: boolean
                        example: false
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Backfill AI ingestion for email (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/backfill/aichat":
    post:
      description: Queues AI ingestion of AI chat data for the cases in `caseIds`,
        else every case of the organizations in `organizationIds`, else every active
        case when `confirmUnrestricted` is true (otherwise 500); an unrestricted run
        over more than 5 million items also needs `force`. Archived cases are skipped.
        `dryRun` only counts; `force` first clears existing embeddings in scope so
        they are rebuilt, even on a dry run. Super admins only.
      operationId: AiAdminController_backfillAiChat
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiBackfillRequestDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      casesScanned:
                        type: number
                        example: 12
                      casesEnqueued:
                        type: number
                        example: 12
                        description: 0 on a dry run.
                      estimatedMessageScope:
                        type: number
                        example: 48000
                      forced:
                        type: boolean
                        example: false
                      dryRun:
                        type: boolean
                        example: false
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Backfill AI ingestion for AI chat (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/backfill/reddit":
    post:
      description: Queues AI ingestion of Reddit data for the cases in `caseIds`,
        else every case of the organizations in `organizationIds`, else every active
        case when `confirmUnrestricted` is true (otherwise 500); an unrestricted run
        over more than 5 million items also needs `force`. Archived cases are skipped.
        `dryRun` only counts; `force` first clears existing embeddings in scope so
        they are rebuilt, even on a dry run. Super admins only.
      operationId: AiAdminController_backfillReddit
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiBackfillRequestDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      casesScanned:
                        type: number
                        example: 12
                      casesEnqueued:
                        type: number
                        example: 12
                        description: 0 on a dry run.
                      estimatedMessageScope:
                        type: number
                        example: 48000
                      forced:
                        type: boolean
                        example: false
                      dryRun:
                        type: boolean
                        example: false
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Backfill AI ingestion for Reddit (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/cases/{caseId}/jobs/cancel":
    post:
      description: Removes the waiting, delayed, paused and active AI jobs of the
        case from every AI queue and reports how many were removed per queue and state.
        A job already running may still finish its current step. Requires owner or
        admin access to the case and an active plan (403 otherwise); not available
        to opposing counsel.
      operationId: AiCaseResetController_cancelJobs
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      case:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      cancelled:
                        type: object
                        description: Jobs removed per AI queue, by job state.
                        additionalProperties:
                          type: object
                          properties:
                            waiting:
                              type: number
                            delayed:
                              type: number
                            paused:
                              type: number
                            active:
                              type: number
                        example:
                          ai-embedding-jobs:
                            waiting: 4
                            delayed: 0
                            paused: 0
                            active: 1
                      totalCancelled:
                        type: number
                        example: 5
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Cancel the AI jobs of a case
      tags:
      - ai
  "/ai/cases/{caseId}/reset":
    post:
      description: 'Cancels the case AI jobs, then deletes its AI pipeline data (chunks,
        summaries, snippets, processing state, tag suggestion cache, filing extractions
        and message embeddings) while keeping attorney work: attorney-stated and confirmed
        context fields, attorney notes and evidentiary map overrides. With `dryRun:
        true` it only counts what would be removed. Requires owner or admin access
        to the case and an active plan (403 otherwise); not available to opposing
        counsel.'
      operationId: AiCaseResetController_resetCase
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiCaseResetDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      case:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      dryRun:
                        type: boolean
                        example: false
                      cancelledJobs:
                        type: object
                        properties:
                          case:
                            type: string
                            example: 65a1b2c3d4e5f6a7b8c9d0e2
                          cancelled:
                            type: object
                            description: Jobs removed per AI queue, by job state.
                            additionalProperties:
                              type: object
                              properties:
                                waiting:
                                  type: number
                                delayed:
                                  type: number
                                paused:
                                  type: number
                                active:
                                  type: number
                            example:
                              ai-embedding-jobs:
                                waiting: 4
                                delayed: 0
                                paused: 0
                                active: 1
                          totalCancelled:
                            type: number
                            example: 5
                      deleted:
                        type: object
                        description: Documents deleted per collection (counted only
                          on a dry run).
                        properties:
                          chunks:
                            type: number
                          periodSummaries:
                            type: number
                          conversationSummaries:
                            type: number
                          evidenceSnippets:
                            type: number
                          processingStates:
                            type: number
                          tagSuggestionCacheEntries:
                            type: number
                          contextExtractions:
                            type: number
                      messagesEmbeddingsCleared:
                        type: number
                        example: 1200
                      caseSummaryReset:
                        type: boolean
                        example: true
                      evidentiaryMapEntriesPreserved:
                        type: number
                        example: 2
                      contextFieldsCleared:
                        type: array
                        items:
                          type: string
                        example:
                        - keyDates
                      preserved:
                        type: array
                        items:
                          type: string
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Reset the AI data of a case
      tags:
      - ai
  "/ai/cases/{caseId}/search":
    post:
      description: Runs a semantic search over the case evidence and returns the best
        matches (messages or conversation chunks) with score, snippet and, when asked,
        the surrounding messages, filtered by source, dates, participants, conversations,
        topics or relevance. On a case shared with opposing counsel, callers on the
        other side see only evidence released to them, and conversation chunks are
        left out. Requires commenter access or above to the case and an active plan
        (403 otherwise).
      operationId: AiSearchController_search
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiSearchDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AiSearchResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Search case evidence
      tags:
      - ai
  "/ai/cases/{caseId}/progress":
    get:
      description: 'Returns the AI processing status of the case for one evidence
        source (device messages by default): chunking, embedding and summarization
        progress with an overall percentage, timings, an embedding time estimate and
        the last error. Returns 404 when processing has not started for that source.
        Requires commenter access or above to the case and an active plan (403 otherwise);
        not available to opposing counsel.'
      operationId: AiProgressController_getProgress
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: source
        required: false
        in: query
        description: Source to inspect; defaults to device_message
        schema:
          enum:
          - device_message
          - linkedin
          - reddit
          - discord
          - email
          - ai_chat
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/AiCaseProgressEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the AI processing progress
      tags:
      - ai
  "/ai/cases/{caseId}/summary":
    get:
      description: 'Returns the layered case summary: key parties, timeline, factual
        narrative (with any pending regenerated version), evidentiary map, attorney
        notes and open items. A case with no summary yet gets an empty one. Requires
        commenter access or above to the case and an active plan (403 otherwise);
        not available to opposing counsel.'
      operationId: AiCaseSummaryController_getSummary
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: The layered AI case summary.
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      case:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      organization:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e3
                      version:
                        type: number
                        example: 1
                      keyParties:
                        type: object
                        properties:
                          parties:
                            type: array
                            items:
                              type: object
                              properties:
                                name:
                                  type: string
                                role:
                                  type: string
                                aliases:
                                  type: array
                                  items:
                                    type: string
                                frequency:
                                  type: number
                                significance:
                                  type: string
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      timeline:
                        type: object
                        properties:
                          events:
                            type: array
                            items:
                              type: object
                              properties:
                                date:
                                  type: string
                                  format: date-time
                                description:
                                  type: string
                                sourceRefs:
                                  type: array
                                  items:
                                    type: string
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      factualNarrative:
                        type: object
                        properties:
                          content:
                            type: string
                            nullable: true
                          pendingVersion:
                            type: string
                            nullable: true
                          pendingGeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          basedOnEvidenceThrough:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          stale:
                            type: boolean
                            example: false
                          modelVersion:
                            type: string
                            nullable: true
                          promptVersion:
                            type: string
                            nullable: true
                            example: case_summary.v1
                          contextProfileVersion:
                            type: number
                            nullable: true
                            example: 2
                      evidentiaryMap:
                        type: object
                        properties:
                          elements:
                            type: array
                            items:
                              type: object
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      attorneyNotes:
                        type: object
                        properties:
                          theory:
                            type: string
                            nullable: true
                          strategy:
                            type: string
                            nullable: true
                          openQuestions:
                            type: array
                            items:
                              type: string
                          lastEditedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          lastEditedBy:
                            type: string
                            nullable: true
                            example: 65a1b2c3d4e5f6a7b8c9d0e4
                      openItems:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                description:
                                  type: string
                                status:
                                  type: string
                                  enum:
                                  - open
                                  - in_progress
                                  - resolved
                                  - dismissed
                                suggestedBy:
                                  type: string
                                  enum:
                                  - ai_suggested
                                  - attorney_added
                                createdAt:
                                  type: string
                                  format: date-time
                                  example: '2026-01-15T10:15:00.000Z'
                                  nullable: true
                                resolvedAt:
                                  type: string
                                  format: date-time
                                  example: '2026-01-15T10:15:00.000Z'
                                  nullable: true
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the AI case summary
      tags:
      - ai
  "/ai/cases/{caseId}/summary/regenerate":
    post:
      description: 'Queues a rebuild of the factual narrative and answers 202 with
        `enqueued: true`. The new text lands in `factualNarrative.pendingVersion`,
        where it waits to be accepted or discarded. Requires owner or admin access
        to the case and an active plan (403 otherwise); not available to opposing
        counsel.'
      operationId: AiCaseSummaryController_regenerate
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      enqueued:
                        type: boolean
                        example: true
                  statusCode:
                    type: number
                    example: 202
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Regenerate the case narrative
      tags:
      - ai
  "/ai/cases/{caseId}/summary/accept-pending":
    post:
      description: Replaces the factual narrative with its pending version, clears
        the pending fields and the stale flag, and returns the summary. Returns 400
        when nothing is pending and 404 when the case has no summary. Requires owner
        or admin access to the case and an active plan (403 otherwise); not available
        to opposing counsel.
      operationId: AiCaseSummaryController_acceptPending
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: The layered AI case summary.
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      case:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      organization:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e3
                      version:
                        type: number
                        example: 1
                      keyParties:
                        type: object
                        properties:
                          parties:
                            type: array
                            items:
                              type: object
                              properties:
                                name:
                                  type: string
                                role:
                                  type: string
                                aliases:
                                  type: array
                                  items:
                                    type: string
                                frequency:
                                  type: number
                                significance:
                                  type: string
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      timeline:
                        type: object
                        properties:
                          events:
                            type: array
                            items:
                              type: object
                              properties:
                                date:
                                  type: string
                                  format: date-time
                                description:
                                  type: string
                                sourceRefs:
                                  type: array
                                  items:
                                    type: string
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      factualNarrative:
                        type: object
                        properties:
                          content:
                            type: string
                            nullable: true
                          pendingVersion:
                            type: string
                            nullable: true
                          pendingGeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          basedOnEvidenceThrough:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          stale:
                            type: boolean
                            example: false
                          modelVersion:
                            type: string
                            nullable: true
                          promptVersion:
                            type: string
                            nullable: true
                            example: case_summary.v1
                          contextProfileVersion:
                            type: number
                            nullable: true
                            example: 2
                      evidentiaryMap:
                        type: object
                        properties:
                          elements:
                            type: array
                            items:
                              type: object
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      attorneyNotes:
                        type: object
                        properties:
                          theory:
                            type: string
                            nullable: true
                          strategy:
                            type: string
                            nullable: true
                          openQuestions:
                            type: array
                            items:
                              type: string
                          lastEditedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          lastEditedBy:
                            type: string
                            nullable: true
                            example: 65a1b2c3d4e5f6a7b8c9d0e4
                      openItems:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                description:
                                  type: string
                                status:
                                  type: string
                                  enum:
                                  - open
                                  - in_progress
                                  - resolved
                                  - dismissed
                                suggestedBy:
                                  type: string
                                  enum:
                                  - ai_suggested
                                  - attorney_added
                                createdAt:
                                  type: string
                                  format: date-time
                                  example: '2026-01-15T10:15:00.000Z'
                                  nullable: true
                                resolvedAt:
                                  type: string
                                  format: date-time
                                  example: '2026-01-15T10:15:00.000Z'
                                  nullable: true
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Accept the pending narrative
      tags:
      - ai
  "/ai/cases/{caseId}/summary/pending":
    delete:
      description: Clears the pending narrative version and returns the summary. Returns
        404 when the case has no summary. Requires owner or admin access to the case
        and an active plan (403 otherwise); not available to opposing counsel.
      operationId: AiCaseSummaryController_discardPending
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: The layered AI case summary.
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      case:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      organization:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e3
                      version:
                        type: number
                        example: 1
                      keyParties:
                        type: object
                        properties:
                          parties:
                            type: array
                            items:
                              type: object
                              properties:
                                name:
                                  type: string
                                role:
                                  type: string
                                aliases:
                                  type: array
                                  items:
                                    type: string
                                frequency:
                                  type: number
                                significance:
                                  type: string
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      timeline:
                        type: object
                        properties:
                          events:
                            type: array
                            items:
                              type: object
                              properties:
                                date:
                                  type: string
                                  format: date-time
                                description:
                                  type: string
                                sourceRefs:
                                  type: array
                                  items:
                                    type: string
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      factualNarrative:
                        type: object
                        properties:
                          content:
                            type: string
                            nullable: true
                          pendingVersion:
                            type: string
                            nullable: true
                          pendingGeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          basedOnEvidenceThrough:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          stale:
                            type: boolean
                            example: false
                          modelVersion:
                            type: string
                            nullable: true
                          promptVersion:
                            type: string
                            nullable: true
                            example: case_summary.v1
                          contextProfileVersion:
                            type: number
                            nullable: true
                            example: 2
                      evidentiaryMap:
                        type: object
                        properties:
                          elements:
                            type: array
                            items:
                              type: object
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      attorneyNotes:
                        type: object
                        properties:
                          theory:
                            type: string
                            nullable: true
                          strategy:
                            type: string
                            nullable: true
                          openQuestions:
                            type: array
                            items:
                              type: string
                          lastEditedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          lastEditedBy:
                            type: string
                            nullable: true
                            example: 65a1b2c3d4e5f6a7b8c9d0e4
                      openItems:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                description:
                                  type: string
                                status:
                                  type: string
                                  enum:
                                  - open
                                  - in_progress
                                  - resolved
                                  - dismissed
                                suggestedBy:
                                  type: string
                                  enum:
                                  - ai_suggested
                                  - attorney_added
                                createdAt:
                                  type: string
                                  format: date-time
                                  example: '2026-01-15T10:15:00.000Z'
                                  nullable: true
                                resolvedAt:
                                  type: string
                                  format: date-time
                                  example: '2026-01-15T10:15:00.000Z'
                                  nullable: true
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Discard the pending narrative
      tags:
      - ai
  "/ai/cases/{caseId}/summary/attorney-notes":
    put:
      description: Sets the attorney-written theory, strategy and open questions sent
        (the AI never changes these), records who edited them and when, and returns
        the summary, creating it if needed. Requires owner or admin access to the
        case and an active plan (403 otherwise); not available to opposing counsel.
      operationId: AiCaseSummaryController_updateAttorneyNotes
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiAttorneyNotesDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: The layered AI case summary.
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      case:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      organization:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e3
                      version:
                        type: number
                        example: 1
                      keyParties:
                        type: object
                        properties:
                          parties:
                            type: array
                            items:
                              type: object
                              properties:
                                name:
                                  type: string
                                role:
                                  type: string
                                aliases:
                                  type: array
                                  items:
                                    type: string
                                frequency:
                                  type: number
                                significance:
                                  type: string
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      timeline:
                        type: object
                        properties:
                          events:
                            type: array
                            items:
                              type: object
                              properties:
                                date:
                                  type: string
                                  format: date-time
                                description:
                                  type: string
                                sourceRefs:
                                  type: array
                                  items:
                                    type: string
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      factualNarrative:
                        type: object
                        properties:
                          content:
                            type: string
                            nullable: true
                          pendingVersion:
                            type: string
                            nullable: true
                          pendingGeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          basedOnEvidenceThrough:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          stale:
                            type: boolean
                            example: false
                          modelVersion:
                            type: string
                            nullable: true
                          promptVersion:
                            type: string
                            nullable: true
                            example: case_summary.v1
                          contextProfileVersion:
                            type: number
                            nullable: true
                            example: 2
                      evidentiaryMap:
                        type: object
                        properties:
                          elements:
                            type: array
                            items:
                              type: object
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      attorneyNotes:
                        type: object
                        properties:
                          theory:
                            type: string
                            nullable: true
                          strategy:
                            type: string
                            nullable: true
                          openQuestions:
                            type: array
                            items:
                              type: string
                          lastEditedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                          lastEditedBy:
                            type: string
                            nullable: true
                            example: 65a1b2c3d4e5f6a7b8c9d0e4
                      openItems:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              type: object
                              properties:
                                id:
                                  type: string
                                description:
                                  type: string
                                status:
                                  type: string
                                  enum:
                                  - open
                                  - in_progress
                                  - resolved
                                  - dismissed
                                suggestedBy:
                                  type: string
                                  enum:
                                  - ai_suggested
                                  - attorney_added
                                createdAt:
                                  type: string
                                  format: date-time
                                  example: '2026-01-15T10:15:00.000Z'
                                  nullable: true
                                resolvedAt:
                                  type: string
                                  format: date-time
                                  example: '2026-01-15T10:15:00.000Z'
                                  nullable: true
                          lastRegeneratedAt:
                            type: string
                            format: date-time
                            example: '2026-01-15T10:15:00.000Z'
                            nullable: true
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update the attorney notes
      tags:
      - ai
  "/ai/cases/{caseId}/context":
    get:
      description: 'Returns the case context the AI uses as background: case type,
        jurisdiction, theory of the case, legal claims and their elements, what to
        prove, key parties and dates, with where each field came from. A case with
        no context yet gets an empty one. Requires commenter access or above to the
        case and an active plan (403 otherwise); not available to opposing counsel.'
      operationId: AiCaseContextController_getContext
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: The AI case context profile.
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      case:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      organization:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e3
                      caseType:
                        type: string
                        nullable: true
                        example: custody
                      jurisdiction:
                        type: string
                        nullable: true
                        example: California
                      theoryOfCase:
                        type: string
                        nullable: true
                      legalClaims:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                            claim:
                              type: string
                            statuteCitation:
                              type: string
                              nullable: true
                            elements:
                              type: array
                              items:
                                type: object
                                properties:
                                  id:
                                    type: string
                                  description:
                                    type: string
                            source:
                              type: string
                              enum:
                              - complaint
                              - attorney_stated
                              - amended_filing
                      whatToProve:
                        type: array
                        items:
                          type: string
                      keyParties:
                        type: array
                        items:
                          type: object
                          properties:
                            name:
                              type: string
                            aliases:
                              type: array
                              items:
                                type: string
                            role:
                              type: string
                              enum:
                              - petitioner
                              - respondent
                              - plaintiff
                              - defendant
                              - child
                              - witness
                              - third_party
                              - other
                            handles:
                              type: array
                              items:
                                type: string
                            significance:
                              type: string
                              nullable: true
                      keyDates:
                        type: array
                        items:
                          type: object
                          properties:
                            date:
                              type: string
                              format: date-time
                            event:
                              type: string
                            significance:
                              type: string
                              nullable: true
                      relevantDateRange:
                        type: object
                        nullable: true
                        properties:
                          start:
                            type: string
                            format: date-time
                          end:
                            type: string
                            format: date-time
                      sourceDocuments:
                        type: array
                        items:
                          type: string
                      authoredBy:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      confirmedByAttorney:
                        type: boolean
                        example: false
                      fieldProvenance:
                        type: object
                        description: 'Per field: attorney_stated, ai_extracted or
                          ai_extracted_confirmed.'
                        additionalProperties:
                          type: string
                        example:
                          theoryOfCase: attorney_stated
                      extractionModelVersion:
                        type: string
                        nullable: true
                      version:
                        type: number
                        example: 2
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the AI case context
      tags:
      - ai
    patch:
      description: Sets the fields sent and marks each as attorney-stated. `version`
        goes up when a field that steers the AI (case type, jurisdiction, theory,
        claims, what to prove, parties, dates or date range) actually changes. Returns
        the updated context. Requires owner or admin access to the case and an active
        plan (403 otherwise); not available to opposing counsel.
      operationId: AiCaseContextController_updateContext
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiCaseContextPatchDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: The AI case context profile.
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      case:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      organization:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e3
                      caseType:
                        type: string
                        nullable: true
                        example: custody
                      jurisdiction:
                        type: string
                        nullable: true
                        example: California
                      theoryOfCase:
                        type: string
                        nullable: true
                      legalClaims:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                            claim:
                              type: string
                            statuteCitation:
                              type: string
                              nullable: true
                            elements:
                              type: array
                              items:
                                type: object
                                properties:
                                  id:
                                    type: string
                                  description:
                                    type: string
                            source:
                              type: string
                              enum:
                              - complaint
                              - attorney_stated
                              - amended_filing
                      whatToProve:
                        type: array
                        items:
                          type: string
                      keyParties:
                        type: array
                        items:
                          type: object
                          properties:
                            name:
                              type: string
                            aliases:
                              type: array
                              items:
                                type: string
                            role:
                              type: string
                              enum:
                              - petitioner
                              - respondent
                              - plaintiff
                              - defendant
                              - child
                              - witness
                              - third_party
                              - other
                            handles:
                              type: array
                              items:
                                type: string
                            significance:
                              type: string
                              nullable: true
                      keyDates:
                        type: array
                        items:
                          type: object
                          properties:
                            date:
                              type: string
                              format: date-time
                            event:
                              type: string
                            significance:
                              type: string
                              nullable: true
                      relevantDateRange:
                        type: object
                        nullable: true
                        properties:
                          start:
                            type: string
                            format: date-time
                          end:
                            type: string
                            format: date-time
                      sourceDocuments:
                        type: array
                        items:
                          type: string
                      authoredBy:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      confirmedByAttorney:
                        type: boolean
                        example: false
                      fieldProvenance:
                        type: object
                        description: 'Per field: attorney_stated, ai_extracted or
                          ai_extracted_confirmed.'
                        additionalProperties:
                          type: string
                        example:
                          theoryOfCase: attorney_stated
                      extractionModelVersion:
                        type: string
                        nullable: true
                      version:
                        type: number
                        example: 2
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Update the AI case context
      tags:
      - ai
  "/ai/cases/{caseId}/context/confirm":
    post:
      description: 'Marks the context as reviewed by an attorney (`confirmedByAttorney:
        true`), after which the AI treats it as confirmed background. Returns 404
        when the case has no context yet. Requires owner or admin access to the case
        and an active plan (403 otherwise); not available to opposing counsel.'
      operationId: AiCaseContextController_confirm
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: The AI case context profile.
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      case:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      organization:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e3
                      caseType:
                        type: string
                        nullable: true
                        example: custody
                      jurisdiction:
                        type: string
                        nullable: true
                        example: California
                      theoryOfCase:
                        type: string
                        nullable: true
                      legalClaims:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                            claim:
                              type: string
                            statuteCitation:
                              type: string
                              nullable: true
                            elements:
                              type: array
                              items:
                                type: object
                                properties:
                                  id:
                                    type: string
                                  description:
                                    type: string
                            source:
                              type: string
                              enum:
                              - complaint
                              - attorney_stated
                              - amended_filing
                      whatToProve:
                        type: array
                        items:
                          type: string
                      keyParties:
                        type: array
                        items:
                          type: object
                          properties:
                            name:
                              type: string
                            aliases:
                              type: array
                              items:
                                type: string
                            role:
                              type: string
                              enum:
                              - petitioner
                              - respondent
                              - plaintiff
                              - defendant
                              - child
                              - witness
                              - third_party
                              - other
                            handles:
                              type: array
                              items:
                                type: string
                            significance:
                              type: string
                              nullable: true
                      keyDates:
                        type: array
                        items:
                          type: object
                          properties:
                            date:
                              type: string
                              format: date-time
                            event:
                              type: string
                            significance:
                              type: string
                              nullable: true
                      relevantDateRange:
                        type: object
                        nullable: true
                        properties:
                          start:
                            type: string
                            format: date-time
                          end:
                            type: string
                            format: date-time
                      sourceDocuments:
                        type: array
                        items:
                          type: string
                      authoredBy:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      confirmedByAttorney:
                        type: boolean
                        example: false
                      fieldProvenance:
                        type: object
                        description: 'Per field: attorney_stated, ai_extracted or
                          ai_extracted_confirmed.'
                        additionalProperties:
                          type: string
                        example:
                          theoryOfCase: attorney_stated
                      extractionModelVersion:
                        type: string
                        nullable: true
                      version:
                        type: number
                        example: 2
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Confirm the AI case context
      tags:
      - ai
  "/ai/cases/{caseId}/context/extract-filing":
    post:
      description: Queues extraction of case context from one of the case files (a
        complaint, petition or other filing) and answers 202 with the queued extraction;
        list the extractions to follow it. Returns 404 when the file is not an active
        file of the case. Requires owner or admin access to the case and an active
        plan (403 otherwise); not available to opposing counsel.
      operationId: AiContextExtractionController_extractFiling
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/StartFilingExtractionDto"
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      case:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      organization:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e3
                      caseFile:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e5
                      status:
                        type: string
                        enum:
                        - queued
                        - running
                        - completed
                        - failed
                        example: completed
                      rawExtraction:
                        type: object
                        nullable: true
                        description: The model output, kept as returned.
                      appliedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      appliedBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      appliedFields:
                        type: array
                        items:
                          type: string
                        example:
                        - caseType
                        - legalClaims
                      modelVersion:
                        type: string
                        nullable: true
                      promptVersion:
                        type: string
                        nullable: true
                        example: case_context_filing_extraction.v1
                      errorMessage:
                        type: string
                        nullable: true
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 202
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Extract case context from a filing
      tags:
      - ai
  "/ai/cases/{caseId}/context/extractions":
    get:
      description: Lists the latest 50 filing extractions of the case, newest first,
        with their status and, once completed, the raw extraction. Requires commenter
        access or above to the case and an active plan (403 otherwise); not available
        to opposing counsel.
      operationId: AiContextExtractionController_listExtractions
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        _id:
                          type: string
                          example: 65a1b2c3d4e5f6a7b8c9d0e1
                        case:
                          type: string
                          example: 65a1b2c3d4e5f6a7b8c9d0e2
                        organization:
                          type: string
                          example: 65a1b2c3d4e5f6a7b8c9d0e3
                        caseFile:
                          type: string
                          example: 65a1b2c3d4e5f6a7b8c9d0e5
                        status:
                          type: string
                          enum:
                          - queued
                          - running
                          - completed
                          - failed
                          example: completed
                        rawExtraction:
                          type: object
                          nullable: true
                          description: The model output, kept as returned.
                        appliedAt:
                          type: string
                          format: date-time
                          example: '2026-01-15T10:15:00.000Z'
                          nullable: true
                        appliedBy:
                          type: string
                          nullable: true
                          example: 65a1b2c3d4e5f6a7b8c9d0e4
                        appliedFields:
                          type: array
                          items:
                            type: string
                          example:
                          - caseType
                          - legalClaims
                        modelVersion:
                          type: string
                          nullable: true
                        promptVersion:
                          type: string
                          nullable: true
                          example: case_context_filing_extraction.v1
                        errorMessage:
                          type: string
                          nullable: true
                        createdAt:
                          type: string
                          format: date-time
                          example: '2026-01-15T10:15:00.000Z'
                          nullable: true
                        updatedAt:
                          type: string
                          format: date-time
                          example: '2026-01-15T10:15:00.000Z'
                          nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List filing extractions
      tags:
      - ai
  "/ai/cases/{caseId}/context/extractions/{extractionId}/apply":
    post:
      description: Copies a completed extraction into the case context, all fields
        or only those in `fieldsToApply`, marking them as AI-extracted, and returns
        the extraction with `appliedAt`, `appliedBy` and `appliedFields`. Applying
        it again returns it unchanged. Returns 400 when the extraction is not completed
        and 404 when it does not exist or belongs to another case. Requires owner
        or admin access to the case and an active plan (403 otherwise); not available
        to opposing counsel.
      operationId: AiContextExtractionController_applyExtraction
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: extractionId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ApplyFilingExtractionDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      case:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e2
                      organization:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e3
                      caseFile:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e5
                      status:
                        type: string
                        enum:
                        - queued
                        - running
                        - completed
                        - failed
                        example: completed
                      rawExtraction:
                        type: object
                        nullable: true
                        description: The model output, kept as returned.
                      appliedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      appliedBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      appliedFields:
                        type: array
                        items:
                          type: string
                        example:
                        - caseType
                        - legalClaims
                      modelVersion:
                        type: string
                        nullable: true
                      promptVersion:
                        type: string
                        nullable: true
                        example: case_context_filing_extraction.v1
                      errorMessage:
                        type: string
                        nullable: true
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Apply a filing extraction
      tags:
      - ai
  "/ai/cases/{caseId}/evidentiary-map":
    get:
      description: Returns, for each element of each legal claim, the supporting and
        contradicting evidence, its strength and the AI assessment, plus any attorney
        override. Returns 404 when the case has no summary yet. Requires commenter
        access or above to the case and an active plan (403 otherwise); not available
        to opposing counsel.
      operationId: AiEvidentiaryMapController_getEvidentiaryMap
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: The evidentiary map section of the case summary.
                    properties:
                      elements:
                        type: array
                        items:
                          type: object
                          properties:
                            claimId:
                              type: string
                              example: claim-1
                            elementId:
                              type: string
                              example: claim-1-el-1
                            elementDescription:
                              type: string
                            supportingEvidence:
                              type: array
                              items:
                                type: object
                                properties:
                                  kind:
                                    type: string
                                    enum:
                                    - message
                                    - chunk
                                    - file
                                  ref:
                                    type: string
                                  source:
                                    type: string
                                    enum:
                                    - device_message
                                    - linkedin
                                    - reddit
                                    - discord
                                    - email
                                    - ai_chat
                                  snippet:
                                    type: string
                                    nullable: true
                                  relevanceScore:
                                    type: number
                                    example: 0.82
                                  reasoning:
                                    type: string
                                    nullable: true
                            contradictingEvidence:
                              type: array
                              items:
                                type: object
                                properties:
                                  kind:
                                    type: string
                                    enum:
                                    - message
                                    - chunk
                                    - file
                                  ref:
                                    type: string
                                  source:
                                    type: string
                                    enum:
                                    - device_message
                                    - linkedin
                                    - reddit
                                    - discord
                                    - email
                                    - ai_chat
                                  snippet:
                                    type: string
                                    nullable: true
                                  relevanceScore:
                                    type: number
                                    example: 0.82
                                  reasoning:
                                    type: string
                                    nullable: true
                            strength:
                              type: string
                              enum:
                              - gap
                              - weak
                              - moderate
                              - strong
                              example: moderate
                            aiAssessment:
                              type: string
                              nullable: true
                            attorneyOverride:
                              type: string
                              nullable: true
                            lastRegeneratedAt:
                              type: string
                              format: date-time
                              example: '2026-01-15T10:15:00.000Z'
                              nullable: true
                            stale:
                              type: boolean
                              example: false
                      lastRegeneratedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the evidentiary map
      tags:
      - ai
  "/ai/cases/{caseId}/evidentiary-map/regenerate":
    post:
      description: 'Queues a rebuild of every element and answers 202 with `enqueued:
        true`; read the map to see the result. Attorney overrides are kept. Requires
        owner or admin access to the case and an active plan (403 otherwise); not
        available to opposing counsel.'
      operationId: AiEvidentiaryMapController_regenerateMap
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      enqueued:
                        type: boolean
                        example: true
                  statusCode:
                    type: number
                    example: 202
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Regenerate the evidentiary map
      tags:
      - ai
  "/ai/cases/{caseId}/evidentiary-map/regenerate-element":
    post:
      description: 'Queues a rebuild of one claim element (`claimId`, `elementId`)
        and answers 202 with `enqueued: true`. Its attorney override is kept. Requires
        owner or admin access to the case and an active plan (403 otherwise); not
        available to opposing counsel.'
      operationId: AiEvidentiaryMapController_regenerateElement
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RebuildEvidentiaryElementDto"
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      enqueued:
                        type: boolean
                        example: true
                  statusCode:
                    type: number
                    example: 202
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Regenerate one evidentiary map element
      tags:
      - ai
  "/ai/cases/{caseId}/evidentiary-map/elements/{claimId}/{elementId}/override":
    put:
      description: Sets the attorney override text of one claim element, or clears
        it when `override` is null or omitted; regeneration never changes it. Returns
        404 when the claim or element does not exist. Requires owner or admin access
        to the case and an active plan (403 otherwise); not available to opposing
        counsel.
      operationId: AiEvidentiaryMapController_setOverride
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: claimId
        required: true
        in: path
        schema:
          type: string
      - name: elementId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AttorneyOverrideDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      ok:
                        type: boolean
                        example: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Set an attorney override on an element
      tags:
      - ai
  "/ai/cases/{caseId}/chunks/{chunkId}/suggest-tags":
    post:
      description: Returns active system, organization and case tags ranked by confidence
        with the reasoning, plus names for tags that do not exist yet. Nothing is
        applied. Results are cached; `forceRefresh` asks the model again. Returns
        404 when the chunk is not in the case or its source is not supported. Requires
        commenter access or above to the case and an active plan (403 otherwise);
        not available to opposing counsel.
      operationId: AiTagSuggestionController_suggestTags
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      - name: chunkId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SuggestTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      suggestions:
                        type: array
                        items:
                          type: object
                          properties:
                            tagId:
                              type: string
                              example: 65a1b2c3d4e5f6a7b8c9d0e6
                            tagName:
                              type: string
                              example: Threats
                            tagSeverity:
                              type: string
                            tagBackgroundColor:
                              type: string
                              example: "#FDE2E1"
                            tagTextColor:
                              type: string
                              example: "#9B1C1C"
                            confidence:
                              type: number
                              example: 0.87
                            reasoning:
                              type: string
                      noveltyHints:
                        type: array
                        items:
                          type: object
                          properties:
                            proposedTagName:
                              type: string
                              example: Missed pickups
                            reasoning:
                              type: string
                      cached:
                        type: boolean
                        example: false
                      model:
                        type: string
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Suggest tags for a conversation chunk
      tags:
      - ai
  "/ai/admin/prompts":
    get:
      description: Lists the active stored prompt version of each purpose, sorted
        by purpose. Only stored versions are listed; a purpose with none runs on its
        code default. Super admins only.
      operationId: AiPromptAdminController_listActive
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        _id:
                          type: string
                          example: 65a1b2c3d4e5f6a7b8c9d0e1
                        purpose:
                          type: string
                          enum:
                          - chunk_summary
                          - period_summary
                          - conversation_summary
                          - case_summary
                          - case_context_filing_extraction
                          - relevance_gate
                          - evidentiary_map_element
                          - tag_suggestion
                          example: case_summary
                        versionSlug:
                          type: string
                          example: case_summary.v2
                        systemPrompt:
                          type: string
                        toolName:
                          type: string
                        toolDescription:
                          type: string
                        toolInputSchema:
                          type: object
                          description: JSON Schema of the tool input.
                        model:
                          type: string
                          example: sonnet
                        maxTokens:
                          type: number
                          example: 1024
                        temperature:
                          type: number
                          example: 0.2
                        isActive:
                          type: boolean
                          example: true
                        notes:
                          type: string
                        createdBy:
                          type: string
                          nullable: true
                          example: 65a1b2c3d4e5f6a7b8c9d0e4
                        updatedBy:
                          type: string
                          nullable: true
                          example: 65a1b2c3d4e5f6a7b8c9d0e4
                        createdAt:
                          type: string
                          format: date-time
                          example: '2026-01-15T10:15:00.000Z'
                          nullable: true
                        updatedAt:
                          type: string
                          format: date-time
                          example: '2026-01-15T10:15:00.000Z'
                          nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List the active AI prompts (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/prompts/{purpose}":
    get:
      description: Lists every stored version of one prompt purpose, newest first.
        An unknown `purpose` returns 400. Super admins only.
      operationId: AiPromptAdminController_listVersions
      parameters:
      - name: purpose
        required: true
        in: path
        schema:
          enum:
          - chunk_summary
          - period_summary
          - conversation_summary
          - case_summary
          - case_context_filing_extraction
          - relevance_gate
          - evidentiary_map_element
          - tag_suggestion
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        _id:
                          type: string
                          example: 65a1b2c3d4e5f6a7b8c9d0e1
                        purpose:
                          type: string
                          enum:
                          - chunk_summary
                          - period_summary
                          - conversation_summary
                          - case_summary
                          - case_context_filing_extraction
                          - relevance_gate
                          - evidentiary_map_element
                          - tag_suggestion
                          example: case_summary
                        versionSlug:
                          type: string
                          example: case_summary.v2
                        systemPrompt:
                          type: string
                        toolName:
                          type: string
                        toolDescription:
                          type: string
                        toolInputSchema:
                          type: object
                          description: JSON Schema of the tool input.
                        model:
                          type: string
                          example: sonnet
                        maxTokens:
                          type: number
                          example: 1024
                        temperature:
                          type: number
                          example: 0.2
                        isActive:
                          type: boolean
                          example: true
                        notes:
                          type: string
                        createdBy:
                          type: string
                          nullable: true
                          example: 65a1b2c3d4e5f6a7b8c9d0e4
                        updatedBy:
                          type: string
                          nullable: true
                          example: 65a1b2c3d4e5f6a7b8c9d0e4
                        createdAt:
                          type: string
                          format: date-time
                          example: '2026-01-15T10:15:00.000Z'
                          nullable: true
                        updatedAt:
                          type: string
                          format: date-time
                          example: '2026-01-15T10:15:00.000Z'
                          nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List the versions of an AI prompt (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/prompts/{purpose}/active":
    get:
      description: 'Returns the prompt the pipeline uses for one purpose: the active
        stored version, or the code default when none is stored. The result is cached
        for up to a minute and has a different shape from a stored version (the tool
        fields are grouped under `tool`). An unknown `purpose` returns 400. Super
        admins only.'
      operationId: AiPromptAdminController_getActive
      parameters:
      - name: purpose
        required: true
        in: path
        schema:
          enum:
          - chunk_summary
          - period_summary
          - conversation_summary
          - case_summary
          - case_context_filing_extraction
          - relevance_gate
          - evidentiary_map_element
          - tag_suggestion
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      purpose:
                        type: string
                        enum:
                        - chunk_summary
                        - period_summary
                        - conversation_summary
                        - case_summary
                        - case_context_filing_extraction
                        - relevance_gate
                        - evidentiary_map_element
                        - tag_suggestion
                        example: case_summary
                      versionSlug:
                        type: string
                        example: case_summary.v2
                      systemPrompt:
                        type: string
                      tool:
                        type: object
                        properties:
                          name:
                            type: string
                          description:
                            type: string
                          input_schema:
                            type: object
                      model:
                        type: string
                        example: sonnet
                      maxTokens:
                        type: number
                        example: 1024
                      temperature:
                        type: number
                        example: 0.2
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the active AI prompt (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/prompts/{purpose}/versions/{versionSlug}":
    get:
      description: Returns one stored version of a prompt purpose. Returns 404 when
        the version does not exist. An unknown `purpose` returns 400. Super admins
        only.
      operationId: AiPromptAdminController_getVersion
      parameters:
      - name: purpose
        required: true
        in: path
        schema:
          enum:
          - chunk_summary
          - period_summary
          - conversation_summary
          - case_summary
          - case_context_filing_extraction
          - relevance_gate
          - evidentiary_map_element
          - tag_suggestion
          type: string
      - name: versionSlug
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      purpose:
                        type: string
                        enum:
                        - chunk_summary
                        - period_summary
                        - conversation_summary
                        - case_summary
                        - case_context_filing_extraction
                        - relevance_gate
                        - evidentiary_map_element
                        - tag_suggestion
                        example: case_summary
                      versionSlug:
                        type: string
                        example: case_summary.v2
                      systemPrompt:
                        type: string
                      toolName:
                        type: string
                      toolDescription:
                        type: string
                      toolInputSchema:
                        type: object
                        description: JSON Schema of the tool input.
                      model:
                        type: string
                        example: sonnet
                      maxTokens:
                        type: number
                        example: 1024
                      temperature:
                        type: number
                        example: 0.2
                      isActive:
                        type: boolean
                        example: true
                      notes:
                        type: string
                      createdBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      updatedBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get one AI prompt version (internal)
      tags:
      - ai-admin
      x-internal: true
    patch:
      description: Edits the system prompt, tool description or input schema, model,
        token limit, temperature or notes of a version without changing its `versionSlug`,
        so meant for small fixes; use a new draft for material changes. Renaming or
        removing top-level tool input properties returns 400; an unknown version returns
        404. An unknown `purpose` returns 400. Super admins only.
      operationId: AiPromptAdminController_updateVersion
      parameters:
      - name: purpose
        required: true
        in: path
        schema:
          enum:
          - chunk_summary
          - period_summary
          - conversation_summary
          - case_summary
          - case_context_filing_extraction
          - relevance_gate
          - evidentiary_map_element
          - tag_suggestion
          type: string
      - name: versionSlug
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiPromptUpdateDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      purpose:
                        type: string
                        enum:
                        - chunk_summary
                        - period_summary
                        - conversation_summary
                        - case_summary
                        - case_context_filing_extraction
                        - relevance_gate
                        - evidentiary_map_element
                        - tag_suggestion
                        example: case_summary
                      versionSlug:
                        type: string
                        example: case_summary.v2
                      systemPrompt:
                        type: string
                      toolName:
                        type: string
                      toolDescription:
                        type: string
                      toolInputSchema:
                        type: object
                        description: JSON Schema of the tool input.
                      model:
                        type: string
                        example: sonnet
                      maxTokens:
                        type: number
                        example: 1024
                      temperature:
                        type: number
                        example: 0.2
                      isActive:
                        type: boolean
                        example: true
                      notes:
                        type: string
                      createdBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      updatedBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Edit an AI prompt version in place (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/prompts/{purpose}/drafts":
    post:
      description: Creates an inactive version with the next `versionSlug` for the
        purpose, copied from `seedFromVersionSlug` (or the active version, or the
        code default) with the given changes applied. It takes no traffic until activated.
        Renaming or removing top-level tool input properties returns 400. An unknown
        `purpose` returns 400. Super admins only.
      operationId: AiPromptAdminController_createDraft
      parameters:
      - name: purpose
        required: true
        in: path
        schema:
          enum:
          - chunk_summary
          - period_summary
          - conversation_summary
          - case_summary
          - case_context_filing_extraction
          - relevance_gate
          - evidentiary_map_element
          - tag_suggestion
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AiPromptCreateDraftDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      purpose:
                        type: string
                        enum:
                        - chunk_summary
                        - period_summary
                        - conversation_summary
                        - case_summary
                        - case_context_filing_extraction
                        - relevance_gate
                        - evidentiary_map_element
                        - tag_suggestion
                        example: case_summary
                      versionSlug:
                        type: string
                        example: case_summary.v2
                      systemPrompt:
                        type: string
                      toolName:
                        type: string
                      toolDescription:
                        type: string
                      toolInputSchema:
                        type: object
                        description: JSON Schema of the tool input.
                      model:
                        type: string
                        example: sonnet
                      maxTokens:
                        type: number
                        example: 1024
                      temperature:
                        type: number
                        example: 0.2
                      isActive:
                        type: boolean
                        example: true
                      notes:
                        type: string
                      createdBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      updatedBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create an AI prompt draft (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/prompts/{purpose}/versions/{versionSlug}/activate":
    post:
      description: Makes one version the active prompt for its purpose and deactivates
        the previous one; activating the active version changes nothing. Returns 404
        when the version does not exist. An unknown `purpose` returns 400. Super admins
        only.
      operationId: AiPromptAdminController_activate
      parameters:
      - name: purpose
        required: true
        in: path
        schema:
          enum:
          - chunk_summary
          - period_summary
          - conversation_summary
          - case_summary
          - case_context_filing_extraction
          - relevance_gate
          - evidentiary_map_element
          - tag_suggestion
          type: string
      - name: versionSlug
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      purpose:
                        type: string
                        enum:
                        - chunk_summary
                        - period_summary
                        - conversation_summary
                        - case_summary
                        - case_context_filing_extraction
                        - relevance_gate
                        - evidentiary_map_element
                        - tag_suggestion
                        example: case_summary
                      versionSlug:
                        type: string
                        example: case_summary.v2
                      systemPrompt:
                        type: string
                      toolName:
                        type: string
                      toolDescription:
                        type: string
                      toolInputSchema:
                        type: object
                        description: JSON Schema of the tool input.
                      model:
                        type: string
                        example: sonnet
                      maxTokens:
                        type: number
                        example: 1024
                      temperature:
                        type: number
                        example: 0.2
                      isActive:
                        type: boolean
                        example: true
                      notes:
                        type: string
                      createdBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      updatedBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Activate an AI prompt version (internal)
      tags:
      - ai-admin
      x-internal: true
  "/ai/admin/prompts/{purpose}/reset-to-code-default":
    post:
      description: Restores the code-default version of the purpose (creating it when
        missing) to the default text and settings, activates it and returns it. An
        unknown `purpose` returns 400. Super admins only.
      operationId: AiPromptAdminController_resetToCodeDefault
      parameters:
      - name: purpose
        required: true
        in: path
        schema:
          enum:
          - chunk_summary
          - period_summary
          - conversation_summary
          - case_summary
          - case_context_filing_extraction
          - relevance_gate
          - evidentiary_map_element
          - tag_suggestion
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0e1
                      purpose:
                        type: string
                        enum:
                        - chunk_summary
                        - period_summary
                        - conversation_summary
                        - case_summary
                        - case_context_filing_extraction
                        - relevance_gate
                        - evidentiary_map_element
                        - tag_suggestion
                        example: case_summary
                      versionSlug:
                        type: string
                        example: case_summary.v2
                      systemPrompt:
                        type: string
                      toolName:
                        type: string
                      toolDescription:
                        type: string
                      toolInputSchema:
                        type: object
                        description: JSON Schema of the tool input.
                      model:
                        type: string
                        example: sonnet
                      maxTokens:
                        type: number
                        example: 1024
                      temperature:
                        type: number
                        example: 0.2
                      isActive:
                        type: boolean
                        example: true
                      notes:
                        type: string
                      createdBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      updatedBy:
                        type: string
                        nullable: true
                        example: 65a1b2c3d4e5f6a7b8c9d0e4
                      createdAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                      updatedAt:
                        type: string
                        format: date-time
                        example: '2026-01-15T10:15:00.000Z'
                        nullable: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Reset an AI prompt to its code default (internal)
      tags:
      - ai-admin
      x-internal: true
  "/financial-data/sandbox/public_token/create":
    post:
      description: 'Developer tool: asks the Plaid sandbox for a public token (what
        Plaid Link hands back after someone connects a bank) for a test institution
        and set of products. Returns 400 unless the server is pointed at the Plaid
        sandbox.'
      operationId: FinancialDataController_createSandboxPublicToken
      parameters: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SandboxPublicTokenDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/SandboxPublicTokenResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Create a Plaid sandbox public token (internal)
      tags:
      - financial-data
      x-internal: true
  "/financial-data/investment-transactions/get":
    get:
      description: Returns one page of investment transactions (buys, sells, dividends
        and so on) for a financial account, with ticker, security name and attached
        tags; filter by date range, type or tags. On a case shared with opposing counsel,
        an account the caller may not see returns an empty page, and attachedTags
        leave out tags the other side added by hand. Returns 404 when a Plaid item
        or account id sent belongs to another case.
      operationId: FinancialDataController_getInvestmentTransactions
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: caseId
        required: true
        in: query
        description: Case ID this financial data belongs to.
        schema:
          type: string
      - name: financialAccountId
        required: true
        in: query
        description: Internal FinancialAccount document ID.
        schema:
          type: string
      - name: startDate
        required: false
        in: query
        description: Start date filter (YYYY-MM-DD, inclusive).
        schema:
          example: '2024-01-01'
          type: string
      - name: endDate
        required: false
        in: query
        description: End date filter (YYYY-MM-DD, inclusive).
        schema:
          example: '2024-12-31'
          type: string
      - name: transactionType
        required: false
        in: query
        description: Filter by transaction type (e.g. buy, sell, dividend, transfer).
        schema:
          example: sell
          type: string
      - name: tagIds
        required: false
        in: query
        description: Filter by tag IDs (reserved for future use).
        schema:
          type: array
          items:
            type: string
      - name: sortBy
        required: false
        in: query
        description: Field to sort by.
        schema:
          default: date
          type: string
          enum:
          - date
      - name: sortOrder
        required: false
        in: query
        description: Sort direction.
        schema:
          default: desc
          type: string
          enum:
          - asc
          - desc
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/InvestmentTransactionDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List investment transactions
      tags:
      - financial-data
  "/financial-data/plaid-item/{plaidItemId}/status":
    get:
      description: Calls Plaid's /item/get for the connected bank login and returns
        Plaid's response unchanged. Case owners and admins only; returns 409 if the
        organization has not finished Plaid onboarding.
      operationId: FinancialDataController_getPlaidItemStatus
      parameters:
      - name: plaidItemId
        required: true
        in: path
        description: PlaidItem MongoDB ID
        schema:
          type: string
      - name: caseId
        required: true
        in: query
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: Plaid's /item/get response, passed through unchanged.
                      Its shape is defined by Plaid, not by this API.
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a Plaid item's live status
      tags:
      - financial-data
  "/financial-data/plaid-item/{plaidItemId}/remove":
    delete:
      description: Revokes the bank connection on Plaid (/item/remove, with an optional
        reason), marks the item inactive and records a case event. Case owners and
        admins only, not available to opposing counsel; returns 400 if the item is
        already inactive.
      operationId: FinancialDataController_removePlaidItem
      parameters:
      - name: plaidItemId
        required: true
        in: path
        description: PlaidItem MongoDB ID
        schema:
          type: string
      - name: caseId
        required: true
        in: query
        schema:
          type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RemovePlaidItemDto"
      responses:
        '204':
          description: Item disconnected. No response body.
      security:
      - access-token: []
      summary: Disconnect a Plaid item
      tags:
      - financial-data
  "/financial-data/accounts/data":
    get:
      description: Returns one page of financial accounts with balances and the provider
        they came from, for one Plaid item or, when none is given, every item linked
        to the case; filter by account type. On a case shared with opposing counsel,
        accounts the caller may not see are excluded; ocSharedStatus (the account's
        sharing status) is filled in for opposing counsel, who can also filter by
        it, and is null for everyone else. Returns 404 when a Plaid item or account
        id sent belongs to another case.
      operationId: FinancialDataController_getAccountsData
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: caseId
        required: true
        in: query
        description: Case ID this financial data belongs to.
        schema:
          type: string
      - name: plaidItemId
        required: false
        in: query
        description: Internal PlaidItem document ID returned by the public token exchange
          endpoint. Optional for extraction code users — resolved automatically from
          caseId.
        schema:
          type: string
      - name: accountType
        required: false
        in: query
        description: Filter accounts by type.
        schema:
          type: string
          enum:
          - depository
          - credit
          - loan
          - investment
          - brokerage
          - other
      - name: financialAccountId
        required: false
        in: query
        description: Filter by a specific financial account ID.
        schema:
          type: string
      - name: sharedStatus
        required: false
        in: query
        description: Opposing-council only. Narrows to accounts in one sharing state;
          ignored for OL/OG and extraction-code callers.
        schema:
          type: string
          enum:
          - shared
          - pending
          - not_gated
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/AccountDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List financial accounts
      tags:
      - financial-data
  "/financial-data/investments/holdings/get":
    get:
      description: Returns one page of investment holdings (security, quantity, price,
        value, cost basis) for an account, a Plaid item, or every item linked to the
        case, sorted by value; filter by security type or tags. On a case shared with
        opposing counsel, accounts the caller may not see are excluded, and attachedTags
        leave out tags the other side added by hand. Returns 404 when a Plaid item
        or account id sent belongs to another case.
      operationId: FinancialDataController_getInvestmentHoldings
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: caseId
        required: true
        in: query
        description: Case ID this financial data belongs to.
        schema:
          type: string
      - name: plaidItemId
        required: false
        in: query
        description: Internal PlaidItem document ID. Optional for extraction code
          users — resolved automatically from caseId.
        schema:
          type: string
      - name: type
        required: false
        in: query
        description: Filter by security type (e.g. equity, etf, derivative).
        schema:
          type: string
      - name: financialAccountId
        required: false
        in: query
        description: Filter by a specific financial account ID.
        schema:
          type: string
      - name: sortBy
        required: false
        in: query
        description: Field to sort by.
        schema:
          default: totalValue
          type: string
          enum:
          - totalValue
      - name: sortOrder
        required: false
        in: query
        description: Sort direction.
        schema:
          default: desc
          type: string
          enum:
          - asc
          - desc
      - name: tagIds
        required: false
        in: query
        description: Filter holdings that have at least one of the provided tag IDs.
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/HoldingDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List investment holdings
      tags:
      - financial-data
  "/financial-data/liabilities/get":
    get:
      description: Returns one page of liabilities for an account, a Plaid item, or
        every item linked to the case, split into credit card, mortgage and student
        loan lists; filter by type, overdue or tags. On a case shared with opposing
        counsel, accounts the caller may not see are excluded, and attachedTags leave
        out tags the other side added by hand. Returns 404 when a Plaid item or account
        id sent belongs to another case.
      operationId: FinancialDataController_getLiabilities
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: caseId
        required: true
        in: query
        description: Case ID this financial data belongs to.
        schema:
          type: string
      - name: plaidItemId
        required: false
        in: query
        description: Internal PlaidItem document ID. Optional for extraction code
          users — resolved automatically from caseId.
        schema:
          type: string
      - name: liabilityType
        required: false
        in: query
        description: Filter by liability type.
        schema:
          type: string
          enum:
          - credit
          - mortgage
          - student
      - name: isOverdue
        required: false
        in: query
        description: Filter by overdue status. Applies to credit and student liabilities
          only.
        schema:
          type: boolean
      - name: financialAccountId
        required: false
        in: query
        description: Filter by a specific financial account ID.
        schema:
          type: string
      - name: tagIds
        required: false
        in: query
        description: Filter liabilities that have at least one of the provided tag
          IDs.
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/GetLiabilitiesResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List liabilities
      tags:
      - financial-data
  "/financial-data/plaid-item/{plaidItemId}/resync":
    post:
      description: 'Support tool: resets the chosen Plaid products (all of transactions,
        recurring transactions, investments and liabilities when none are given) and
        queues fresh sync jobs for the item. Case owners and admins only, not available
        to opposing counsel; returns 400 if the item is inactive.'
      operationId: FinancialSyncRetryController_resyncPlaidItem
      parameters:
      - name: plaidItemId
        required: true
        in: path
        description: PlaidItem MongoDB ID
        schema:
          type: string
      - name: caseId
        required: true
        in: query
        schema:
          type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RetrySyncDto"
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - statusCode
                - timestamp
                properties:
                  statusCode:
                    type: number
                    example: 202
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Re-run Plaid syncs for an item (internal)
      tags:
      - financial-data
      x-internal: true
  "/financial-data/transactions/get":
    get:
      description: Returns one page of transactions for a financial account, with
        amounts as positive numbers plus a debit/credit type, the effective category
        (your override if set) and attached tags. Filter by category, subcategory,
        name, merchant, date range, amount range, debit/credit or tags. On a case
        shared with opposing counsel, an account the caller may not see returns an
        empty page, and attachedTags leave out tags the other side added by hand.
        Returns 404 when a Plaid item or account id sent belongs to another case.
      operationId: TransactionsController_getTransactions
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: caseId
        required: true
        in: query
        description: Case ID this financial data belongs to.
        schema:
          type: string
      - name: plaidItemId
        required: false
        in: query
        description: Internal PlaidItem document ID. Optional for extraction code
          users — resolved automatically from caseId.
        schema:
          type: string
      - name: financialAccountId
        required: true
        in: query
        description: Internal FinancialAccount document ID.
        schema:
          type: string
      - name: category
        required: false
        in: query
        description: Filter by transaction category (categoryPrimary). Case-insensitive
          partial match.
        schema:
          type: string
      - name: subcategory
        required: false
        in: query
        description: Filter by transaction subcategory (Plaid categoryDetailed). Case-insensitive
          partial match. Only matches transactions whose primary category has not
          been overridden.
        schema:
          type: string
      - name: name
        required: false
        in: query
        description: Filter by transaction name. Case-insensitive partial match.
        schema:
          type: string
      - name: merchant
        required: false
        in: query
        description: Filter to a single merchant. Exact match on merchantName, as
          returned by the transaction list. Transactions with no merchant name never
          match.
        schema:
          type: string
      - name: startDate
        required: false
        in: query
        description: 'Filter transactions from this date (inclusive). Format: YYYY-MM-DD.'
        schema:
          type: string
      - name: endDate
        required: false
        in: query
        description: 'Filter transactions up to this date (inclusive). Format: YYYY-MM-DD.'
        schema:
          type: string
      - name: minAmount
        required: false
        in: query
        description: Filter transactions with amount >= minAmount.
        schema:
          type: number
      - name: maxAmount
        required: false
        in: query
        description: Filter transactions with amount <= maxAmount (absolute value).
        schema:
          type: number
      - name: transactionType
        required: false
        in: query
        description: Filter by transaction type. debit = money out (positive in Plaid),
          credit = money in (negative in Plaid).
        schema:
          type: string
          enum:
          - debit
          - credit
      - name: sortBy
        required: false
        in: query
        description: Field to sort by.
        schema:
          default: date
          type: string
          enum:
          - date
          - amount
          - category
      - name: sortOrder
        required: false
        in: query
        description: Sort direction.
        schema:
          default: desc
          type: string
          enum:
          - asc
          - desc
      - name: tagIds
        required: false
        in: query
        description: Filter transactions that have all of the provided tag IDs.
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/TransactionDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List transactions
      tags:
      - financial-data
  "/financial-data/account/{accountId}/transactions/summary":
    get:
      description: Returns debit and credit totals and a transaction count per category
        for one account, plus overall totals; optionally filter by category text and
        date range, or nest subcategories with groupBy=detailed. On a case shared
        with opposing counsel, an account the caller may not see returns an empty
        summary. Returns 404 when a Plaid item or account id sent belongs to another
        case.
      operationId: TransactionsController_getTransactionsSummary
      parameters:
      - name: accountId
        required: true
        in: path
        description: Financial account ID
        schema:
          type: string
      - name: caseId
        required: true
        in: query
        description: Case ID this financial data belongs to.
        schema:
          type: string
      - name: plaidItemId
        required: false
        in: query
        description: Internal PlaidItem document ID. Optional — resolved automatically
          from accountId.
        schema:
          type: string
      - name: category
        required: false
        in: query
        description: Filter by category (case-insensitive partial match on categoryPrimary).
          If omitted, all categories are included.
        schema:
          type: string
      - name: startDate
        required: false
        in: query
        description: 'Filter transactions from this date (inclusive). Format: YYYY-MM-DD.'
        schema:
          example: '2024-01-01'
          type: string
      - name: endDate
        required: false
        in: query
        description: 'Filter transactions up to this date (inclusive). Format: YYYY-MM-DD.'
        schema:
          example: '2024-12-31'
          type: string
      - name: groupBy
        required: false
        in: query
        description: Category granularity. `detailed` nests Plaid subcategories under
          each category; `primary` (default) returns the category breakdown only.
        schema:
          default: primary
          type: string
          enum:
          - primary
          - detailed
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/GetTransactionsSummaryResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Summarize an account's transactions by category
      tags:
      - financial-data
  "/financial-data/case/{caseId}/transactions/monthly-category-summary":
    get:
      description: Returns debit and credit totals and counts per category for each
        month, across all the case's accounts or the ones given, with optional category
        and date filters. On a case shared with opposing counsel, only accounts the
        caller may see are counted. Returns 403 while the monthly category summary
        feature is turned off.
      operationId: TransactionsController_getMonthlyCategorySummary
      parameters:
      - name: caseId
        required: true
        in: path
        description: Case ID
        schema:
          type: string
      - name: accountIds
        required: false
        in: query
        description: Restrict the aggregation to these FinancialAccount IDs (all must
          belong to the case). Omit to include every linked account in the case. Accepts
          a repeated query param or a comma-separated list.
        schema:
          type: array
          items:
            type: string
      - name: category
        required: false
        in: query
        description: Filter by one or more categories (case-insensitive partial match
          on categoryPrimary; matches any of them). Accepts a repeated query param
          or a comma-separated list. If omitted, every category is returned per month.
        schema:
          type: array
          items:
            type: string
      - name: startDate
        required: false
        in: query
        description: 'Include transactions from this date (inclusive). Format: YYYY-MM-DD.'
        schema:
          example: '2024-01-01'
          type: string
      - name: endDate
        required: false
        in: query
        description: 'Include transactions up to this date (inclusive). Format: YYYY-MM-DD.'
        schema:
          example: '2024-12-31'
          type: string
      - name: groupBy
        required: false
        in: query
        description: Category granularity. `detailed` nests Plaid subcategories under
          each broad category; `primary` (default) returns the broad-category breakdown
          only.
        schema:
          default: primary
          type: string
          enum:
          - primary
          - detailed
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/GetMonthlyCategorySummaryResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Summarize a case's transactions by month and category
      tags:
      - financial-data
  "/financial-data/case/{caseId}/transactions/monthly-category-summary/export":
    get:
      description: Same data as the monthly category summary, returned directly as
        a CSV file attachment instead of JSON. On a case shared with opposing counsel,
        only accounts the caller may see are counted. Returns 403 while the monthly
        category summary feature is turned off.
      operationId: TransactionsController_exportMonthlyCategorySummary
      parameters:
      - name: caseId
        required: true
        in: path
        description: Case ID
        schema:
          type: string
      - name: accountIds
        required: false
        in: query
        description: Restrict the aggregation to these FinancialAccount IDs (all must
          belong to the case). Omit to include every linked account in the case. Accepts
          a repeated query param or a comma-separated list.
        schema:
          type: array
          items:
            type: string
      - name: category
        required: false
        in: query
        description: Filter by one or more categories (case-insensitive partial match
          on categoryPrimary; matches any of them). Accepts a repeated query param
          or a comma-separated list. If omitted, every category is returned per month.
        schema:
          type: array
          items:
            type: string
      - name: startDate
        required: false
        in: query
        description: 'Include transactions from this date (inclusive). Format: YYYY-MM-DD.'
        schema:
          example: '2024-01-01'
          type: string
      - name: endDate
        required: false
        in: query
        description: 'Include transactions up to this date (inclusive). Format: YYYY-MM-DD.'
        schema:
          example: '2024-12-31'
          type: string
      - name: groupBy
        required: false
        in: query
        description: Category granularity. `detailed` nests Plaid subcategories under
          each broad category; `primary` (default) returns the broad-category breakdown
          only.
        schema:
          default: primary
          type: string
          enum:
          - primary
          - detailed
      responses:
        '200':
          description: CSV file (text/csv attachment). Not wrapped in the JSON envelope.
          content:
            text/csv:
              schema:
                type: string
      security:
      - access-token: []
      summary: Download the monthly category summary as CSV
      tags:
      - financial-data
  "/financial-data/case/{caseId}/transactions/categories":
    get:
      description: Returns each category, with its subcategories, that has transactions
        across the case's accounts or the ones given, with a count at both levels
        and the account ids used; optional date range. On a case shared with opposing
        counsel, only accounts the caller may see are counted. Returns 403 while the
        monthly category summary feature is turned off.
      operationId: TransactionsController_getCaseCategories
      parameters:
      - name: caseId
        required: true
        in: path
        description: Case ID
        schema:
          type: string
      - name: accountIds
        required: false
        in: query
        description: Restrict the lookup to these FinancialAccount IDs (all must belong
          to the case). Omit to include every linked account in the case. Accepts
          a repeated query param or a comma-separated list.
        schema:
          type: array
          items:
            type: string
      - name: startDate
        required: false
        in: query
        description: 'Only consider transactions from this date (inclusive). Format:
          YYYY-MM-DD.'
        schema:
          example: '2024-01-01'
          type: string
      - name: endDate
        required: false
        in: query
        description: 'Only consider transactions up to this date (inclusive). Format:
          YYYY-MM-DD.'
        schema:
          example: '2024-12-31'
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/GetCaseCategoriesResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a case's transaction categories
      tags:
      - financial-data
  "/financial-data/case/{caseId}/transactions/{transactionId}/category":
    patch:
      description: Sets your own category on a transaction in the case (Plaid's original
        is kept); omit the category or send null to go back to Plaid's. Can also apply
        the change to the case's other transactions from the same merchant. Returns
        the effective category and how many transactions changed. Not available to
        opposing counsel.
      operationId: TransactionsController_updateTransactionCategory
      parameters:
      - name: caseId
        required: true
        in: path
        description: Case ID
        schema:
          type: string
      - name: transactionId
        required: true
        in: path
        description: Transaction ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/UpdateTransactionCategoryDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/UpdateTransactionCategoryResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Change a transaction's category
      tags:
      - financial-data
  "/financial-data/recurring-transactions/get":
    get:
      description: Returns one page of recurring transaction streams (regular payments
        or deposits Plaid detected) for a financial account, with amounts, frequency,
        dates and attached tags; filter by stream type, active flag, frequency or
        tags. On a case shared with opposing counsel, an account the caller may not
        see returns an empty page, and attachedTags leave out tags the other side
        added by hand.
      operationId: TransactionsController_getRecurringTransactionsStream
      parameters:
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: caseId
        required: true
        in: query
        description: Case ID this financial data belongs to.
        schema:
          type: string
      - name: plaidItemId
        required: false
        in: query
        description: Internal PlaidItem document ID. Optional for extraction code
          users — resolved automatically from caseId.
        schema:
          type: string
      - name: financialAccountId
        required: true
        in: query
        description: Internal FinancialAccount document ID.
        schema:
          type: string
      - name: type
        required: false
        in: query
        description: Filter by stream type.
        schema:
          type: string
          enum:
          - inflow
          - outflow
      - name: isActive
        required: false
        in: query
        description: Filter by active status.
        schema:
          type: boolean
      - name: frequency
        required: false
        in: query
        description: Filter by frequency (e.g. WEEKLY, MONTHLY).
        schema:
          type: string
      - name: sortBy
        required: false
        in: query
        description: Field to sort by.
        schema:
          default: lastDate
          type: string
          enum:
          - averageAmount
          - lastDate
          - firstDate
      - name: sortOrder
        required: false
        in: query
        description: Sort direction.
        schema:
          default: desc
          type: string
          enum:
          - asc
          - desc
      - name: tagIds
        required: false
        in: query
        description: Filter recurring transactions that have at least one of the provided
          tag IDs.
        schema:
          type: array
          items:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/RecurringTransactionStreamDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List recurring transactions
      tags:
      - financial-data
  "/financial-data/transaction/{transactionId}/tags":
    post:
      description: Adds tags to a transaction by name or id; names that do not exist
        yet are created as case tags. The transaction must belong to the case. Returns
        the full transaction row after the change; on a case shared with opposing
        counsel, attachedTags leave out tags the other side added by hand.
      operationId: TransactionTagsController_addTags
      parameters:
      - name: transactionId
        required: true
        in: path
        description: Transaction ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddTransactionTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/TransactionDataEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag a transaction
      tags:
      - financial-data
    delete:
      description: Removes the given tags (by name or id) from a transaction in the
        case and returns the full transaction row after the change. Returns 400 if
        any of them is not on the transaction. On a case shared with opposing counsel,
        attachedTags leave out tags the other side added by hand.
      operationId: TransactionTagsController_removeTags
      parameters:
      - name: transactionId
        required: true
        in: path
        description: Transaction ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RemoveTransactionTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/TransactionDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove tags from a transaction
      tags:
      - financial-data
  "/financial-data/transactions/tags/bulk":
    post:
      description: Adds the same tags to every listed transaction and returns the
        updated rows in a data array. If any transaction fails, the request returns
        400 with the errors, but tags already added to the others stay. On a case
        shared with opposing counsel, attachedTags leave out tags the other side added
        by hand.
      operationId: TransactionTagsController_bulkAddTags
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BulkAddTransactionTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          "$ref": "#/components/schemas/TransactionDataEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag several transactions
      tags:
      - financial-data
  "/financial-data/recurring-transaction/{streamId}/tags":
    post:
      description: Adds tags to a recurring transaction by name or id; names that
        do not exist yet are created as case tags. The recurring transaction must
        belong to the case. Returns the full recurring transaction row after the change;
        on a case shared with opposing counsel, attachedTags leave out tags the other
        side added by hand.
      operationId: RecurringTransactionTagsController_addTags
      parameters:
      - name: streamId
        required: true
        in: path
        description: Recurring transaction stream ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddTransactionTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RecurringTransactionStreamDataEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag a recurring transaction
      tags:
      - financial-data
    delete:
      description: Removes the given tags (by name or id) from a recurring transaction
        in the case and returns the full recurring transaction row after the change.
        Returns 400 if any of them is not on the recurring transaction. On a case
        shared with opposing counsel, attachedTags leave out tags the other side added
        by hand.
      operationId: RecurringTransactionTagsController_removeTags
      parameters:
      - name: streamId
        required: true
        in: path
        description: Recurring transaction stream ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RemoveTransactionTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/RecurringTransactionStreamDataEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove tags from a recurring transaction
      tags:
      - financial-data
  "/financial-data/recurring-transactions/tags/bulk":
    post:
      description: Adds the same tags to every listed recurring transaction and returns
        the updated rows in a data array. If any recurring transaction fails, the
        request returns 400 with the errors, but tags already added to the others
        stay. On a case shared with opposing counsel, attachedTags leave out tags
        the other side added by hand.
      operationId: RecurringTransactionTagsController_bulkAddTags
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BulkAddRecurringTransactionTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          "$ref": "#/components/schemas/RecurringTransactionStreamDataEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag several recurring transactions
      tags:
      - financial-data
  "/financial-data/liability/{liabilityId}/tags":
    post:
      description: Adds tags to a liability by name or id; names that do not exist
        yet are created as case tags. The liability must belong to the case. Returns
        the liability's id, type, dates and tags after the change; on a case shared
        with opposing counsel, attachedTags leave out tags the other side added by
        hand.
      operationId: LiabilityTagsController_addTags
      parameters:
      - name: liabilityId
        required: true
        in: path
        description: Liability ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddTransactionTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      financialAccount:
                        type: string
                      plaidAccountId:
                        type: string
                      liabilityType:
                        type: string
                        example: credit
                      createdAt:
                        type: string
                        format: date-time
                      updatedAt:
                        type: string
                        format: date-time
                      tags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/TransactionTagData"
                      attachedTags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/AttachedTagResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag a liability
      tags:
      - financial-data
    delete:
      description: Removes the given tags (by name or id) from a liability in the
        case and returns the liability's id, type, dates and tags after the change.
        Returns 400 if any of them is not on the liability. On a case shared with
        opposing counsel, attachedTags leave out tags the other side added by hand.
      operationId: LiabilityTagsController_removeTags
      parameters:
      - name: liabilityId
        required: true
        in: path
        description: Liability ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RemoveTransactionTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      financialAccount:
                        type: string
                      plaidAccountId:
                        type: string
                      liabilityType:
                        type: string
                        example: credit
                      createdAt:
                        type: string
                        format: date-time
                      updatedAt:
                        type: string
                        format: date-time
                      tags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/TransactionTagData"
                      attachedTags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/AttachedTagResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove tags from a liability
      tags:
      - financial-data
  "/financial-data/liabilities/tags/bulk":
    post:
      description: Adds the same tags to every listed liability and returns the updated
        rows in a data array. If any liability fails, the request returns 400 with
        the errors, but tags already added to the others stay. On a case shared with
        opposing counsel, attachedTags leave out tags the other side added by hand.
      operationId: LiabilityTagsController_bulkAddTags
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BulkAddLiabilityTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                            financialAccount:
                              type: string
                            plaidAccountId:
                              type: string
                            liabilityType:
                              type: string
                              example: credit
                            createdAt:
                              type: string
                              format: date-time
                            updatedAt:
                              type: string
                              format: date-time
                            tags:
                              type: array
                              items:
                                "$ref": "#/components/schemas/TransactionTagData"
                            attachedTags:
                              type: array
                              items:
                                "$ref": "#/components/schemas/AttachedTagResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag several liabilities
      tags:
      - financial-data
  "/financial-data/holding/{holdingId}/tags":
    post:
      description: Adds tags to a holding by name or id; names that do not exist yet
        are created as case tags. The holding must belong to the case. Returns the
        holding's id, dates and tags after the change; on a case shared with opposing
        counsel, attachedTags leave out tags the other side added by hand.
      operationId: HoldingTagsController_addTags
      parameters:
      - name: holdingId
        required: true
        in: path
        description: Holding ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddTransactionTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      financialAccount:
                        type: string
                      plaidSecurityId:
                        type: string
                      createdAt:
                        type: string
                        format: date-time
                      updatedAt:
                        type: string
                        format: date-time
                      tags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/TransactionTagData"
                      attachedTags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/AttachedTagResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag a holding
      tags:
      - financial-data
    delete:
      description: Removes the given tags (by name or id) from a holding in the case
        and returns the holding's id, dates and tags after the change. Returns 400
        if any of them is not on the holding. On a case shared with opposing counsel,
        attachedTags leave out tags the other side added by hand.
      operationId: HoldingTagsController_removeTags
      parameters:
      - name: holdingId
        required: true
        in: path
        description: Holding ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RemoveTransactionTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      financialAccount:
                        type: string
                      plaidSecurityId:
                        type: string
                      createdAt:
                        type: string
                        format: date-time
                      updatedAt:
                        type: string
                        format: date-time
                      tags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/TransactionTagData"
                      attachedTags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/AttachedTagResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove tags from a holding
      tags:
      - financial-data
  "/financial-data/holdings/tags/bulk":
    post:
      description: Adds the same tags to every listed holding and returns the updated
        rows in a data array. If any holding fails, the request returns 400 with the
        errors, but tags already added to the others stay. On a case shared with opposing
        counsel, attachedTags leave out tags the other side added by hand.
      operationId: HoldingTagsController_bulkAddTags
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BulkAddHoldingTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                            financialAccount:
                              type: string
                            plaidSecurityId:
                              type: string
                            createdAt:
                              type: string
                              format: date-time
                            updatedAt:
                              type: string
                              format: date-time
                            tags:
                              type: array
                              items:
                                "$ref": "#/components/schemas/TransactionTagData"
                            attachedTags:
                              type: array
                              items:
                                "$ref": "#/components/schemas/AttachedTagResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag several holdings
      tags:
      - financial-data
  "/financial-data/investment-transaction/{investmentTransactionId}/tags":
    post:
      description: Adds tags to an investment transaction by name or id; names that
        do not exist yet are created as case tags. The investment transaction must
        belong to the case. Returns the transaction's core fields (date, type, name,
        amount) and tags after the change; on a case shared with opposing counsel,
        attachedTags leave out tags the other side added by hand.
      operationId: InvestmentTransactionTagsController_addTags
      parameters:
      - name: investmentTransactionId
        required: true
        in: path
        description: Investment transaction ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/AddTransactionTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      financialAccountId:
                        type: string
                      date:
                        type: string
                        format: date-time
                      type:
                        type: string
                      subtype:
                        type: string
                      name:
                        type: string
                      amount:
                        type: number
                      createdAt:
                        type: string
                        format: date-time
                      updatedAt:
                        type: string
                        format: date-time
                      tags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/TransactionTagData"
                      attachedTags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/AttachedTagResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag an investment transaction
      tags:
      - financial-data
    delete:
      description: Removes the given tags (by name or id) from an investment transaction
        in the case and returns the transaction's core fields (date, type, name, amount)
        and tags after the change. Returns 400 if any of them is not on the investment
        transaction. On a case shared with opposing counsel, attachedTags leave out
        tags the other side added by hand.
      operationId: InvestmentTransactionTagsController_removeTags
      parameters:
      - name: investmentTransactionId
        required: true
        in: path
        description: Investment transaction ID
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/RemoveTransactionTagsDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      financialAccountId:
                        type: string
                      date:
                        type: string
                        format: date-time
                      type:
                        type: string
                      subtype:
                        type: string
                      name:
                        type: string
                      amount:
                        type: number
                      createdAt:
                        type: string
                        format: date-time
                      updatedAt:
                        type: string
                        format: date-time
                      tags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/TransactionTagData"
                      attachedTags:
                        type: array
                        items:
                          "$ref": "#/components/schemas/AttachedTagResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove tags from an investment transaction
      tags:
      - financial-data
  "/financial-data/investment-transactions/tags/bulk":
    post:
      description: Adds the same tags to every listed investment transaction and returns
        the updated rows in a data array. If any investment transaction fails, the
        request returns 400 with the errors, but tags already added to the others
        stay. On a case shared with opposing counsel, attachedTags leave out tags
        the other side added by hand.
      operationId: InvestmentTransactionTagsController_bulkAddTags
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BulkAddInvestmentTransactionTagsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                            financialAccountId:
                              type: string
                            date:
                              type: string
                              format: date-time
                            type:
                              type: string
                            subtype:
                              type: string
                            name:
                              type: string
                            amount:
                              type: number
                            createdAt:
                              type: string
                              format: date-time
                            updatedAt:
                              type: string
                              format: date-time
                            tags:
                              type: array
                              items:
                                "$ref": "#/components/schemas/TransactionTagData"
                            attachedTags:
                              type: array
                              items:
                                "$ref": "#/components/schemas/AttachedTagResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Tag several investment transactions
      tags:
      - financial-data
  "/financial-data/account/{accountId}/transactions/export":
    post:
      description: Queues a background job that builds a CSV or PDF of one account's
        transactions (with the given filters) and returns its job id; poll GET /financial-data/export/:jobId
        for the download URL. On a case shared with opposing counsel, an account the
        caller may not see returns 404.
      operationId: FinancialExportController_exportTransactions
      parameters:
      - name: accountId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportTransactionsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - jobId
                    properties:
                      jobId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0f1
                        description: Export job id. Poll GET /financial-data/export/:jobId
                          for status and the download URL.
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export an account's transactions
      tags:
      - financial-data-export
  "/financial-data/plaid-item/{plaidItemId}/transactions/export":
    post:
      description: Queues a background job that builds a CSV or PDF of the transactions
        of every account on the Plaid item (optionally narrowed by account type, with
        the given filters) and returns its job id; poll GET /financial-data/export/:jobId
        for the download URL. On a case shared with opposing counsel, only accounts
        the caller may see are included (404 if none are left).
      operationId: FinancialExportController_exportPlaidItemTransactions
      parameters:
      - name: plaidItemId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportPlaidItemTransactionsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - jobId
                    properties:
                      jobId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0f1
                        description: Export job id. Poll GET /financial-data/export/:jobId
                          for status and the download URL.
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export a Plaid item's transactions
      tags:
      - financial-data-export
  "/financial-data/account/{accountId}/recurring-transactions/export":
    post:
      description: Queues a background job that builds a CSV or PDF of one account's
        recurring transactions (with the given filters) and returns its job id; poll
        GET /financial-data/export/:jobId for the download URL. On a case shared with
        opposing counsel, an account the caller may not see returns 404.
      operationId: FinancialExportController_exportRecurringTransactions
      parameters:
      - name: accountId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportRecurringTransactionsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - jobId
                    properties:
                      jobId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0f1
                        description: Export job id. Poll GET /financial-data/export/:jobId
                          for status and the download URL.
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export an account's recurring transactions
      tags:
      - financial-data-export
  "/financial-data/plaid-item/{plaidItemId}/recurring-transactions/export":
    post:
      description: Queues a background job that builds a CSV or PDF of the recurring
        transactions of every account on the Plaid item (optionally narrowed by account
        type, with the given filters) and returns its job id; poll GET /financial-data/export/:jobId
        for the download URL. On a case shared with opposing counsel, only accounts
        the caller may see are included (404 if none are left).
      operationId: FinancialExportController_exportPlaidItemRecurringTransactions
      parameters:
      - name: plaidItemId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportPlaidItemRecurringTransactionsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - jobId
                    properties:
                      jobId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0f1
                        description: Export job id. Poll GET /financial-data/export/:jobId
                          for status and the download URL.
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export a Plaid item's recurring transactions
      tags:
      - financial-data-export
  "/financial-data/account/{accountId}/holdings/export":
    post:
      description: Queues a background job that builds a CSV or PDF of one account's
        holdings (with the given filters) and returns its job id; poll GET /financial-data/export/:jobId
        for the download URL. On a case shared with opposing counsel, an account the
        caller may not see returns 404.
      operationId: FinancialExportController_exportHoldings
      parameters:
      - name: accountId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportHoldingsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - jobId
                    properties:
                      jobId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0f1
                        description: Export job id. Poll GET /financial-data/export/:jobId
                          for status and the download URL.
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export an account's holdings
      tags:
      - financial-data-export
  "/financial-data/plaid-item/{plaidItemId}/holdings/export":
    post:
      description: Queues a background job that builds a CSV or PDF of the holdings
        of every account on the Plaid item (optionally narrowed by account type, with
        the given filters) and returns its job id; poll GET /financial-data/export/:jobId
        for the download URL. On a case shared with opposing counsel, only accounts
        the caller may see are included (404 if none are left).
      operationId: FinancialExportController_exportPlaidItemHoldings
      parameters:
      - name: plaidItemId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportPlaidItemHoldingsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - jobId
                    properties:
                      jobId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0f1
                        description: Export job id. Poll GET /financial-data/export/:jobId
                          for status and the download URL.
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export a Plaid item's holdings
      tags:
      - financial-data-export
  "/financial-data/account/{accountId}/liabilities/export":
    post:
      description: Queues a background job that builds a CSV or PDF of one account's
        liabilities (with the given filters) and returns its job id; poll GET /financial-data/export/:jobId
        for the download URL. On a case shared with opposing counsel, an account the
        caller may not see returns 404.
      operationId: FinancialExportController_exportLiabilities
      parameters:
      - name: accountId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportLiabilitiesDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - jobId
                    properties:
                      jobId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0f1
                        description: Export job id. Poll GET /financial-data/export/:jobId
                          for status and the download URL.
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export an account's liabilities
      tags:
      - financial-data-export
  "/financial-data/plaid-item/{plaidItemId}/liabilities/export":
    post:
      description: Queues a background job that builds a CSV or PDF of the liabilities
        of every account on the Plaid item (optionally narrowed by account type, with
        the given filters) and returns its job id; poll GET /financial-data/export/:jobId
        for the download URL. On a case shared with opposing counsel, only accounts
        the caller may see are included (404 if none are left).
      operationId: FinancialExportController_exportPlaidItemLiabilities
      parameters:
      - name: plaidItemId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportPlaidItemLiabilitiesDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - jobId
                    properties:
                      jobId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0f1
                        description: Export job id. Poll GET /financial-data/export/:jobId
                          for status and the download URL.
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export a Plaid item's liabilities
      tags:
      - financial-data-export
  "/financial-data/account/{accountId}/investment-transactions/export":
    post:
      description: Queues a background job that builds a CSV or PDF of one account's
        investment transactions (with the given filters) and returns its job id; poll
        GET /financial-data/export/:jobId for the download URL. On a case shared with
        opposing counsel, an account the caller may not see returns 404.
      operationId: FinancialExportController_exportAccountInvestmentTransactions
      parameters:
      - name: accountId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportAccountInvestmentTransactionsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - jobId
                    properties:
                      jobId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0f1
                        description: Export job id. Poll GET /financial-data/export/:jobId
                          for status and the download URL.
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export an account's investment transactions
      tags:
      - financial-data-export
  "/financial-data/plaid-item/{plaidItemId}/investment-transactions/export":
    post:
      description: Queues a background job that builds a CSV or PDF of the investment
        transactions of every account on the Plaid item (optionally narrowed by account
        type, with the given filters) and returns its job id; poll GET /financial-data/export/:jobId
        for the download URL. On a case shared with opposing counsel, only accounts
        the caller may see are included (404 if none are left).
      operationId: FinancialExportController_exportInvestmentTransactions
      parameters:
      - name: plaidItemId
        required: true
        in: path
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ExportInvestmentTransactionsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    required:
                    - jobId
                    properties:
                      jobId:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0f1
                        description: Export job id. Poll GET /financial-data/export/:jobId
                          for status and the download URL.
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Export a Plaid item's investment transactions
      tags:
      - financial-data-export
  "/financial-data/case/{caseId}/exports":
    get:
      description: Returns every financial export job on the case, from all users
        and including statement bundles, newest first; opposing counsel gets only
        the jobs it started. Finished jobs include a fresh download URL. Not paginated.
      operationId: FinancialExportController_listExportsByCaseId
      parameters:
      - name: caseId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/ListFinancialExportJobsItemEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a case's financial exports
      tags:
      - financial-data-export
  "/financial-data/export/{jobId}":
    get:
      description: Returns the status of one financial export job (statement bundles
        included). When completed it also returns the file name, completion time and
        a fresh download URL; when failed, the error message. Only the user who started
        the export can see it; anyone else gets 404.
      operationId: FinancialExportController_getExportJobStatus
      parameters:
      - name: jobId
        required: true
        in: path
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/FinancialExportJobResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get a financial export's status
      tags:
      - financial-data-export
  "/financial-data/statements/{plaidItemId}":
    get:
      description: Asks Plaid for the item's available statements, stores any new
        ones, and returns every stored statement record for the item (account, month,
        year, and the downloaded file once there is one). Returns 409 if the item's
        accounts have not synced yet.
      operationId: PlaidStatementsController_listStatements
      parameters:
      - name: plaidItemId
        required: true
        in: path
        description: MongoDB _id of the PlaidItem
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    description: Stored statement records for the item, as saved in
                      the database.
                    items:
                      type: object
                      properties:
                        _id:
                          type: string
                          example: 65a1b2c3d4e5f6a7b8c9d0f1
                        plaidItem:
                          type: string
                        financialAccount:
                          type: string
                          nullable: true
                        statementId:
                          type: string
                        plaidAccountId:
                          type: string
                        month:
                          type: number
                          example: 3
                        year:
                          type: number
                          example: 2026
                        userFile:
                          type: string
                          nullable: true
                          description: Set once the PDF has been downloaded
                        createdAt:
                          type: string
                          format: date-time
                        updatedAt:
                          type: string
                          format: date-time
                        __v:
                          type: number
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List a Plaid item's bank statements
      tags:
      - financial-data
  "/financial-data/statements/{plaidItemId}/bulk-download":
    post:
      description: Queues a background job that downloads every stored statement PDF
        for the item and zips them, and returns its job id; poll GET /financial-data/export/:jobId
        for the download URL. If a bundle job for the item is already running in this
        case, returns that job's id instead. Returns 400 if no statements are stored
        yet (list them first).
      operationId: PlaidStatementsController_bulkDownloadStatements
      parameters:
      - name: plaidItemId
        required: true
        in: path
        description: MongoDB _id of the PlaidItem
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BulkDownloadStatementsDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/BulkDownloadStatementsResponseEntity"
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Download all statements as a ZIP
      tags:
      - financial-data
  "/financial-data/statements/{plaidItemId}/refresh":
    post:
      description: 'Asks Plaid to re-fetch the last 24 months of statements, then
        does the same as listing: stores any new statements and returns every stored
        statement record for the item. Already-downloaded PDFs are untouched.'
      operationId: PlaidStatementsController_refreshStatements
      parameters:
      - name: plaidItemId
        required: true
        in: path
        description: MongoDB _id of the PlaidItem
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    description: Stored statement records for the item, as saved in
                      the database.
                    items:
                      type: object
                      properties:
                        _id:
                          type: string
                          example: 65a1b2c3d4e5f6a7b8c9d0f1
                        plaidItem:
                          type: string
                        financialAccount:
                          type: string
                          nullable: true
                        statementId:
                          type: string
                        plaidAccountId:
                          type: string
                        month:
                          type: number
                          example: 3
                        year:
                          type: number
                          example: 2026
                        userFile:
                          type: string
                          nullable: true
                          description: Set once the PDF has been downloaded
                        createdAt:
                          type: string
                          format: date-time
                        updatedAt:
                          type: string
                          format: date-time
                        __v:
                          type: number
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Refresh a Plaid item's statements
      tags:
      - financial-data
  "/plaid-partner/customer":
    post:
      description: Org admins only. Registers the organization as a Plaid end customer
        through Plaid's reseller partner API, saves the record and tries to enable
        it straight away, then returns the saved record (Plaid secrets are never included).
        Returns 409 if the organization is already registered, or outside production.
      operationId: PlaidPartnerController_createEndCustomer
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreatePartnerCustomerDto"
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    description: The stored Plaid end-customer record.
                    properties:
                      _id:
                        type: string
                        example: 65a1b2c3d4e5f6a7b8c9d0f1
                      organization:
                        type: string
                      companyName:
                        type: string
                      legalEntityName:
                        type: string
                        nullable: true
                      website:
                        type: string
                        nullable: true
                      applicationName:
                        type: string
                        nullable: true
                      endCustomerClientId:
                        type: string
                        nullable: true
                      status:
                        type: string
                        enum:
                        - under_review
                        - pending_enablement
                        - active
                        - denied
                      products:
                        type: array
                        items:
                          type: string
                      plaidRequestId:
                        type: string
                        nullable: true
                      createdAt:
                        type: string
                        format: date-time
                      updatedAt:
                        type: string
                        format: date-time
                      __v:
                        type: number
                  statusCode:
                    type: number
                    example: 201
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
        '409':
          description: End Customer already exists
      security:
      - access-token: []
      summary: Register the organization with Plaid
      tags:
      - plaid-partner
    delete:
      description: Org admins only. Removes the organization's end customer on Plaid
        and deletes the stored record. Returns 404 if the organization is not registered.
        Returns 409 outside production.
      operationId: PlaidPartnerController_removeEndCustomer
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Removed. No response body.
      security:
      - access-token: []
      summary: Remove the organization from Plaid
      tags:
      - plaid-partner
  "/plaid-partner/customer/status":
    get:
      description: Org admins only. Returns the stored onboarding status of the organization's
        Plaid end customer as a plain string, without calling Plaid. Returns 404 if
        the organization is not registered. Returns 409 outside production.
      operationId: PlaidPartnerController_getEndCustomerStatus
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: string
                    enum:
                    - under_review
                    - pending_enablement
                    - active
                    - denied
                    example: active
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Get the organization's Plaid status
      tags:
      - plaid-partner
  "/plaid-partner/customer/oauth-institutions":
    get:
      description: Org admins only. Asks Plaid which OAuth banks the organization's
        end customer is registered with and returns Plaid's oauth_institutions list
        unchanged (empty if Plaid sends none). Returns 404 if the organization is
        not registered. Returns 409 outside production.
      operationId: PlaidPartnerController_getOAuthInstitutions
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    description: Plaid's oauth_institutions list, passed through unchanged.
                      Its shape is defined by Plaid, not by this API.
                    items:
                      type: object
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List OAuth bank registrations
      tags:
      - plaid-partner
  "/plaid-partner/customer/webhooks/plaid":
    post:
      description: 'Called by Plaid, no sign-in. When Plaid reports that an end customer''s
        OAuth registration is approved, the matching organization''s Plaid account
        is enabled; other events are ignored. Answers with received: true. The request
        must carry Plaid''s `Plaid-Verification` signature; while the `plaid-webhook-verification`
        flag is on, a missing or invalid signature gets 401.'
      operationId: PlaidPartnerController_handlePlaidWebhook
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      received:
                        type: boolean
                        example: true
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Receive a Plaid partner webhook (internal)
      tags:
      - plaid-partner
      x-internal: true
  "/.well-known/microsoft-identity-association.json":
    get:
      description: Publishes the Microsoft application ids allowed to use this domain,
        for Microsoft's publisher domain verification. No auth; not wrapped in the
        envelope.
      operationId: WellKnownController_appleDeveloperMerchantidDomainAssociation
      parameters: []
      responses:
        '200':
          description: The association file.
          content:
            application/json:
              schema:
                type: object
                example:
                  associatedApplications:
                  - applicationId: 00000000-0000-0000-0000-000000000000
      summary: Microsoft identity association (internal)
      tags:
      - WellKnown
      x-internal: true
  "/data-source":
    get:
      description: Lists every data source Hearsay can collect from (for example iOS
        and Android devices, Gmail, Facebook exports), sorted by category. Reference
        data; the list rarely changes.
      operationId: DataSourceController_findAll
      parameters:
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/MasterDataSourceEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List data sources
      tags:
      - data-source
  "/health":
    get:
      description: 'Infrastructure probe: checks outbound HTTP, the database, disk
        usage (below 90%) and heap memory (below 4 GB), and returns each result. No
        auth. Returns 503 as soon as one check fails.'
      operationId: AppController_healthCheck
      parameters: []
      responses:
        '200':
          description: The Health Check is successful
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    type: object
                    properties:
                      httpCheck:
                        type: object
                        example:
                          status: ok
                          info:
                            google:
                              status: up
                          error: {}
                      mongoCheck:
                        type: object
                        example:
                          status: ok
                          info:
                            mongo:
                              status: up
                          error: {}
                      diskCheck:
                        type: object
                        example:
                          status: ok
                          info:
                            storage:
                              status: up
                          error: {}
                      memoryCheck:
                        type: object
                        example:
                          status: ok
                          info:
                            memory_heap:
                              status: up
                          error: {}
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
        '503':
          description: |-
            The Health Check is not successful

            A check failed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: error
                  info:
                    type: object
                    example:
                      database:
                        status: up
                    additionalProperties:
                      type: object
                      required:
                      - status
                      properties:
                        status:
                          type: string
                      additionalProperties: true
                    nullable: true
                  error:
                    type: object
                    example:
                      redis:
                        status: down
                        message: Could not connect
                    additionalProperties:
                      type: object
                      required:
                      - status
                      properties:
                        status:
                          type: string
                      additionalProperties: true
                    nullable: true
                  details:
                    type: object
                    example:
                      database:
                        status: up
                      redis:
                        status: down
                        message: Could not connect
                    additionalProperties:
                      type: object
                      required:
                      - status
                      properties:
                        status:
                          type: string
                      additionalProperties: true
      summary: Health check (internal)
      tags:
      - app
      x-internal: true
  "/case/{caseId}/events":
    get:
      description: Lists the case's event log (general, case-data and opposing-counsel
        events), newest first.
      operationId: CaseEventsController_listCaseEvents
      parameters:
      - name: caseId
        required: true
        in: path
        description: Id of the case
        schema:
          type: string
      - name: page
        required: false
        in: query
        schema:
          default: 1
          type: number
      - name: perPage
        required: false
        in: query
        schema:
          maximum: 100
          default: 10
          type: number
      - name: skip
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: limit
        required: false
        in: query
        description: If skip/limit not provided, page/perPage will take precedence
        schema:
          type: number
      - name: sortOrder
        required: false
        in: query
        schema:
          default: asc
          type: string
          enum:
          - asc
          - desc
      - name: type
        required: false
        in: query
        schema:
          type: string
          enum:
          - financial_connection_removed
          - oc_invited
          - oc_accepted
          - oc_member_added
          - oc_member_removed
          - oc_ec_created
          - oc_suggestion_created
          - oc_suggestion_updated
          - oc_suggestion_message
          - oc_suggestion_rejected
          - oc_ec_approved
          - oc_data_approved
          - oc_data_revoked
          - oc_code_released
          - oc_code_unreleased
          - member_invited
          - member_accepted
          - case_created
          - case_status_changed
          - extraction_code_created
          - extraction_code_updated
          - extraction_code_locked
          - custodian_updated
          - device_attached
          - data_uploaded
          - export_started
          - export_finished
          - export_errored
          - export_cancelled
      - name: category
        required: false
        in: query
        schema:
          type: string
          enum:
          - general
          - case_data
          - oc
      - name: organization
        in: header
        description: id of the selected (active) organization
        required: true
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    allOf:
                    - "$ref": "#/components/schemas/PaginatedResponseEntity"
                    - properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/CaseEventEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List case events
      tags:
      - case-events
  "/admin/exports/live":
    get:
      description: Lists exports for diagnosing stalls, earliest started first, with
        progress counters, time since the last progress and live queue state. By default
        it shows running exports, cancellations from the last 15 minutes and, while
        queue operations are on, exports whose queue job an admin removed in the last
        hour; `status`, `startedBefore`, `caseId` and `organizationId` narrow it,
        and `limit` (default 25, at most 100) and `skip` page it. Queue state or case
        names that cannot be loaded come back as null instead of failing the request.
        Super admin only.
      operationId: ExportAdminController_listLive
      parameters:
      - name: status
        required: false
        in: query
        description: One status or several. Defaults to in-progress AND cancelled
          (the "live" view, which keeps a just-cancelled export visible) when omitted
          — see ExportAdminService.
        schema:
          type: array
          items:
            type: string
            enum:
            - not-started
            - inprogress
            - completed
            - failed
            - cancelled
      - name: startedBefore
        required: false
        in: query
        description: Only exports started before this instant (ISO 8601).
        schema:
          format: date-time
          type: string
      - name: caseId
        required: false
        in: query
        description: Id of the case
        schema:
          type: string
      - name: organizationId
        required: false
        in: query
        description: Id of the organization
        schema:
          type: string
      - name: skip
        required: false
        in: query
        schema:
          maximum: 10000
          default: 0
          type: number
      - name: limit
        required: false
        in: query
        schema:
          maximum: 100
          default: 25
          type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/LiveExportListResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: List live exports (internal)
      tags:
      - admin
      x-internal: true
  "/admin/exports/{id}/cancel":
    post:
      description: 'Records a cancel request: an export that has not started is cancelled
        at once and its queue job removed, and a running export stops at its next
        conversation boundary. Repeating it on a cancelled export succeeds. Only conversation
        PDF exports can be cancelled; other exports and completed or failed ones return
        400, an unknown export 404, and 403 while export cancellation is turned off.
        Super admin only.'
      operationId: ExportAdminController_cancel
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the export to cancel
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/CancelExportResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Cancel an export (internal)
      tags:
      - admin
      x-internal: true
  "/admin/exports/{id}/resume":
    post:
      description: Re-queues a failed, cancelled or stuck in-progress export so its
        next run reuses the conversations an earlier attempt finished, keeping its
        checkpoints and progress counters. Only conversation PDF exports can be resumed;
        other exports and other statuses return 400, an unknown export 404, and 403
        while export resume is turned off. Super admin only.
      operationId: ExportAdminController_resume
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the export to resume
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ResumeExportResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Resume an export (internal)
      tags:
      - admin
      x-internal: true
  "/admin/exports/{id}/priority":
    post:
      description: Moves a waiting export to the `top` or `bottom` of the export queue
        by removing and re-adding its job, which gives it a new queue job id (returned)
        and resets its retry count. An export with no live job or one a worker already
        picked up returns 400 and one with several queued jobs 409; if the re-add
        fails it returns 500 and the export has no queue job until it is re-run or
        resumed. Returns 404 for an unknown export and 403 while queue operations
        are turned off. Super admin only.
      operationId: ExportAdminController_reprioritize
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the export to move
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ReprioritizeExportDto"
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/ReprioritizeExportResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Move a queued export in the queue (internal)
      tags:
      - admin
      x-internal: true
  "/admin/exports/{id}/job":
    delete:
      description: Takes the export's job out of the export queue and leaves the export
        not started; it is not cancelled and runs only once re-run or resumed. When
        no live job is found the response says so instead of failing. A job a worker
        already picked up returns 400, several queued jobs 409, an unreadable queue
        500, an unknown export 404 and 403 while queue operations are turned off.
        Super admin only.
      operationId: ExportAdminController_dequeue
      parameters:
      - name: id
        required: true
        in: path
        description: Id of the export whose queue job should be removed
        schema:
          type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - statusCode
                - timestamp
                properties:
                  data:
                    "$ref": "#/components/schemas/DequeueExportResponseEntity"
                  statusCode:
                    type: number
                    example: 200
                  timestamp:
                    type: string
                    format: date-time
                    example: '2026-01-15T10:15:00.000Z'
      security:
      - access-token: []
      summary: Remove a queued export's job (internal)
      tags:
      - admin
      x-internal: true
info:
  title: UseHearsay API
  description: Every HTTP response is wrapped as { data, statusCode, timestamp } unless
    the operation says otherwise. Authenticated requests send a bearer token and,
    where the operation lists it, an `organization` header.
  version: '1.0'
  contact: {}
tags:
- name: UseHearsay API
  description: ''
servers: []
components:
  securitySchemes:
    access-token:
      scheme: bearer
      bearerFormat: JWT
      type: http
  schemas:
    UserFileEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: photo.heic
        providerId:
          type: string
          example: uploads/507f1f77bcf86cd799439011/photo.heic
        convertedName:
          type: string
          example: photo.jpg
          nullable: true
        convertedProviderId:
          type: string
          example: uploads/507f1f77bcf86cd799439011/photo.jpg
        provider:
          type: string
          example: s3
        size:
          type: number
          description: Size in bytes
          example: 204800
        transferState:
          type: number
          nullable: true
          example:
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - providerId
      - convertedName
      - convertedProviderId
      - provider
      - size
      - transferState
    ActivePlanEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        subscription:
          type: object
          nullable: true
          example:
          description: Not loaded on public routes, so always null there. Only the
            internal admin organization report loads it.
        relatedSubscription:
          type: array
          items:
            type: object
          nullable: true
          example: []
          description: Not loaded by any route, so always null or an empty list.
        organization:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        seats:
          type: number
          example: 5
        seatsUsed:
          type: number
          example: 3
        devices:
          type: number
          example: 10
        deviceUsed:
          type: number
          example: 4
        storage:
          type: number
          description: Storage in bytes
          example: 100000000000
        storageUsed:
          type: number
          description: Storage used in bytes
          example: 12000000000
        payments:
          type: array
          items:
            type: object
          nullable: true
          example: []
          description: Not loaded by any route, so always null or an empty list.
        type:
          type: string
          enum:
          - admin
          - free
          - limited-access
          - trial
          - tier1
          - tier2
          - tier3
          - tier4
          - tier5
          - tier6
          - tier7
        isActive:
          type: boolean
          example: true
        isBetweenPlanChanges:
          type: boolean
          example: false
        storageInKb:
          type: number
          example: 100000000
        storageUsedInKb:
          type: number
          example: 12000000
        storageInMb:
          type: number
          example: 100000
        storageUsedInMb:
          type: number
          example: 12000
        storageInGb:
          type: number
          example: 100
        storageUsedInGb:
          type: number
          example: 12
        storageInTb:
          type: number
          example: 0.1
        storageUsedInTb:
          type: number
          example: 0.012
        isFreePlan:
          type: boolean
          example: false
        remainingSeats:
          type: number
          example: 2
        remainingDevices:
          type: number
          example: 6
        remainingStorage:
          type: number
          description: Storage left in bytes (storage minus storageUsed).
          example: 88000000000
      required:
      - createdAt
      - updatedAt
      - id
      - subscription
      - relatedSubscription
      - organization
      - seats
      - seatsUsed
      - devices
      - deviceUsed
      - storage
      - storageUsed
      - payments
      - type
      - isActive
      - isBetweenPlanChanges
      - storageInKb
      - storageUsedInKb
      - storageInMb
      - storageUsedInMb
      - storageInGb
      - storageUsedInGb
      - storageInTb
      - storageUsedInTb
      - isFreePlan
      - remainingSeats
      - remainingDevices
      - remainingStorage
    OrganizationEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: Example Law LLP
        slug:
          type: string
          example: example-law-llp
        email:
          type: string
          example: office@example.com
        contact:
          type: string
          example: Jane Doe
        city:
          type: string
          example: New York
        state:
          type: string
          example: NY
        country:
          type: string
          example: US
        description:
          type: string
          example: Litigation practice
        phone:
          example:
          - "+15555550100"
          type: array
          items:
            type: string
        logo:
          type: string
          example: https://files.example.com/logo.png
          nullable: true
        isActive:
          type: boolean
          example: true
        isPriority:
          type: boolean
          example: false
        activePlan:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/ActivePlanEntity"
        metadata:
          type: object
          example: {}
        type:
          type: string
          example: law_firm
        version:
          type: string
          enum:
          - v1
          - v2
          - eu
          - ca
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - slug
      - email
      - contact
      - city
      - state
      - country
      - description
      - phone
      - logo
      - isActive
      - activePlan
      - metadata
      - type
      - version
    UserReferenceEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: Jane Doe
        email:
          type: string
          example: jane.doe@example.com
        phone:
          example:
          - "+15555550100"
          type: array
          items:
            type: string
        isActive:
          type: boolean
          example: true
        isEmailVerified:
          type: boolean
          example: true
        lastActive:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        roles:
          example:
          - user
          type: array
          items:
            type: string
        avatar:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileEntity"
        onboardingStatus:
          type: string
          example: completed
          nullable: true
        userType:
          type: string
          example: attorney
          nullable: true
        firstName:
          type: string
          example: Jane
        lastName:
          type: string
          example: Doe
        initials:
          type: string
          example: JD
        userOrganizations:
          type: array
          items:
            type: object
          example: []
          description: Only filled on the signed-in user's own profile; absent or
            empty on a user nested in another record.
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - email
      - phone
      - isActive
      - isEmailVerified
      - lastActive
      - roles
      - avatar
      - onboardingStatus
      - userType
      - firstName
      - lastName
      - initials
    UserOrgPermissionEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        user:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        role:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
          example: member
        organization:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        lastActiveAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - user
      - role
      - organization
      - lastActiveAt
    OrganizationBillingLimitsEntity:
      type: object
      properties:
        seatCap:
          type: number
          nullable: true
          description: null = unlimited
          example: 10
        storageGB:
          type: number
          example: 5000
        perCaseSourceCap:
          type: number
          nullable: true
          description: Provisioned, not enforced — display only
          example:
      required:
      - seatCap
      - storageGB
      - perCaseSourceCap
    OrganizationBillingPriceEntity:
      type: object
      properties:
        skuKey:
          type: string
          enum:
          - collection.phone
          - collection.social
          - collection.email
          - collection.ai-chat
          - collection.financial
          - collection.discord
          - collection.reddit
          - collection.linkedin
          - collection.legal
          - collection.file
          - collection.other
          - case.flat
          - case.pro-upgrade
          - addon.kit-rental
          - addon.laptop-rental
          - addon.laptop-rental-setup
          - addon.managed-service
          - addon.shipping
          - addon.live-bank
          - plan.base
          - source.per
          - ai.tokens-overage
          - adjustment.manual
          - adjustment.discount
          example: collection.phone
        name:
          type: string
          example: Phone collection
        unitAmountCents:
          type: number
          description: What this org is charged per unit, in cents
          example: 17900
        included:
          type: boolean
          description: Bundled by the plan — charged at 0, not billed
          example: false
        listAmountCents:
          type: number
          nullable: true
          description: What an included item would otherwise have cost
          example:
        overridden:
          type: boolean
          description: Set by this org's negotiated price rather than the plan's catalog
            rate
          example: false
        recurrence:
          type: string
          enum:
          - monthly
          - semiannual
          nullable: true
          description: null for a one-time charge; otherwise how often it repeats
          example:
        freeFirstPeriod:
          type: boolean
          description: First period of a recurring item costs 0
          example: false
      required:
      - skuKey
      - name
      - unitAmountCents
      - included
      - listAmountCents
      - overridden
      - recurrence
      - freeFirstPeriod
    OrganizationBillingEntity:
      type: object
      properties:
        tier:
          type: string
          enum:
          - public
          - solo
          - firm
          - firm-plus
          example: firm
        tierName:
          type: string
          example: Firm
        billingMode:
          type: string
          enum:
          - payg
          - per-source
          - flat-case
          example: per-source
        cadence:
          type: string
          enum:
          - monthly
          - annual
          example: monthly
        baseFeeCents:
          type: number
          description: Platform fee for one period at this cadence, in cents
          example: 24900
        paymentMethodOnFile:
          type: boolean
          example: true
        supportTier:
          type: string
          example: next-day
        limits:
          "$ref": "#/components/schemas/OrganizationBillingLimitsEntity"
        features:
          example:
          - rsmf
          - ai
          - mcp
          - whiteLabel
          description: Feature keys enabled for this tier
          type: array
          items:
            type: string
        prices:
          type: array
          items:
            "$ref": "#/components/schemas/OrganizationBillingPriceEntity"
        overrides:
          type: object
          description: This org's negotiated exceptions, as stored. An absent SKU
            means the plan's catalog rate applies — read `prices` for what is actually
            charged.
          example:
            skuUnitAmountCents:
              collection.phone: 9900
      required:
      - tier
      - tierName
      - billingMode
      - cadence
      - baseFeeCents
      - paymentMethodOnFile
      - supportTier
      - limits
      - features
      - prices
      - overrides
    OrganizationDetailsEntity:
      type: object
      properties:
        organization:
          "$ref": "#/components/schemas/OrganizationEntity"
        permission:
          "$ref": "#/components/schemas/UserOrgPermissionEntity"
        billing:
          nullable: true
          description: The org's plan, limits and resolved prices. `null` when the
            org is billed by the legacy model — either it has not been migrated to
            case billing or the feature is off.
          type: object
          allOf:
          - "$ref": "#/components/schemas/OrganizationBillingEntity"
      required:
      - organization
      - permission
      - billing
    UserEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: Jane Doe
        email:
          type: string
          example: jane.doe@example.com
        phone:
          example:
          - "+15555550100"
          type: array
          items:
            type: string
        isActive:
          type: boolean
          example: true
        isEmailVerified:
          type: boolean
          example: true
        lastActive:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        roles:
          example:
          - user
          type: array
          items:
            type: string
        avatar:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileEntity"
        onboardingStatus:
          type: string
          example: completed
          nullable: true
        userType:
          type: string
          example: attorney
          nullable: true
        firstName:
          type: string
          example: Jane
        lastName:
          type: string
          example: Doe
        initials:
          type: string
          example: JD
        userOrganizations:
          type: array
          items:
            "$ref": "#/components/schemas/OrganizationDetailsEntity"
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - email
      - phone
      - isActive
      - isEmailVerified
      - lastActive
      - roles
      - avatar
      - onboardingStatus
      - userType
      - firstName
      - lastName
      - initials
    LoginEmailDto:
      type: object
      properties:
        email:
          type: string
        password:
          type: string
      required:
      - email
      - password
    GenerateTokenResponseEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        token:
          type: string
          description: The token value. Returned once at mint time only.
          example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2NWExYjJjMyJ9.c2lnbmF0dXJl
        validTill:
          format: date-time
          type: string
          example: '2027-01-15T10:15:00.000Z'
        scopes:
          type: array
          items:
            type: string
            enum:
            - cases:create
            - codes:create
            - exports:create
        label:
          type: string
          example: Intake sync
      required:
      - createdAt
      - updatedAt
      - id
      - token
      - validTill
    GenerateTokenDto:
      type: object
      properties:
        scopes:
          type: array
          description: Automation scopes to grant. Presence of this field mints a
            scoped automation token; must be non-empty and a subset of the allowed
            scopes.
          items:
            type: string
            enum:
            - cases:create
            - codes:create
            - exports:create
        label:
          type: string
          description: Human-readable label for the automation token (e.g. "Zapier
            – intake").
        ttlDays:
          type: number
          description: Token lifetime in days (automation tokens only, 1–400). Defaults
            to the configured automation TTL (~365).
          minimum: 1
          maximum: 400
    AutomationTokenListEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        label:
          type: string
          example: Intake automation
        scopes:
          type: array
          example:
          - cases:create
          - codes:create
          items:
            type: string
            enum:
            - cases:create
            - codes:create
            - exports:create
        lastUsed:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        validTill:
          format: date-time
          type: string
          example: '2027-01-15T14:30:00.000Z'
        isRevoked:
          type: boolean
          example: false
      required:
      - createdAt
      - updatedAt
      - id
      - scopes
      - isRevoked
    UpdateTokenScopesDto:
      type: object
      properties:
        scopes:
          type: array
          description: New scope set for the token. Must be a subset of the allowed
            scopes. Empty array disables the token.
          items:
            type: string
            enum:
            - cases:create
            - codes:create
            - exports:create
      required:
      - scopes
    RefreshTokenResponseEntity:
      type: object
      properties:
        accessToken:
          type: string
          example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2NWExYjJjMyJ9.c2lnbmF0dXJl
          description: 'New access token. Send it as `Authorization: Bearer <token>`.'
        refreshToken:
          type: string
          example: b7c1e2f0a9d84c3e8f6a5b4c3d2e1f00
          description: The same refresh token that was sent; it keeps working until
            it expires.
      required:
      - accessToken
      - refreshToken
    RefreshTokenDto:
      type: object
      properties:
        refreshToken:
          type: string
      required:
      - refreshToken
    RegisterEmailDto:
      type: object
      properties:
        email:
          type: string
        password:
          type: string
        name:
          type: string
          maxLength: 200
          minLength: 3
        organizationName:
          type: string
        inviteEmails:
          type: array
          items:
            type: array
        phone:
          type: array
          items:
            type: string
            maximum: 20
            minimum: 1
        userType:
          type: string
        termsAccepted:
          type: boolean
      required:
      - email
      - password
      - name
      - userType
    MessageResponseEntity:
      type: object
      properties:
        message:
          type: string
          example: Done
        isSuccess:
          type: boolean
          example: true
      required:
      - message
      - isSuccess
    SendForgotPasswordLinkDto:
      type: object
      properties:
        email:
          type: string
      required:
      - email
    ResetPasswordDto:
      type: object
      properties:
        email:
          type: string
        newPassword:
          type: string
        resetToken:
          type: string
      required:
      - email
      - newPassword
      - resetToken
    EmailAvailabilityResponseEntity:
      type: object
      properties:
        isAvailable:
          type: boolean
          description: Whether the email is available
          example: true
        message:
          type: string
          description: The message
          example: Email is available
        type:
          type: string
          description: Type of user (user/custodian)
          example: user
      required:
      - isAvailable
      - message
      - type
    CheckEmailAvailabilityDto:
      type: object
      properties:
        email:
          type: string
          description: The email to check availability for
          example: test@example.com
      required:
      - email
    CaseProUpgradeResponseEntity:
      type: object
      properties:
        status:
          type: string
          example: processing
      required:
      - status
    BillingSetupSessionResponseEntity:
      type: object
      properties:
        url:
          type: string
          example: https://checkout.stripe.com/c/pay/cs_test_a1Example
      required:
      - url
    CreateBillingSetupSessionDto:
      type: object
      properties:
        successUrl:
          type: string
        cancelUrl:
          type: string
      required:
      - successUrl
      - cancelUrl
    SelectedPlanResponseEntity:
      type: object
      properties:
        tier:
          type: string
          enum:
          - public
          - solo
          - firm
          - firm-plus
          example: firm
        cadence:
          type: string
          enum:
          - monthly
          - annual
          example: monthly
        billingMode:
          type: string
          enum:
          - payg
          - per-source
          - flat-case
          example: payg
      required:
      - tier
      - cadence
      - billingMode
    SelectPlanResponseEntity:
      type: object
      properties:
        plan:
          "$ref": "#/components/schemas/SelectedPlanResponseEntity"
        paymentRequired:
          type: boolean
          example: true
        setupSessionUrl:
          type: string
          example: https://checkout.stripe.com/c/pay/cs_test_a1Example
        prorationCents:
          type: number
          description: Net cents charged (positive) or credited (negative) now to
            reconcile the base fee on a plan change; 0 on a first-time plan selection.
          example: 15000
      required:
      - plan
      - paymentRequired
      - prorationCents
    SelectPlanDto:
      type: object
      properties:
        tier:
          type: string
          enum:
          - public
          - solo
          - firm
          - firm-plus
        cadence:
          type: string
          enum:
          - monthly
          - annual
        successUrl:
          type: string
        cancelUrl:
          type: string
      required:
      - tier
    CaseStatementRowResponseEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e4
        skuKey:
          type: string
          example: collection.phone
        description:
          type: string
          example: Phone collection
        quantity:
          type: number
          example: 1
        amountCents:
          type: number
          example: 17900
        informational:
          type: boolean
          example: false
        listAmountCents:
          type: number
          nullable: true
          example: 17900
        status:
          type: string
          enum:
          - DRAFT
          - PENDING
          - PROCESSING
          - INVOICED
          - PAID
          - FAILED
          - REFUNDED
          - COMPED
          - VOID
          example: INVOICED
        hostedInvoiceUrl:
          type: string
          nullable: true
          example: https://invoice.stripe.com/i/acct_Example/test_Example
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
      required:
      - id
      - skuKey
      - description
      - quantity
      - amountCents
      - informational
      - listAmountCents
      - status
      - hostedInvoiceUrl
      - createdAt
    CaseStatementResponseEntity:
      type: object
      properties:
        caseId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        rows:
          type: array
          items:
            "$ref": "#/components/schemas/CaseStatementRowResponseEntity"
        totalChargedCents:
          type: number
          example: 17900
        totalInformationalCents:
          type: number
          example: 5000
      required:
      - caseId
      - rows
      - totalChargedCents
      - totalInformationalCents
    OrgReconciliationRowResponseEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e4
        skuKey:
          type: string
          example: collection.phone
        description:
          type: string
          example: Phone collection
        quantity:
          type: number
          example: 1
        amountCents:
          type: number
          example: 17900
        informational:
          type: boolean
          example: false
        listAmountCents:
          type: number
          nullable: true
          example: 17900
        status:
          type: string
          enum:
          - DRAFT
          - PENDING
          - PROCESSING
          - INVOICED
          - PAID
          - FAILED
          - REFUNDED
          - COMPED
          - VOID
          example: INVOICED
        hostedInvoiceUrl:
          type: string
          nullable: true
          example: https://invoice.stripe.com/i/acct_Example/test_Example
        invoiceId:
          type: string
          nullable: true
          example: in_1PExample
        chargeScope:
          type: string
          enum:
          - case
          - platform
          description: "'case' = a charge for work on a specific case; 'platform'
            = an org-wide charge such as the plan base fee or storage overage. Stripe
            bills the two on separate invoices."
          example: case
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
      required:
      - id
      - skuKey
      - description
      - quantity
      - amountCents
      - informational
      - listAmountCents
      - status
      - hostedInvoiceUrl
      - invoiceId
      - chargeScope
      - createdAt
    OrgReconciliationGroupResponseEntity:
      type: object
      properties:
        caseId:
          type: string
          description: The case id, or the literal 'organization' for the org-wide
            group. Prefer `chargeScope` over comparing against that literal.
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        chargeScope:
          type: string
          enum:
          - case
          - platform
          description: Scope of every row in this group — the whole group is one or
            the other, because invoices are grouped the same way.
          example: case
        rows:
          type: array
          items:
            "$ref": "#/components/schemas/OrgReconciliationRowResponseEntity"
        subtotalChargedCents:
          type: number
          example: 17900
      required:
      - caseId
      - chargeScope
      - rows
      - subtotalChargedCents
    OrgReconciliationResponseEntity:
      type: object
      properties:
        groups:
          type: array
          items:
            "$ref": "#/components/schemas/OrgReconciliationGroupResponseEntity"
      required:
      - groups
    BillingHistoryResponseEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e5
        type:
          type: string
          enum:
          - subscribed
          - plan_upgraded
          - plan_canceled
          - payment_succeeded
          - payment_failed
          - grace_started
          - grace_recovered
          - card_added
          - card_removed
          - extraction_code_paid
          - charge_recorded
          - invoice_created
          - invoice_paid
          - invoice_failed
          - refunded
          - dispute_created
          - case_pro_upgraded
          - tokens_granted
          - admin_draft
          - admin_draft_ready
          - admin_comp
          - admin_void
          - admin_profile_updated
          - migrated_to_case_billing
          - credit_granted
          - credit_deducted
          - credit_reversed
          - addon_activated
          - addon_canceled
          example: charge_recorded
        category:
          type: string
          enum:
          - subscription
          - invoicing
          - payment_method
          - case_billing
          - admin
          example: case_billing
        description:
          type: string
          example: 'Charge recorded: Phone collection'
        case:
          type: string
          nullable: true
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        createdBy:
          type: string
          nullable: true
          example: 65a1b2c3d4e5f6a7b8c9d0e6
        metadata:
          type: object
          example:
            skuKey: collection.phone
            amountCents: 17900
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
      required:
      - id
      - type
      - category
      - description
      - case
      - createdBy
      - metadata
      - createdAt
    PlanOptionResponseEntity:
      type: object
      properties:
        tier:
          type: string
          enum:
          - public
          - solo
          - firm
          - firm-plus
          example: firm
        name:
          type: string
          example: Firm
        monthlyCents:
          type: number
          example: 24900
        annualCents:
          type: number
          example: 268920
        seatCap:
          type: number
          nullable: true
          example:
        storageGB:
          type: number
          example: 5000
        features:
          example:
          - rsmf
          - ai
          type: array
          items:
            type: string
        annualOnly:
          type: boolean
          example: false
        cadenceOptions:
          type: array
          example:
          - monthly
          - annual
          items:
            type: string
            enum:
            - monthly
            - annual
      required:
      - tier
      - name
      - monthlyCents
      - annualCents
      - seatCap
      - storageGB
      - features
      - annualOnly
      - cadenceOptions
    BillingPlansResponseEntity:
      type: object
      properties:
        plans:
          type: array
          items:
            "$ref": "#/components/schemas/PlanOptionResponseEntity"
      required:
      - plans
    PaymentMethodResponseEntity:
      type: object
      properties:
        brand:
          type: string
          example: visa
        last4:
          type: string
          example: '4242'
        expMonth:
          type: number
          example: 4
        expYear:
          type: number
          example: 2044
      required:
      - brand
      - last4
      - expMonth
      - expYear
    CurrentPlanDetailsResponseEntity:
      type: object
      properties:
        tier:
          type: string
          enum:
          - public
          - solo
          - firm
          - firm-plus
          example: firm
        cadence:
          type: string
          enum:
          - monthly
          - annual
          example: monthly
        billingMode:
          type: string
          enum:
          - payg
          - per-source
          - flat-case
          example: payg
        paymentMethodOnFile:
          type: boolean
          description: Whether a usable card is on file, read live from Stripe rather
            than from the stored flag.
          example: true
        paymentMethod:
          nullable: true
          description: The card that will actually be charged (the customer default),
            or null when there is none.
          type: object
          allOf:
          - "$ref": "#/components/schemas/PaymentMethodResponseEntity"
      required:
      - tier
      - cadence
      - billingMode
      - paymentMethodOnFile
      - paymentMethod
    UsageWithCapResponseEntity:
      type: object
      properties:
        used:
          type: number
          example: 12
        cap:
          type: number
          nullable: true
          example: 50
      required:
      - used
      - cap
    UsageWithBudgetResponseEntity:
      type: object
      properties:
        used:
          type: number
          example: 250000
        budget:
          type: number
          example: 1000000
      required:
      - used
      - budget
    CurrentPlanUsageResponseEntity:
      type: object
      properties:
        seats:
          "$ref": "#/components/schemas/UsageWithCapResponseEntity"
        storageGB:
          "$ref": "#/components/schemas/UsageWithCapResponseEntity"
        aiTokens:
          "$ref": "#/components/schemas/UsageWithBudgetResponseEntity"
        cases:
          "$ref": "#/components/schemas/UsageWithCapResponseEntity"
      required:
      - seats
      - storageGB
      - aiTokens
      - cases
    BillingPlanCurrentResponseEntity:
      type: object
      properties:
        plan:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/CurrentPlanDetailsResponseEntity"
        usage:
          "$ref": "#/components/schemas/CurrentPlanUsageResponseEntity"
        delinquent:
          type: boolean
          example: false
      required:
      - plan
      - usage
      - delinquent
    CreditLedgerEntryResponseEntity:
      type: object
      properties:
        type:
          type: string
          enum:
          - credit
          - debit
          - reversal
          example: debit
        reason:
          type: string
          enum:
          - admin_grant
          - deduction
          - reversal
          example: deduction
        deltaCents:
          type: number
          example: -5000
        balanceAfterCents:
          type: number
          example: 20000
        description:
          type: string
          example: Credit applied to invoice in_1QExampleInvoice0001
        invoice:
          type: string
          nullable: true
          example: in_1QExampleInvoice0001
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
      required:
      - type
      - reason
      - deltaCents
      - balanceAfterCents
      - description
      - invoice
      - createdAt
    CreditBalanceResponseEntity:
      type: object
      properties:
        balanceCents:
          type: number
          example: 20000
        currency:
          type: string
          example: usd
        effectiveDeductPercentage:
          type: number
          example: 100
        entries:
          type: array
          items:
            "$ref": "#/components/schemas/CreditLedgerEntryResponseEntity"
      required:
      - balanceCents
      - currency
      - effectiveDeductPercentage
      - entries
    ReportWindow:
      type: object
      properties:
        startDate:
          format: date-time
          type: string
          example: '2026-01-01T00:00:00.000Z'
        endDate:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
      required:
      - startDate
      - endDate
    ReportRow:
      type: object
      properties:
        date:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
        skuKey:
          type: string
          enum:
          - collection.phone
          - collection.social
          - collection.email
          - collection.ai-chat
          - collection.financial
          - collection.discord
          - collection.reddit
          - collection.linkedin
          - collection.legal
          - collection.file
          - collection.other
          - case.flat
          - case.pro-upgrade
          - addon.kit-rental
          - addon.laptop-rental
          - addon.laptop-rental-setup
          - addon.managed-service
          - addon.shipping
          - addon.live-bank
          - plan.base
          - source.per
          - ai.tokens-overage
          - adjustment.manual
          - adjustment.discount
          example: collection.phone
        description:
          type: string
          example: Phone collection
        quantity:
          type: number
          example: 1
        unitAmountCents:
          type: number
          example: 17900
        amountCents:
          type: number
          example: 17900
        status:
          type: string
          example: INVOICED
        invoiceId:
          type: string
          nullable: true
          example: in_1PExample
      required:
      - date
      - skuKey
      - description
      - quantity
      - unitAmountCents
      - amountCents
      - status
      - invoiceId
    CaseGroup:
      type: object
      properties:
        caseId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        caseName:
          type: string
          example: Smith v. Jones
        rows:
          type: array
          items:
            "$ref": "#/components/schemas/ReportRow"
        subtotalChargedCents:
          type: number
          example: 17900
        subtotalInformationalCents:
          type: number
          example: 0
      required:
      - caseId
      - caseName
      - rows
      - subtotalChargedCents
      - subtotalInformationalCents
    OrgReport:
      type: object
      properties:
        organizationId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e3
        organizationName:
          type: string
          example: Acme Legal
        tier:
          type: string
          nullable: true
          example: firm
        billingMode:
          type: string
          nullable: true
          example: payg
        onCaseBilling:
          type: boolean
          example: true
        window:
          "$ref": "#/components/schemas/ReportWindow"
        cases:
          type: array
          items:
            "$ref": "#/components/schemas/CaseGroup"
        totalChargedCents:
          type: number
          example: 42300
        totalInformationalCents:
          type: number
          example: 5000
        totalNonChargeableCents:
          type: number
          example: 0
        totalFailedCents:
          type: number
          example: 0
        totalRefundedCents:
          type: number
          example: 0
      required:
      - organizationId
      - organizationName
      - tier
      - billingMode
      - onCaseBilling
      - window
      - cases
      - totalChargedCents
      - totalInformationalCents
      - totalNonChargeableCents
      - totalFailedCents
      - totalRefundedCents
    BillingReportResponseEntity:
      type: object
      properties:
        organizations:
          type: array
          items:
            "$ref": "#/components/schemas/OrgReport"
        grandTotalChargedCents:
          type: number
          example: 42300
      required:
      - organizations
      - grandTotalChargedCents
    BillingReportUserFileEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e1
        name:
          type: string
          example: billing-report-2026-01.pdf
        mimeType:
          type: string
          example: application/pdf
        size:
          type: number
          example: 184320
      required:
      - id
      - name
      - mimeType
      - size
    BillingReportFileResponseEntity:
      type: object
      properties:
        userFile:
          "$ref": "#/components/schemas/BillingReportUserFileEntity"
        downloadUrl:
          type: string
          description: Presigned, time-limited download URL
          example: https://acme-user-data.s3.amazonaws.com/billing-report-2026-01.pdf?X-Amz-Signature=abc123
      required:
      - userFile
      - downloadUrl
    BillingReportQueryDto:
      type: object
      properties:
        caseIds:
          description: Defaults to all cases
          type: array
          items:
            type: string
        startDate:
          format: date-time
          type: string
          description: Defaults to the start of the organization's current billing
            cadence period
        endDate:
          format: date-time
          type: string
          description: Defaults to now
        format:
          type: string
          enum:
          - json
          - pdf
          default: json
    UpdateUserDto:
      type: object
      properties:
        name:
          type: string
          maxLength: 200
          minLength: 3
        phone:
          type: array
          items:
            type: string
            maximum: 20
            minimum: 1
        onboardingStatus:
          type: string
        lastOnboardingStep:
          type: number
        userType:
          type: string
      required:
      - onboardingStatus
      - lastOnboardingStep
      - userType
    ChangePasswordDto:
      type: object
      properties:
        oldPassword:
          type: string
        newPassword:
          type: string
      required:
      - oldPassword
      - newPassword
    VerifyEmailDto:
      type: object
      properties:
        token:
          type: string
        email:
          type: string
      required:
      - token
    OrganizationWithoutPlanEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: Example Law LLP
        slug:
          type: string
          example: example-law-llp
        email:
          type: string
          example: office@example.com
        contact:
          type: string
          example: Jane Doe
        city:
          type: string
          example: New York
        state:
          type: string
          example: NY
        country:
          type: string
          example: US
        description:
          type: string
          example: Litigation practice
        phone:
          example:
          - "+15555550100"
          type: array
          items:
            type: string
        logo:
          type: string
          example: https://files.example.com/logo.png
          nullable: true
        isActive:
          type: boolean
          example: true
        isPriority:
          type: boolean
          example: false
        activePlan:
          type: object
          nullable: true
          description: Not loaded on this route, so always null.
          example:
        metadata:
          type: object
          example: {}
        type:
          type: string
          example: law_firm
        version:
          type: string
          enum:
          - v1
          - v2
          - eu
          - ca
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - slug
      - email
      - contact
      - city
      - state
      - country
      - description
      - phone
      - logo
      - isActive
      - activePlan
      - metadata
      - type
      - version
    OrganizationPermissionEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        user:
          type: object
          nullable: true
          description: Not loaded on this route, so always null.
          example:
        role:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
          example: member
        organization:
          type: object
          nullable: true
          example:
          description: Not loaded on this route, so always null.
        lastActiveAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - user
      - role
      - organization
      - lastActiveAt
    OrganizationDetailsResponseEntity:
      type: object
      properties:
        organization:
          "$ref": "#/components/schemas/OrganizationWithoutPlanEntity"
        permission:
          "$ref": "#/components/schemas/OrganizationPermissionEntity"
        billing:
          nullable: true
          description: The org's plan, limits and resolved prices. `null` when the
            org is billed by the legacy model — either it has not been migrated to
            case billing or the feature is off.
          type: object
          allOf:
          - "$ref": "#/components/schemas/OrganizationBillingEntity"
      required:
      - organization
      - permission
      - billing
    CreateOrganizationDto:
      type: object
      properties:
        name:
          type: string
        website:
          type: string
        logo:
          type: string
        contact:
          type: string
        addressLine1:
          type: string
        addressLine2:
          type: string
        city:
          type: string
        state:
          type: string
        country:
          type: string
        zipCode:
          type: string
        email:
          type: string
        description:
          type: string
        phone:
          type: array
          items:
            type: string
            maximum: 20
            minimum: 1
        inviteEmails:
          type: array
          items:
            type: array
      required:
      - name
    InvitationEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        invitedUserEmail:
          type: string
          example: jane.doe@example.com
        invitedUserName:
          type: string
          example: Jane Doe
        invitedUserPhone:
          description: Phone numbers stored on the invitation. No invite flow fills
            it today, so it is an empty list.
          example:
          - "+15555550100"
          type: array
          items:
            type: string
        invitedBy:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        organization:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/OrganizationEntity"
        invitedUserRole:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
          example: member
        status:
          type: string
          enum:
          - pending
          - accepted
          - rejected
          example: pending
        emailSent:
          type: boolean
          example: false
        emailSentAt:
          format: date-time
          type: string
          nullable: true
          example:
        acceptedAt:
          format: date-time
          type: string
          nullable: true
          example:
        acceptedBy:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        invitedUser:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        rejectedAt:
          format: date-time
          type: string
          nullable: true
          example:
        rejectedReason:
          type: string
          example: Already joined another organization.
          nullable: true
        initials:
          type: string
          example: JD
      required:
      - createdAt
      - updatedAt
      - id
      - invitedUserEmail
      - invitedUserName
      - invitedUserPhone
      - invitedBy
      - organization
      - invitedUserRole
      - status
      - emailSent
      - emailSentAt
      - acceptedAt
      - acceptedBy
      - invitedUser
      - rejectedAt
      - rejectedReason
      - initials
    InvitationListEntity:
      type: object
      properties:
        total:
          type: number
          example: 42
        page:
          type: number
          example: 1
        perPage:
          type: number
          example: 20
        skip:
          type: number
          example: 0
        limit:
          type: number
          example: 20
        totalPages:
          type: number
          example: 3
        nextPage:
          type: number
          example: 2
          nullable: true
        previousPage:
          type: number
          example:
          nullable: true
        data:
          type: array
          items:
            "$ref": "#/components/schemas/InvitationEntity"
      required:
      - total
      - totalPages
      - nextPage
      - previousPage
      - data
    OrganizationNameValidateResponseEntity:
      type: object
      properties:
        isValid:
          type: boolean
          example: true
      required:
      - isValid
    ValidateOrganizationNameDto:
      type: object
      properties:
        name:
          type: string
      required:
      - name
    CommunicationPreferencesDto:
      type: object
      properties:
        contactAllowed:
          type: boolean
        emailOk:
          type: boolean
        smsOk:
          type: boolean
        callOk:
          type: boolean
        maxTouchesPerWeek:
          type: number
        minHoursBetween:
          type: number
        quietHoursStart:
          type: number
        quietHoursEnd:
          type: number
        preferredTone:
          type: string
        escalateToFirmOk:
          type: boolean
        firmContactEmail:
          type: string
        equipmentAutoApproved:
          type: boolean
        managedUpgradeAllowed:
          type: boolean
        notes:
          type: string
    UpdateOrganizationDto:
      type: object
      properties:
        name:
          type: string
        website:
          type: string
        logo:
          type: string
        contact:
          type: string
        addressLine1:
          type: string
        addressLine2:
          type: string
        city:
          type: string
        state:
          type: string
        country:
          type: string
        zipCode:
          type: string
        email:
          type: string
        description:
          type: string
        phone:
          type: array
          items:
            type: string
            maximum: 20
            minimum: 1
        originData:
          type: array
          items:
            type: string
        metadata:
          type: object
        acceptPlaidTerms:
          type: boolean
        communicationPreferences:
          "$ref": "#/components/schemas/CommunicationPreferencesDto"
    OrganizationUsagesStatEntity:
      type: object
      properties:
        isActive:
          type: boolean
          example: true
        billingModel:
          type: string
          enum:
          - legacy
          - case-billing
          description: 'Which billing system the org is on. On `case-billing` there
            is no seat/device allowance: `seats`/`devices` are -1 (unlimited) and
            clients must not gate work on them.'
          example: legacy
        isPaidPlan:
          type: boolean
          example: true
        isPayAsYouGo:
          type: boolean
          example: false
        seats:
          type: number
          example: 10
        seatsUsed:
          type: number
          example: 4
        devices:
          type: number
          example: 20
        deviceUsed:
          type: number
          example: 7
        storage:
          type: number
          description: Storage in bytes
          example: 100000000000
        storageUsed:
          type: number
          description: Storage used in bytes
          example: 25000000000
        organizationName:
          type: string
          example: Acme Law LLP
        organizationSlug:
          type: string
          example: acme-law-llp
        organizationId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e1
        hasUserAccessToBilling:
          type: boolean
          example: true
        isNewDevice:
          type: boolean
          example: true
        clientPayDetails:
          type: object
          nullable: true
          example:
        canAddFreeDevice:
          type: boolean
          example: true
        canAddFreeSeats:
          type: boolean
          example: true
        storageInKb:
          type: number
          example: 100000000
        storageUsedInKb:
          type: number
          example: 25000000
        storageInMb:
          type: number
          example: 100000
        storageUsedInMb:
          type: number
          example: 25000
        storageInGb:
          type: number
          example: 100
        storageUsedInGb:
          type: number
          example: 25
        storageInTb:
          type: number
          example: 0.1
        storageUsedInTb:
          type: number
          example: 0.025
      required:
      - isActive
      - billingModel
      - isPaidPlan
      - isPayAsYouGo
      - seats
      - seatsUsed
      - devices
      - deviceUsed
      - storage
      - storageUsed
      - organizationName
      - organizationSlug
      - organizationId
      - hasUserAccessToBilling
      - isNewDevice
      - canAddFreeDevice
      - canAddFreeSeats
      - storageInKb
      - storageUsedInKb
      - storageInMb
      - storageUsedInMb
      - storageInGb
      - storageUsedInGb
      - storageInTb
      - storageUsedInTb
    OrganizationStorageStatsCaseEntity:
      type: object
      properties:
        caseId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e1
        caseName:
          type: string
          example: Smith v. Jones
        caseCreatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        usagesInBytes:
          type: number
          example: 52428800
      required:
      - caseId
      - caseName
      - caseCreatedAt
      - usagesInBytes
    OrganizationStorageStatsEntity:
      type: object
      properties:
        totalOrgUsagesInBytes:
          type: number
          example: 1073741824
        cases:
          type: array
          items:
            "$ref": "#/components/schemas/OrganizationStorageStatsCaseEntity"
      required:
      - totalOrgUsagesInBytes
      - cases
    OrganizationListEntity:
      type: object
      properties:
        total:
          type: number
          example: 42
        page:
          type: number
          example: 1
        perPage:
          type: number
          example: 20
        skip:
          type: number
          example: 0
        limit:
          type: number
          example: 20
        totalPages:
          type: number
          example: 3
        nextPage:
          type: number
          example: 2
          nullable: true
        previousPage:
          type: number
          example:
          nullable: true
        data:
          type: array
          items:
            "$ref": "#/components/schemas/OrganizationDetailsEntity"
      required:
      - total
      - totalPages
      - nextPage
      - previousPage
      - data
    OrganizationMembersListEntity:
      type: object
      properties:
        total:
          type: number
          example: 42
        page:
          type: number
          example: 1
        perPage:
          type: number
          example: 20
        skip:
          type: number
          example: 0
        limit:
          type: number
          example: 20
        totalPages:
          type: number
          example: 3
        nextPage:
          type: number
          example: 2
          nullable: true
        previousPage:
          type: number
          example:
          nullable: true
        data:
          type: array
          items:
            "$ref": "#/components/schemas/UserOrgPermissionEntity"
      required:
      - total
      - totalPages
      - nextPage
      - previousPage
      - data
    InviteOrganizationUser:
      type: object
      properties:
        email:
          type: string
        name:
          type: string
          minLength: 1
          maxLength: 200
        invitedUserRole:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
          default: member
        sendInstructions:
          type: boolean
          default: false
      required:
      - email
    InviteOrganizationUsersDto:
      type: object
      properties:
        users:
          type: array
          items:
            "$ref": "#/components/schemas/InviteOrganizationUser"
      required:
      - users
    AppConfigDeviceSettingsEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        systemId:
          type: string
          example: 3f8e1c5a9b2d4e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f
        deviceId:
          type: string
          example: '00008110-001A2B3C4D5E'
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        backupPaths:
          example:
          - "/Users/jane/HearsayBackups"
          type: array
          items:
            type: string
        defaultBackupPath:
          type: string
          example: "/Users/jane/HearsayBackups"
        lastUsedBackupPath:
          type: string
          example: "/Users/jane/HearsayBackups"
        device:
          type: string
          nullable: true
          description: Id of the device these settings belong to
          example: 507f1f77bcf86cd799439011
        lastBackupAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        lastLocalBackupAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        autoBackupFrequency:
          type: string
          enum:
          - none
          - daily
          - weekly
          - monthly
          default: none
        deviceMetadata:
          type: object
          example:
            name: Jane Doe's iPhone
            productType: iPhone15,2
            productVersion: 17.4.1
            timeZone: America/Chicago
        otherSettings:
          type: object
          example: {}
      required:
      - createdAt
      - updatedAt
      - id
      - systemId
      - deviceId
      - deviceType
      - backupPaths
      - defaultBackupPath
      - lastUsedBackupPath
      - device
      - lastBackupAt
      - lastLocalBackupAt
      - autoBackupFrequency
      - deviceMetadata
      - otherSettings
    OSDetailsEntity:
      type: object
      properties:
        arch:
          type: string
          example: arm64
        platform:
          type: string
          example: darwin
        version:
          type: string
          example: '14.4'
        type:
          type: string
          description: 'Never sent today: nothing fills it (the OS type is stored
            and sent as osType).'
          example: Darwin
        osType:
          type: string
          description: Operating system type reported by the desktop app.
          example: Darwin
        machine:
          type: string
          example: arm64
        totalMemory:
          type: number
          example: 17179869184
        cpus:
          type: object
          example: {}
        extraInformation:
          type: object
          example:
            cpu:
              manufacturer: Apple
              brand: M2
              physicalCores: 8
            mem:
              total: 17179869184
            osInfo:
              platform: darwin
              distro: macOS
              release: '14.4'
              arch: arm64
            battery:
              hasBattery: true
              percent: 80
    AppConfigEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        systemId:
          type: string
          example: 3f8e1c5a9b2d4e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f
        deviceSettings:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/AppConfigDeviceSettingsEntity"
        defaultBackupPath:
          type: string
          example: "/Users/jane/HearsayBackups"
        lastUsedBackupPath:
          type: string
          example: "/Users/jane/HearsayBackups"
        otherSettings:
          type: object
          example: {}
        osDetails:
          "$ref": "#/components/schemas/OSDetailsEntity"
        lastAppUsedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        lastUsedAppVersion:
          type: string
          example: 2.1.15
        defaultSettings:
          type: object
          example:
            contactAdminDeviceRequestText: Please add another device to my account.
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - systemId
      - deviceSettings
      - defaultBackupPath
      - lastUsedBackupPath
      - otherSettings
      - osDetails
      - lastAppUsedAt
      - lastUsedAppVersion
      - defaultSettings
    ExtractionCodeConfigDateRangeEntity:
      type: object
      properties:
        start:
          format: date-time
          type: string
          nullable: true
          example:
        end:
          format: date-time
          type: string
          nullable: true
          example:
      required:
      - start
      - end
    ExtractionCodeConfigAppConfigConcurrencyEntity:
      type: object
      properties:
        mediaType:
          type: string
          enum:
          - images
          - video
          - audio
          - documents
        concurrency:
          example:
          - 4
          - 6
          - 12
          - 16
          type: array
          items:
            type: number
      required:
      - mediaType
      - concurrency
    ExtractionCodeConfigAppConfigEntity:
      type: object
      properties:
        conversionConcurrency:
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeConfigAppConfigConcurrencyEntity"
        compressionConcurrency:
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeConfigAppConfigConcurrencyEntity"
        enableLocalMediaConversion:
          type: boolean
          example: true
        enableLocalMediaCompression:
          type: boolean
          example: true
        enableIosEncryptedBackupExtraction:
          type: boolean
          example: false
        allowOnlyEncryptedIosBackup:
          type: boolean
          example: false
        autoIncludeSafariData:
          type: boolean
          example: false
        autoIncludeNotesData:
          type: boolean
          example: false
        autoIncludeContacts:
          type: boolean
          default: false
          example: false
        shareAction:
          type: string
          enum:
          - share
          - export
          - share_and_export
          default: share
      required:
      - conversionConcurrency
      - compressionConcurrency
    ExtractionCodeConfigEnableLocalExportDefaultConfigEntity:
      type: object
      properties:
        chunkType:
          type: string
          enum:
          - year
          - month
          - week
          - day
          - none
        includeAttachments:
          type: boolean
          example: true
      required:
      - chunkType
      - includeAttachments
    ExtractionCodeConfigEnableLocalExportEntity:
      type: object
      properties:
        formats:
          example:
          - pdf
          type: array
          items:
            type: string
        defaultConfig:
          "$ref": "#/components/schemas/ExtractionCodeConfigEnableLocalExportDefaultConfigEntity"
    ExtractionCodeConfigAllowClientEntity:
      type: object
      properties:
        allowSocialApps:
          example:
          - whatsapp
          type: array
          items:
            type: string
        allowShareAll:
          type: boolean
          example: false
      required:
      - allowSocialApps
      - allowShareAll
    ExtractionCodeConfigAutoShareSearchesEntity:
      type: object
      properties:
        type:
          type: string
          enum:
          - name
          - phone
          - keywords
          - email
          default: name
        values:
          example:
          - Jane Doe
          type: array
          items:
            type: string
        apps:
          example:
          - whatsapp
          type: array
          items:
            type: string
        dateRange:
          "$ref": "#/components/schemas/ExtractionCodeConfigDateRangeEntity"
        exactMatch:
          type: boolean
          example: false
        include:
          type: boolean
          example: true
        conversationType:
          type: string
          enum:
          - all
          - dm
          - group
          default: all
      required:
      - type
      - values
      - apps
      - dateRange
      - exactMatch
      - include
      - conversationType
    ExtractionCodeConfigAutoShareEntity:
      type: object
      properties:
        allowClientApproval:
          type: boolean
          example: false
        allowAdditionalShare:
          type: boolean
          example: false
        searches:
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeConfigAutoShareSearchesEntity"
      required:
      - allowClientApproval
      - allowAdditionalShare
      - searches
    ExtractionCodeConfigShareAllSearchesExcludeEntity:
      type: object
      properties:
        type:
          type: string
          enum:
          - name
          - phone
          - keywords
          - email
          default: name
        values:
          example:
          - Jane Doe
          type: array
          items:
            type: string
        apps:
          example:
          - whatsapp
          type: array
          items:
            type: string
        exactMatch:
          type: boolean
          example: false
        conversationType:
          type: string
          enum:
          - all
          - dm
          - group
          default: all
      required:
      - type
      - values
      - apps
      - exactMatch
      - conversationType
    ExtractionCodeConfigShareAllEntity:
      type: object
      properties:
        apps:
          example:
          - whatsapp
          type: array
          items:
            type: string
        includeAttachments:
          type: boolean
          example: true
        dateRange:
          "$ref": "#/components/schemas/ExtractionCodeConfigDateRangeEntity"
        searchesExclude:
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeConfigShareAllSearchesExcludeEntity"
      required:
      - apps
      - includeAttachments
    ExtractionCodeConfigEntity:
      type: object
      properties:
        type:
          type: string
          enum:
          - allow_client
          - share_all
          - auto_share
          default: allow_client
        origin:
          type: string
          example: Website
          nullable: true
        ignoreMediaPermissionAndroid:
          type: boolean
          example: false
        dateRange:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/ExtractionCodeConfigDateRangeEntity"
        appConfig:
          "$ref": "#/components/schemas/ExtractionCodeConfigAppConfigEntity"
        enableLocalExport:
          "$ref": "#/components/schemas/ExtractionCodeConfigEnableLocalExportEntity"
        allowClient:
          "$ref": "#/components/schemas/ExtractionCodeConfigAllowClientEntity"
        autoShare:
          "$ref": "#/components/schemas/ExtractionCodeConfigAutoShareEntity"
        shareAll:
          "$ref": "#/components/schemas/ExtractionCodeConfigShareAllEntity"
      required:
      - type
      - origin
    MasterDataSourceEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: gmail
        displayName:
          type: string
          example: Gmail
        category:
          type: string
          example: email
        config:
          type: object
          description: Collection options for this source. The keys below are the
            ones the code reads, for device sources (ios, android).
          properties:
            iosEnableEncryptedBackup:
              type: boolean
            iosAllowOnlyEncryptedBackup:
              type: boolean
            iosAutoIncludeSafariData:
              type: boolean
            iosAutoIncludeNotesData:
              type: boolean
            androidIgnoreMediaPermission:
              type: boolean
            iosAllApps:
              type: array
              items:
                type: string
            iosAllowedApps:
              type: array
              items:
                type: string
            androidAllApps:
              type: array
              items:
                type: string
            androidAllowedApps:
              type: array
              items:
                type: string
          additionalProperties: true
          example:
            iosEnableEncryptedBackup: true
            iosAllowOnlyEncryptedBackup: false
            iosAllApps:
            - messages
            - call_log
            - whatsapp
            - photos
            iosAllowedApps:
            - messages
            - call_log
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - displayName
      - category
      - config
    CaseDataSourceEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        dataSource:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/MasterDataSourceEntity"
        config:
          type: object
          example:
            iosEnableEncryptedBackup: true
            iosAllowOnlyEncryptedBackup: false
            iosAutoIncludeSafariData: true
            iosAutoIncludeNotesData: false
            iosAllowedApps:
            - messages
            - call_log
            - whatsapp
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - case
      - dataSource
      - config
    ExtractionCodeDataSourceEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        extractionCode:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        dataSource:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/MasterDataSourceEntity"
        caseDataSource:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/CaseDataSourceEntity"
        config:
          type: object
          example:
            iosEnableEncryptedBackup: true
            iosAllowOnlyEncryptedBackup: false
            iosAllApps:
            - messages
            - call_log
            - whatsapp
            - photos
            iosAllowedApps:
            - messages
            - call_log
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - case
      - extractionCode
      - dataSource
      - caseDataSource
      - config
    ExtractionCodeEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        code:
          type: string
          example: K7Q2-9XPL
        email:
          type: string
          example: jane.doe@example.com
        name:
          type: string
          example: Jane Doe
          nullable: true
        phone:
          type: string
          nullable: true
          example: "+15555550100"
        isActive:
          type: boolean
          example: true
        label:
          type: string
          example: Primary phone
          nullable: true
        config:
          "$ref": "#/components/schemas/ExtractionCodeConfigEntity"
        devices:
          type: array
          items:
            type: object
          nullable: true
          example: []
          description: Not loaded by any route, so always null or an empty list.
        totalDevicesCount:
          type: number
          example: 1
        dueDate:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        sendEmail:
          type: boolean
          default: false
          example: true
        lockedAt:
          format: date-time
          type: string
          example:
          nullable: true
        hasDueDateCrossed:
          type: boolean
          example: false
        status:
          type: string
          example: pending
        reviewStatus:
          type: string
          enum:
          - approved
          - rejected
          - not_reviewed
          - na
          nullable: true
        reviewMessage:
          type: string
          nullable: true
          example:
        lastApprovedBy:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        ocReviewedByOrg:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/OrganizationEntity"
        ocReviewedAt:
          format: date-time
          type: string
          nullable: true
          example:
        ocRequired:
          type: boolean
          default: true
          example: true
        allowShareAll:
          type: boolean
          deprecated: true
          example: false
        metadata:
          type: object
          example: {}
        logsConfig:
          type: object
          example: {}
        sources:
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeDataSourceEntity"
        payment:
          type: object
          example:
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - code
      - email
      - name
      - phone
      - isActive
      - label
      - config
      - devices
      - totalDevicesCount
      - hasDueDateCrossed
      - status
      - reviewStatus
      - reviewMessage
      - lastApprovedBy
      - ocReviewedByOrg
      - ocReviewedAt
      - ocRequired
      - allowShareAll
      - metadata
      - logsConfig
      - payment
    ExtractionLoginCaseEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: Doe v. Example Corp
        slug:
          type: string
          example: doe-v-example-corp
        description:
          type: string
          example: Employment dispute
          nullable: true
        matterId:
          type: string
          nullable: true
          example: MAT-0042
        status:
          type: string
          enum:
          - open
          - closed
          - archived
          - pending
          example: open
        assignStatus:
          type: string
          enum:
          - assigned
          - unassigned
          example: assigned
        createdBy:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        assignedTo:
          description: Assigned user ids
          example:
          - 507f1f77bcf86cd799439011
          type: array
          items:
            type: string
        organization:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/OrganizationEntity"
        isActive:
          type: boolean
          example: true
        priority:
          type: string
          enum:
          - low
          - medium
          - high
          - urgent
          example: medium
        attachedDevices:
          description: Attached device ids
          example:
          - 507f1f77bcf86cd799439012
          type: array
          items:
            type: string
        totalConversations:
          type: number
          example: 128
        archivedBy:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        exportJobId:
          type: string
          example: job-1234
          nullable: true
        metadata:
          type: object
          example: {}
        extractionCodePaymentThreadId:
          type: string
          nullable: true
          example:
        ocCase:
          type: boolean
          example: false
        isOcShared:
          type: boolean
          example: false
        proEnabled:
          type: boolean
          example: false
        isArchived:
          type: boolean
          example: false
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - slug
      - description
      - status
      - assignStatus
      - createdBy
      - assignedTo
      - organization
      - isActive
      - priority
      - attachedDevices
      - totalConversations
      - archivedBy
      - exportJobId
      - metadata
      - extractionCodePaymentThreadId
      - ocCase
      - isOcShared
      - proEnabled
      - isArchived
    LoginResponseEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        accessToken:
          type: string
          example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2NWExYjJjMyJ9.c2lnbmF0dXJl
        refreshToken:
          type: string
          example: b7c1e2f0a9d84c3e8f6a5b4c3d2e1f00
        userProfile:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserEntity"
        appConfig:
          "$ref": "#/components/schemas/AppConfigEntity"
        extractionCodeDetails:
          "$ref": "#/components/schemas/ExtractionCodeEntity"
        caseDetails:
          description: Sent only by the extraction-code login
          allOf:
          - "$ref": "#/components/schemas/ExtractionLoginCaseEntity"
        collectionEventsEnabled:
          type: boolean
          description: Desktop/portal should send collection events and heartbeats
          example: true
      required:
      - createdAt
      - updatedAt
      - id
      - accessToken
      - refreshToken
      - userProfile
    InvitationAcceptResponseEntity:
      type: object
      properties:
        login:
          "$ref": "#/components/schemas/LoginResponseEntity"
        organization:
          "$ref": "#/components/schemas/OrganizationDetailsResponseEntity"
      required:
      - organization
    AcceptOrganizationInvitationRequestDto:
      type: object
      properties:
        token:
          type: string
        email:
          type: string
        name:
          type: string
        password:
          type: string
      required:
      - token
      - email
    RejectOrganizationInvitationRequestDto:
      type: object
      properties:
        token:
          type: string
        email:
          type: string
        reason:
          type: string
      required:
      - token
      - email
    UpdateOrganizationUserAccess:
      type: object
      properties:
        id:
          type: string
        accessLevel:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
          default: member
      required:
      - id
      - accessLevel
    UpdateOrganizationUserAccessDto:
      type: object
      properties:
        users:
          type: array
          items:
            "$ref": "#/components/schemas/UpdateOrganizationUserAccess"
      required:
      - users
    UpdateOrganizationUser:
      type: object
      properties:
        id:
          type: string
        role:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
        name:
          type: string
          maxLength: 200
          minLength: 3
        email:
          type: string
          maxLength: 100
      required:
      - id
    UpdateOrganizationUsersDto:
      type: object
      properties:
        users:
          type: array
          items:
            "$ref": "#/components/schemas/UpdateOrganizationUser"
      required:
      - users
    DeviceRequestContactAdminDto:
      type: object
      properties:
        contactAdminDeviceRequestText:
          type: string
        caseId:
          type: string
    PaginatedResponseEntity:
      type: object
      properties:
        total:
          type: number
          example: 42
        page:
          type: number
          example: 1
        perPage:
          type: number
          example: 20
        skip:
          type: number
          example: 0
        limit:
          type: number
          example: 20
        totalPages:
          type: number
          example: 3
        nextPage:
          type: number
          example: 2
          nullable: true
        previousPage:
          type: number
          example:
          nullable: true
      required:
      - total
      - totalPages
      - nextPage
      - previousPage
    TagEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: Important
        nameLowercase:
          type: string
          example: important
        backgroundColor:
          type: string
          example: "#FDE68A"
        textColor:
          type: string
          example: "#92400E"
        type:
          type: string
          enum:
          - system
          - organization
          - case
          - user
        severity:
          type: string
          enum:
          - info
          - warning
          - danger
        origin:
          type: string
          enum:
          - manual
          - auto-import
        creatorAccessType:
          type: string
          enum:
          - org
          - opposing_council
          nullable: true
        isActive:
          type: boolean
          example: true
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - nameLowercase
      - backgroundColor
      - textColor
      - type
      - severity
      - origin
      - isActive
    CreateOrganizationTagDto:
      type: object
      properties:
        name:
          type: string
        backgroundColor:
          type: string
        textColor:
          type: string
        severity:
          type: string
          enum:
          - info
          - warning
          - danger
          default: info
      required:
      - name
    UpdateOrganizationTagDto:
      type: object
      properties:
        name:
          type: string
        backgroundColor:
          type: string
        textColor:
          type: string
        severity:
          type: string
          enum:
          - info
          - warning
          - danger
          default: info
    CaseIntegrationEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        integrationType:
          type: string
          enum:
          - goodfact
          - clio
          - smokeball
          example: clio
        attachedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        remoteCaseId:
          type: string
          example: '1234567890'
        remoteCaseName:
          type: string
          example: Doe v. Example Corp
      required:
      - createdAt
      - updatedAt
      - id
      - integrationType
      - attachedAt
      - remoteCaseId
      - remoteCaseName
    AttachedTagResponseEntity:
      type: object
      properties:
        tagId:
          type: string
          description: Tag id (Tag._id)
          example: 507f1f77bcf86cd799439011
        tag:
          type: string
          description: Tag display name (Tag.name)
          example: Important
        addedBy:
          type: string
          enum:
          - user
          - ai
          - auto
          - auto-import
          - system
          - default
        addedById:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439012
        removedAt:
          format: date-time
          type: string
          nullable: true
          example:
        removedBy:
          type: string
          nullable: true
          enum:
          - user
          - ai
          - auto
          - auto-import
          - system
          - default
          example:
        removedById:
          type: string
          nullable: true
          example:
      required:
      - tagId
      - tag
      - addedBy
    CaseEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: Doe v. Example Corp
        slug:
          type: string
          example: doe-v-example-corp
        description:
          type: string
          example: Employment dispute
          nullable: true
        matterId:
          type: string
          nullable: true
          example: MAT-0042
        status:
          type: string
          enum:
          - open
          - closed
          - archived
          - pending
          example: open
        assignStatus:
          type: string
          enum:
          - assigned
          - unassigned
          example: assigned
        createdBy:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        assignedTo:
          description: Assigned user ids
          example:
          - 507f1f77bcf86cd799439011
          type: array
          items:
            type: string
        organization:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/OrganizationEntity"
        isActive:
          type: boolean
          example: true
        tags:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/TagEntity"
        priority:
          type: string
          enum:
          - low
          - medium
          - high
          - urgent
          example: medium
        attachedDevices:
          description: Attached device ids
          example:
          - 507f1f77bcf86cd799439012
          type: array
          items:
            type: string
        integrations:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/CaseIntegrationEntity"
        totalConversations:
          type: number
          example: 128
        archivedBy:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        exportJobId:
          type: string
          example: job-1234
          nullable: true
        metadata:
          type: object
          example: {}
        extractionCodePaymentThreadId:
          type: string
          nullable: true
          example:
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
        ocCase:
          type: boolean
          example: false
        isOcShared:
          type: boolean
          example: false
        proEnabled:
          type: boolean
          example: false
        isArchived:
          type: boolean
          example: false
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - slug
      - description
      - status
      - assignStatus
      - createdBy
      - assignedTo
      - organization
      - isActive
      - tags
      - priority
      - attachedDevices
      - integrations
      - totalConversations
      - archivedBy
      - exportJobId
      - metadata
      - extractionCodePaymentThreadId
      - ocCase
      - isOcShared
      - proEnabled
      - isArchived
    UserWithCaseAccessEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        user:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        status:
          type: string
          example: active
        level:
          type: string
          example: admin
        lastAccessedAt:
          format: date-time
          type: string
          example: '2026-01-16T09:12:00.000Z'
      required:
      - createdAt
      - updatedAt
      - id
      - user
      - status
      - level
      - lastAccessedAt
    CaseInvitationEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        invitedUserEmail:
          type: string
          example: jane.doe@example.com
        invitedUserName:
          type: string
          example: Jane Doe
        invitedBy:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        organization:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        case:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        invitedUserRole:
          type: string
          example: member
        status:
          type: string
          example: pending
        emailSent:
          type: boolean
          example: true
        emailSentAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        acceptedAt:
          format: date-time
          type: string
          example:
          nullable: true
        acceptedBy:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        invitedUser:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        rejectedAt:
          format: date-time
          type: string
          example:
          nullable: true
        rejectedReason:
          type: string
          example: Not involved in this matter
          nullable: true
        initials:
          type: string
          example: JD
      required:
      - createdAt
      - updatedAt
      - id
      - invitedUserEmail
      - invitedUserName
      - invitedBy
      - organization
      - case
      - invitedUserRole
      - status
      - emailSent
      - emailSentAt
      - acceptedAt
      - acceptedBy
      - invitedUser
      - rejectedAt
      - rejectedReason
      - initials
    CaseListItemEntity:
      type: object
      properties:
        case:
          "$ref": "#/components/schemas/CaseEntity"
        usersWithAccess:
          type: array
          items:
            "$ref": "#/components/schemas/UserWithCaseAccessEntity"
        usersWithPendingAccess:
          nullable: true
          type: array
          items:
            "$ref": "#/components/schemas/CaseInvitationEntity"
        conversationsCount:
          type: number
          example: 128
        extractionCodes:
          nullable: true
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeEntity"
        totalExtractionCodes:
          type: number
          example: 5
        dataUsedInBytes:
          type: number
          example: 52428800
        dataSourcesStatusCount:
          type: object
          example:
            completed: 4
            in_progress: 1
      required:
      - case
      - usersWithAccess
      - usersWithPendingAccess
      - conversationsCount
      - extractionCodes
      - totalExtractionCodes
      - dataUsedInBytes
      - dataSourcesStatusCount
    CaseViewEntity:
      type: object
      properties:
        case:
          "$ref": "#/components/schemas/CaseEntity"
        usersWithAccess:
          type: array
          items:
            "$ref": "#/components/schemas/UserWithCaseAccessEntity"
        usersWithPendingAccess:
          nullable: true
          type: array
          items:
            "$ref": "#/components/schemas/CaseInvitationEntity"
        conversationsCount:
          type: number
          example: 128
        extractionCodes:
          nullable: true
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeEntity"
        totalExtractionCodes:
          type: number
          example: 5
        dataSourcesStatusCount:
          type: object
          example:
            completed: 4
            in_progress: 1
      required:
      - case
      - usersWithAccess
      - usersWithPendingAccess
      - conversationsCount
      - extractionCodes
      - totalExtractionCodes
      - dataSourcesStatusCount
    ExtractionCodeConfigEnableLocalExportDefaultConfigDto:
      type: object
      properties:
        chunkType:
          type: string
          enum:
          - year
          - month
          - week
          - day
          - none
          default: none
        includeAttachments:
          type: boolean
          default: true
      required:
      - chunkType
    ExtractionCodeConfigEnableLocalExportDto:
      type: object
      properties:
        formats:
          default: []
          type: array
          items:
            type: string
        defaultConfig:
          default:
            chunkType: none
            includeAttachments: true
          allOf:
          - "$ref": "#/components/schemas/ExtractionCodeConfigEnableLocalExportDefaultConfigDto"
    ExtractionCodeConfigAllowClientDto:
      type: object
      properties:
        allowSocialApps:
          default: []
          type: array
          items:
            type: string
        allowShareAll:
          type: boolean
          default: false
    ExtractionCodeConfigDateRangeDto:
      type: object
      properties:
        start:
          type: string
        end:
          type: string
      required:
      - start
      - end
    ExtractionCodeConfigAutoShareSearchesDto:
      type: object
      properties:
        type:
          type: string
          enum:
          - name
          - phone
          - keywords
          - email
          default: name
        values:
          type: array
          items:
            type: string
        apps:
          default: []
          type: array
          items:
            type: string
        dateRange:
          default:
          allOf:
          - "$ref": "#/components/schemas/ExtractionCodeConfigDateRangeDto"
        exactMatch:
          type: boolean
          default: false
        include:
          type: boolean
          default: true
        conversationType:
          type: string
          enum:
          - all
          - dm
          - group
          default: all
      required:
      - type
      - values
      - conversationType
    ExtractionCodeConfigAutoShareDto:
      type: object
      properties:
        allowClientApproval:
          type: boolean
        allowAdditionalShare:
          type: boolean
        searches:
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeConfigAutoShareSearchesDto"
    ExtractionCodeConfigShareAllSearchesExcludeDto:
      type: object
      properties:
        type:
          type: string
          enum:
          - name
          - phone
          - keywords
          - email
          default: name
        values:
          type: array
          items:
            type: string
        apps:
          type: array
          items:
            type: string
        exactMatch:
          type: boolean
        conversationType:
          type: string
          enum:
          - all
          - dm
          - group
          default: all
      required:
      - type
      - values
      - conversationType
    ExtractionCodeConfigShareAllDto:
      type: object
      properties:
        apps:
          type: array
          items:
            type: string
        includeAttachments:
          type: boolean
          default: true
        dateRange:
          "$ref": "#/components/schemas/ExtractionCodeConfigDateRangeDto"
        searchesExclude:
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeConfigShareAllSearchesExcludeDto"
    ExtractionCodeConfigAppConfigConcurrencyDto:
      type: object
      properties:
        mediaType:
          type: string
          enum:
          - images
          - video
          - audio
          - documents
        concurrency:
          type: array
          items:
            type: number
      required:
      - mediaType
    ExtractionCodeConfigAppConfigDto:
      type: object
      properties:
        conversionConcurrency:
          "$ref": "#/components/schemas/ExtractionCodeConfigAppConfigConcurrencyDto"
        compressionConcurrency:
          "$ref": "#/components/schemas/ExtractionCodeConfigAppConfigConcurrencyDto"
        enableLocalMediaConversion:
          type: boolean
          default: false
        enableLocalMediaCompression:
          type: boolean
          default: false
        enableIosEncryptedBackupExtraction:
          type: boolean
          default: false
        allowOnlyEncryptedIosBackup:
          type: boolean
          default: false
        autoIncludeSafariData:
          type: boolean
          default: false
        autoIncludeNotesData:
          type: boolean
          default: false
        autoIncludeContacts:
          type: boolean
          default: false
        shareAction:
          type: string
          enum:
          - share
          - export
          - share_and_export
          default: share
    ExtractionCodeConfigDto:
      type: object
      properties:
        type:
          type: string
          enum:
          - allow_client
          - share_all
          - auto_share
          default: allow_client
        ignoreMediaPermissionAndroid:
          type: boolean
          default: false
        origin:
          type: string
        enableFileUpload:
          type: boolean
          default: false
        enableLocalExport:
          "$ref": "#/components/schemas/ExtractionCodeConfigEnableLocalExportDto"
        allowClient:
          "$ref": "#/components/schemas/ExtractionCodeConfigAllowClientDto"
        autoShare:
          "$ref": "#/components/schemas/ExtractionCodeConfigAutoShareDto"
        shareAll:
          "$ref": "#/components/schemas/ExtractionCodeConfigShareAllDto"
        dateRange:
          "$ref": "#/components/schemas/ExtractionCodeConfigDateRangeDto"
        appConfig:
          "$ref": "#/components/schemas/ExtractionCodeConfigAppConfigDto"
      required:
      - type
    ExtractionCodeSendRemindersDto:
      type: object
      properties:
        dueIn2Weeks:
          type: boolean
          default: false
        dueIn1Week:
          type: boolean
          default: false
        dueIn3Days:
          type: boolean
          default: false
        dueIn1Day:
          type: boolean
          default: false
        after1Week:
          type: boolean
          default: false
        after2Weeks:
          type: boolean
          default: false
        after1Month:
          type: boolean
          default: false
    PrepaidItemDto:
      type: object
      properties:
        productKey:
          type: string
          enum:
          - prepaid.device
          - prepaid.email
          - prepaid.device-email
          - prepaid.support
        quantity:
          type: number
          default: 1
        unitAmountCents:
          type: number
          description: Negotiated unit price in cents
      required:
      - productKey
      - quantity
      - unitAmountCents
    InviteCaseUser:
      type: object
      properties:
        email:
          type: string
        name:
          type: string
          minLength: 1
          maxLength: 200
        accessLevel:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
          default: viewer
        sendInstructions:
          type: boolean
          default: false
        accessType:
          type: string
          enum:
          - org
          - opposing_council
          default: org
        orgName:
          type: string
          maxLength: 200
      required:
      - email
      - accessLevel
    CreateCaseDto:
      type: object
      properties:
        custodianEmail:
          type: string
          maxLength: 100
          minLength: 3
        custodianName:
          type: string
          maxLength: 100
          minLength: 3
        custodianPhone:
          type: string
          maxLength: 50
          minLength: 3
        sendEmail:
          type: boolean
        label:
          type: string
          maxLength: 100
          minLength: 3
        allowShareAll:
          type: boolean
          deprecated: true
          description: Use config instead
        config:
          "$ref": "#/components/schemas/ExtractionCodeConfigDto"
        requestedSources:
          type: array
          items:
            type: string
        dueDate:
          type: string
        sendReminders:
          "$ref": "#/components/schemas/ExtractionCodeSendRemindersDto"
        metadata:
          type: object
        watchlist:
          type: boolean
          default: false
        ocRequired:
          type: boolean
          default: true
          description: 'OC cases only: when false, the OL creates/keeps this code
            without the opposing-council approval gate (code stays `na`, immediately
            usable, and the custodian email is sent). Ignored on non-OC cases. Defaults
            to true (gate on).'
        paymentRequired:
          type: boolean
          description: Whether payment is required for this extraction code
        paymentAmount:
          type: number
          description: Payment amount in cents
        paymentCurrency:
          type: string
          description: Payment currency (defaults to usd)
          default: usd
        paymentSuccessRedirectUrl:
          type: string
          description: Payment success redirect URL
        productName:
          type: string
          description: Product name
        prepaidItems:
          description: Prepaid basket. Each entry becomes its own Stripe line item
            and grants units on payment. Prices are negotiable — unitAmountCents is
            what the customer is charged, and is never validated against the catalog
            default.
          type: array
          items:
            "$ref": "#/components/schemas/PrepaidItemDto"
        name:
          type: string
          maxLength: 100
          minLength: 3
        description:
          type: string
        matterId:
          type: string
          maxLength: 100
          description: The firm's own client/matter reference. Printed on Stripe invoice
            line items and carried in invoice metadata.
        tags:
          type: array
          items:
            type: string
        status:
          type: string
          enum:
          - open
          - closed
          - archived
          - pending
          default: pending
        priority:
          type: string
          enum:
          - low
          - medium
          - high
          - urgent
          default: low
        isDefault:
          type: boolean
          default: false
        createExtractionCode:
          type: boolean
          default: false
        addExistingUsersToCase:
          type: boolean
          default: false
        addExistingUsersAccessLevel:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
          default: admin
        users:
          type: array
          items:
            "$ref": "#/components/schemas/InviteCaseUser"
      required:
      - dueDate
      - name
    CheckCaseNameResponseEntity:
      type: object
      properties:
        isAvailable:
          type: boolean
          example: true
      required:
      - isAvailable
    CaseDataSourceListItemEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: string
          example: 507f1f77bcf86cd799439011
        dataSource:
          type: string
          description: Id of the master data source
          example: 507f1f77bcf86cd799439012
      required:
      - createdAt
      - updatedAt
      - id
      - case
      - dataSource
    UpdateCaseDto:
      type: object
      properties:
        name:
          type: string
          maxLength: 100
          minLength: 3
        description:
          type: string
        matterId:
          type: string
          maxLength: 100
          description: The firm's own client/matter reference. Printed on Stripe invoice
            line items and carried in invoice metadata.
        tags:
          type: array
          items:
            type: string
        status:
          type: string
          enum:
          - open
          - closed
          - archived
          - pending
          default: pending
        priority:
          type: string
          enum:
          - low
          - medium
          - high
          - urgent
          default: low
        metadata:
          type: object
    ArchiveCaseDto:
      type: object
      properties:
        deleteImmediately:
          type: boolean
          description: Schedule to delete immediately after archiving
          example: false
          default: false
      required:
      - deleteImmediately
    LinkConversationsToCaseDto:
      type: object
      properties:
        conversations:
          type: array
          items:
            type: array
      required:
      - conversations
    UnlinkConversationsFromCaseDto:
      type: object
      properties:
        conversations:
          type: array
          items:
            type: array
      required:
      - conversations
    CaseCollectionExportEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        organization:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        device:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        type:
          type: string
          enum:
          - doc
          - text
          - csv
          - pdf
          - goodfact
          - rsmf
          - eml
          - files-zip
        source:
          type: string
          enum:
          - conversation
          - browser-history
          - emails
          - case-files
          - case-attachments
          - financial
          - linkedin
          - ai-chat
          - contacts
        chunkType:
          type: string
          enum:
          - none
          - day
          - week
          - month
          - year
        exportedBy:
          type: string
          nullable: true
          description: Id of the user who requested the export
          example: 507f1f77bcf86cd799439011
        tags:
          type: array
          items:
            type: object
          nullable: true
          example: []
          description: Not loaded by any route, so always null or an empty list.
        emails:
          example:
          - jane.doe@example.com
          type: array
          items:
            type: string
        caseFileIds:
          description: Snapshot of CaseFile ids resolved at request time (only set
            when source = case-files)
          example:
          - 507f1f77bcf86cd799439015
          type: array
          items:
            type: string
        fileName:
          type: string
          example: doe-v-example-export.pdf
          nullable: true
        file:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileEntity"
        sortBy:
          type: string
          enum:
          - old-to-new
          - new-to-old
        size:
          type: number
          description: Size in bytes
          example: 1048576
        errorMessage:
          type: string
          example:
          nullable: true
        totalMessages:
          type: number
          example: 1200
        messagesProcessed:
          type: number
          example: 600
        totalConversations:
          type: number
          example: 12
        conversationsProcessed:
          type: number
          example: 6
        status:
          type: string
          enum:
          - not-started
          - inprogress
          - completed
          - failed
          - cancelled
        jobId:
          type: string
          example: job-1234
          nullable: true
        totalJobs:
          type: number
          example: 1
          description: Number of chunk jobs this export was split into
        emailData:
          example: []
          items:
            type: array
            items:
              type: object
          type: array
        metadata:
          type: object
          example:
          nullable: true
        failedThreadsCount:
          type: number
          example: 0
          description: Email threads this export could not render
        cmpSyncStatus:
          type: string
          enum:
          - pending
          - processing
          - completed
          - failed
          - disabled
          - skipped
          nullable: true
          description: CMP delivery status when this export was sent to a provider
            (pending/processing/completed/failed/…). Null when the export was not
            sent to a CMP — i.e. a plain download-to-computer export.
          example:
        progress:
          type: number
          example: 100
        conversationNames:
          example:
          - Jane Doe
          type: array
          items:
            type: string
        caseName:
          type: string
          example: Doe v. Example Corp
      required:
      - createdAt
      - updatedAt
      - id
      - organization
      - device
      - type
      - source
      - chunkType
      - exportedBy
      - tags
      - emails
      - fileName
      - file
      - sortBy
      - size
      - errorMessage
      - totalMessages
      - messagesProcessed
      - totalConversations
      - conversationsProcessed
      - status
      - jobId
      - totalJobs
      - emailData
      - progress
      - conversationNames
      - caseName
    EmailExportEmailSearchDto:
      type: object
      properties:
        searchText:
          type: string
          description: Text search applied to all indexed fields (subject, body, snippet,
            from/to/cc/bcc). Supports the same AND / OR / NOT / -prefix, parentheses,
            "quoted phrases" and wildcards as the boolean query parser.
          example: (discriminate OR racist) AND ("Larry Graham" OR Robinson)
        folders:
          type: array
          items:
            type: string
        domains:
          description: Filter by participant domain(s) (part after @), any-of. Case-insensitive.
            Requires the `domains` field on the emailtext Atlas index.
          type: array
          items:
            type: string
        excludeDomains:
          description: Exclude items whose domain(s) match any of these (any-of).
            Case-insensitive. Must not overlap `domains`.
          type: array
          items:
            type: string
        tags:
          type: array
          items:
            type: string
        excludeTags:
          description: Exclude items carrying any of these tag ids. Must not overlap
            `tags`.
          type: array
          items:
            type: string
        untagged:
          type: boolean
          description: When truthy (1/true), return only emails with no tags attached.
            Takes precedence over the `tags` filter.
        participants:
          description: Filter by participant email address(es), any-of.
          type: array
          items:
            type: string
        excludeEmails:
          description: Exclude items whose participant address matches any of these
            (any-of). Must not overlap `participants`.
          type: array
          items:
            type: string
        startDate:
          type: string
          format: date-time
          description: Only return emails whose `date` is at/after this instant (inclusive).
            Accepts an ISO-8601 date-time.
          example: '2024-01-01T00:00:00.000Z'
        endDate:
          type: string
          format: date-time
          description: Only return emails whose `date` is at/before this instant (inclusive).
            Accepts an ISO-8601 date-time.
          example: '2024-12-31T23:59:59.999Z'
        ids:
          description: Restrict to these email ids, AND-intersected with the other
            filters.
          type: array
          items:
            type: string
        exportWholeThread:
          type: boolean
          default: false
          description: When true, export the full threads containing the matched emails.
            When false (default), export only the matched emails, grouped under their
            thread.
    EmailExportThreadSearchDto:
      type: object
      properties:
        folders:
          type: array
          items:
            type: string
        domains:
          description: Filter by participant domain(s) (part after @), any-of. Case-insensitive.
          type: array
          items:
            type: string
        excludeDomains:
          description: Exclude items whose domain(s) match any of these (any-of).
            Case-insensitive. Must not overlap `domains`.
          type: array
          items:
            type: string
        tags:
          type: array
          items:
            type: string
        excludeTags:
          description: Exclude items carrying any of these tag ids. Must not overlap
            `tags`.
          type: array
          items:
            type: string
        participants:
          description: Filter by participant email address(es), any-of.
          type: array
          items:
            type: string
        excludeEmails:
          description: Exclude items whose participant address matches any of these
            (any-of). Must not overlap `participants`.
          type: array
          items:
            type: string
        starred:
          type: boolean
        unread:
          type: boolean
        untagged:
          type: boolean
          description: When truthy (1/true), return only threads with no tags attached.
            Takes precedence over the `tags` filter.
        startDate:
          type: string
          format: date-time
          description: Only return threads whose `latestMessageReceivedDate` is at/after
            this instant (inclusive). Accepts an ISO-8601 date-time.
          example: '2024-01-01T00:00:00.000Z'
        endDate:
          type: string
          format: date-time
          description: Only return threads whose `latestMessageReceivedDate` is at/before
            this instant (inclusive). Accepts an ISO-8601 date-time.
          example: '2024-12-31T23:59:59.999Z'
        ids:
          description: Restrict to these thread ids, AND-intersected with the other
            filters.
          type: array
          items:
            type: string
    EmailExportConfigDto:
      type: object
      properties:
        providerId:
          type: string
          description: Email data provider to export from
        emailSearch:
          "$ref": "#/components/schemas/EmailExportEmailSearchDto"
        threadSearch:
          "$ref": "#/components/schemas/EmailExportThreadSearchDto"
        includeAttachments:
          type: boolean
          default: false
          description: When true, each email's attachments are bundled into the export
            alongside the rendered PDF/EML output. Attachments are streamed to S3
            and are subject to the files-zip size limits.
      required:
      - providerId
    CaseCollectionExportDto:
      type: object
      properties:
        collection:
          type: string
          deprecated: true
        conversations:
          type: array
          items:
            type: array
        excludeConversations:
          type: array
          items:
            type: array
        excludeDeviceSources:
          type: array
          description: Device conversation sources to exclude from the export. Applies
            to device-conversation exports only.
          items:
            type: string
            enum:
            - message
            - whatsapp
            - voicemail
            - call_log
            - facebook_dump
            - instagram_dump
            - threads_dump
            - email
        type:
          type: string
          enum:
          - doc
          - text
          - csv
          - pdf
          - goodfact
          - rsmf
          - eml
          - files-zip
        source:
          type: string
          enum:
          - conversation
          - browser-history
          - emails
          - case-files
          - case-attachments
          - financial
          - linkedin
          - ai-chat
          - contacts
        chunkType:
          type: string
          enum:
          - none
          - day
          - week
          - month
          - year
          default: none
        emails:
          description: Additional email addresses to send email to when export completes
            (other than user email)
          type: array
          items:
            type: string
        fileName:
          type: string
        exportConfig:
          type: array
          items:
            type: string
            enum:
            - phone-numbers
            - comments
            - tags
            - app-icons
            - device-name
            - images
            - hide-attachment-links
            - extra-texts
            - line-break
            - device-logs
            - custom-entry
            - recently-deleted
            - deleted-messages-inline
            - deleted-messages-page
            - deleted-messages-inline-page
            - ignore-conversations
            - search-history-page
            - hide-page-numbers
            - print-footer-work-timestamp
        sortBy:
          type: string
          enum:
          - old-to-new
          - new-to-old
        startDate:
          type: string
        endDate:
          type: string
        tags:
          type: array
          items:
            type: array
        tagsPadding:
          type: number
        device:
          type: string
        emailExportConfig:
          "$ref": "#/components/schemas/EmailExportConfigDto"
        includeHiddenEmailThreads:
          type: boolean
          default: false
          description: When true, hidden threads (hiddenAt != null) are included in
            the email export. Default false. Does not affect explicitly-provided emailDataThreads
            ids, which are always exported.
        timezone:
          type: string
          description: IANA timezone string (e.g. "America/New_York") for formatting
            the export timestamp
      required:
      - type
      - sortBy
    DeviceEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        deviceId:
          type: string
          example: 3F2504E0-4F89-11D3-9A0C-0305E82C3301
        serialNumber:
          type: string
          example: F2LXK0QJHG7F
        name:
          type: string
          example: Jane's iPhone
        type:
          type: string
          example: ios
        nonDeviceType:
          type: string
          nullable: true
          example:
        model:
          type: string
          example: iPhone 15
        timeZone:
          type: string
          example: America/New_York
        osVersion:
          type: string
          example: '18.0'
        owner:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        devicePhoneNumber:
          type: string
          example: "+15555550100"
        organizations:
          description: Organization ids
          example:
          - 507f1f77bcf86cd799439011
          type: array
          items:
            type: string
        extractionCodes:
          description: Extraction code ids
          example:
          - 507f1f77bcf86cd799439013
          type: array
          items:
            type: string
        availableData:
          type: object
          description: Which data types exist for this device (optionally scoped by
            query filters).
          example:
            messages: true
            calls: false
      required:
      - createdAt
      - updatedAt
      - id
      - deviceId
      - serialNumber
      - name
      - type
      - nonDeviceType
      - model
      - timeZone
      - osVersion
      - owner
      - devicePhoneNumber
      - organizations
      - extractionCodes
    UserEmailDataProviderEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        email:
          type: string
          example: jane.doe@example.com
        ecCode:
          type: string
          description: 'Never sent today: no importer fills it.'
          example: K7Q2-9XPL
        grantId:
          type: string
          example: 5f0c8b3e-2a41-4d7c-9a51-1c2b3d4e5f60
          nullable: true
        provider:
          type: string
          example: google
          nullable: true
        isValid:
          type: boolean
          example: true
        lastAccessedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        folders:
          type: array
          description: Mailbox folders saved when the account was connected or its
            folders were refreshed.
          items:
            type: object
            properties:
              name:
                type: string
                example: INBOX
              id:
                type: string
                example: INBOX
              isSystemFolder:
                type: boolean
                example: true
              backgroundColor:
                type: string
                example: "#ffffff"
              metadata:
                type: object
                description: 'A copy of the provider''s folder record. OAuth (Nylas)
                  accounts: id, name, object, grantId, and depending on the provider
                  backgroundColor, textColor, systemFolder, parentId, childCount,
                  totalCount, unreadCount, attributes. IMAP accounts: name, id, isSystemFolder,
                  backgroundColor and an empty metadata object.'
                additionalProperties: true
                example:
                  grantId: 5f0c8b3e-2a41-4d7c-9a51-1c2b3d4e5f60
                  id: INBOX
                  name: INBOX
                  object: folder
                  systemFolder: true
          example:
          - backgroundColor: "#ffffff"
            id: INBOX
            isSystemFolder: true
            metadata:
              grantId: 5f0c8b3e-2a41-4d7c-9a51-1c2b3d4e5f60
              id: INBOX
              name: INBOX
              object: folder
              systemFolder: true
            name: INBOX
      required:
      - createdAt
      - updatedAt
      - id
      - email
      - grantId
      - provider
      - isValid
      - lastAccessedAt
      - folders
    CaseFileProviderEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        extractionCode:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
      required:
      - createdAt
      - updatedAt
      - id
      - case
      - extractionCode
    DiscordUserProviderEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        discordUserId:
          type: string
          example: '1012345678901234567'
        username:
          type: string
          example: janedoe
          nullable: true
        globalName:
          type: string
          example: Jane Doe
          nullable: true
        discriminator:
          type: number
          example: 0
          nullable: true
        email:
          type: string
          example: jane.doe@example.com
          nullable: true
        phone:
          type: string
          example: "+15555550100"
          nullable: true
        avatar:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        verified:
          type: boolean
          example: true
        flags:
          example: []
          type: array
          items:
            type: string
        servers:
          type: array
          items:
            type: object
          example:
          - name: Example Server
            serverId: '1023456789012345678'
        adsProfile:
          type: object
          example:
            age: 25-34
            gender: female
          nullable: true
        accountData:
          type: object
          example:
            discord_store:
              orders: []
          nullable: true
        settings:
          type: object
          example:
            locale: en-US
            theme: dark
          nullable: true
        metadata:
          type: object
          example:
            avatar_hash:
            has_mobile: true
            premium_until:
          nullable: true
        deletedAt:
          format: date-time
          type: string
          nullable: true
          example:
        deletedBy:
          type: object
          nullable: true
          example:
        ocSharedStatus:
          type: string
          nullable: true
          enum:
          - shared
          - pending
          - not_gated
          description: 'Opposing-council only: whether this provider has been shared
            with the OL. `null` for OL/OG callers — same convention as every other
            item listing. Provider-level: every item under it shares this status,
            since approval here is all-or-nothing.'
      required:
      - createdAt
      - updatedAt
      - id
      - discordUserId
      - username
      - globalName
      - discriminator
      - email
      - phone
      - avatar
      - verified
      - flags
      - servers
      - adsProfile
      - accountData
      - settings
      - metadata
      - deletedAt
      - deletedBy
    RedditUserProviderEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        redditUserId:
          type: string
          example: jane_doe_42
        email:
          type: string
          description: Not filled by any importer today, so always null.
          example:
          nullable: true
        emailVerified:
          type: boolean
          description: Not filled by any importer today, so always false.
          example: false
        registrationDate:
          format: date-time
          type: string
          description: Not filled by any importer today, so always null.
          example:
          nullable: true
        isDeleted:
          type: boolean
          example: false
        accountGender:
          type: string
          example: female
          nullable: true
        birthdate:
          type: string
          example: '1990-04-12'
          nullable: true
        linkedIdentities:
          type: array
          items:
            type: object
          example:
          - issuerId: https://accounts.google.com
            subjectId: '104857600123456789012'
        friends:
          type: array
          items:
            type: object
          example:
          - note: ''
            username: john_smith_7
        subscribedSubreddits:
          example:
          - AskReddit
          - personalfinance
          type: array
          items:
            type: string
        gildedContent:
          type: array
          items:
            type: object
          example:
          - amount: 1
            award: gold
            contentLink: https://www.reddit.com/r/AskReddit/comments/1abc2de/comment/kx1y2z3/
            date: '2026-01-15T14:30:00.000Z'
        multireddits:
          type: array
          items:
            type: object
          example:
          - display_name: news
            id: m4abc1
            subreddits: worldnews,news
        ipLogs:
          type: array
          items:
            type: object
          example:
          - date: 2026-01-15 14:30:00 UTC
            ip: 203.0.113.7
        deletedAt:
          format: date-time
          type: string
          example:
          nullable: true
        deletedBy:
          type: string
          nullable: true
          example:
        ocSharedStatus:
          type: string
          nullable: true
          enum:
          - shared
          - pending
          - not_gated
          description: 'Opposing-council only: whether this provider has been shared
            with the OL. `null` for OL/OG callers — same convention as every other
            item listing. Provider-level: every item under it shares this status,
            since approval here is all-or-nothing.'
      required:
      - createdAt
      - updatedAt
      - id
      - redditUserId
      - email
      - emailVerified
      - registrationDate
      - isDeleted
      - accountGender
      - birthdate
      - linkedIdentities
      - friends
      - subscribedSubreddits
      - gildedContent
      - multireddits
      - ipLogs
      - deletedAt
      - deletedBy
    AiChatProviderEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        platform:
          type: string
          enum:
          - claude
          - chatgpt
          - gemini
          example: chatgpt
        externalUserId:
          type: string
          example: user-a1B2c3D4e5F6g7H8i9J0k1L2
        email:
          type: string
          example: jane.doe@example.com
          nullable: true
        displayName:
          type: string
          example: Jane Doe
          nullable: true
        accountPlan:
          type: string
          example: plus
          nullable: true
        accountData:
          type: object
          example:
            id: user-a1B2c3D4e5F6g7H8i9J0k1L2
            email: jane.doe@example.com
            name: Jane Doe
            chatgpt_plus_user: true
          nullable: true
        metadata:
          type: object
          description: Not filled by any importer today, so always null.
          example:
          nullable: true
        deletedAt:
          format: date-time
          type: string
          example:
          nullable: true
        deletedBy:
          type: string
          nullable: true
          example:
        ocSharedStatus:
          type: string
          nullable: true
          enum:
          - shared
          - pending
          - not_gated
          description: 'Opposing-council only: whether this provider has been shared
            with the OL. `null` for OL/OG callers — same convention as every other
            item listing. Provider-level: every item under it shares this status,
            since approval here is all-or-nothing.'
      required:
      - createdAt
      - updatedAt
      - id
      - platform
      - externalUserId
      - email
      - displayName
      - accountPlan
      - accountData
      - metadata
      - deletedAt
      - deletedBy
    CaseDataStatusRowEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439011
        dataSource:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439011
        caseDataSource:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439011
        extractionCode:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439011
        status:
          type: string
          enum:
          - pending
          - in_progress
          - staged
          - completed
          - failed
        sourceType:
          type: string
          enum:
          - Device
          - UserEmailDataProvider
          - CaseFileProvider
          - FinancialAccountProvider
          - LegalDocumentProvider
          - DiscordUserProvider
          - RedditUserProvider
          - LinkedInUserProvider
          - AiChatProvider
        itemsCount:
          type: number
          example: 1250
        pendingOcApprovalCount:
          type: number
          description: 'Set only for an Originating-org Lawyer on an OC-enabled case:
            count of items uploaded but not yet OC-approved for this source. Omitted
            for OC callers and for OL callers on non-OC cases.'
          example: 0
        device:
          "$ref": "#/components/schemas/DeviceEntity"
        emailProvider:
          "$ref": "#/components/schemas/UserEmailDataProviderEntity"
        fileProvider:
          "$ref": "#/components/schemas/CaseFileProviderEntity"
        discordProvider:
          "$ref": "#/components/schemas/DiscordUserProviderEntity"
        redditProvider:
          "$ref": "#/components/schemas/RedditUserProviderEntity"
        aiChatProvider:
          "$ref": "#/components/schemas/AiChatProviderEntity"
      required:
      - createdAt
      - updatedAt
      - id
      - case
      - dataSource
      - caseDataSource
      - extractionCode
      - status
      - sourceType
      - itemsCount
    CaseDataStatusResponseSingleEntity:
      type: object
      properties:
        dataSource:
          "$ref": "#/components/schemas/MasterDataSourceEntity"
        percentages:
          example:
          - status: completed
            percentage: 66.67
          - status: in_progress
            percentage: 33.33
          type: array
          items:
            type: object
        data:
          type: array
          items:
            "$ref": "#/components/schemas/CaseDataStatusRowEntity"
      required:
      - dataSource
      - percentages
      - data
    CaseDataStatusResponseEntity:
      type: object
      properties:
        data:
          type: array
          items:
            "$ref": "#/components/schemas/CaseDataStatusResponseSingleEntity"
      required:
      - data
    CaseDataStatusCustodianEntity:
      type: object
      properties:
        name:
          type: string
          example: Jane Doe
        email:
          type: string
          example: jane.doe@example.com
      required:
      - name
      - email
    CaseDataStatusDataSourceResponseEntity:
      type: object
      properties:
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        type:
          type: string
          example: iMessage
          description: Name of the master data source
        category:
          type: string
          example:
          nullable: true
        name:
          type: string
          example: iPhone 15
        status:
          type: string
          example: completed
        itemsCount:
          type: number
          example: 1250
        pendingOcApprovalCount:
          type: number
          description: 'Set only for an Originating-org Lawyer on an OC-enabled case:
            count of items uploaded but not yet OC-approved for this source. Omitted
            for OC callers and for OL callers on non-OC cases.'
          example: 0
        ocSharedStatus:
          type: string
          enum:
          - shared
          - pending
          - not_gated
          description: 'Opposing-council only: how this source has been shared with
            the OL. `shared`/`pending` summarize a partially-approved source by whether
            anything is still outstanding — see pendingOcApprovalCount for the exact
            split. Omitted for OL/OG callers.'
        sizeBytes:
          type: number
          example: 52428800
        dateCompleted:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        sourceId:
          type: string
          example: 507f1f77bcf86cd799439012
      required:
      - id
      - type
      - category
      - name
      - status
    CaseDataStatusResponseSingleV2Entity:
      type: object
      properties:
        code:
          type: string
          example: K7Q2-9XPL
        createdDate:
          format: date-time
          type: string
          example: '2026-01-10T09:00:00.000Z'
        dueDate:
          format: date-time
          type: string
          example: '2026-02-01T00:00:00.000Z'
        dateRangeStart:
          format: date-time
          type: string
          example: '2025-01-01T00:00:00.000Z'
        dateRangeEnd:
          format: date-time
          type: string
          example: '2025-12-31T23:59:59.000Z'
        collectionMethod:
          type: string
          example: allow_client
        notes:
          type: string
          example:
          nullable: true
        custodian:
          "$ref": "#/components/schemas/CaseDataStatusCustodianEntity"
        dataSources:
          type: array
          items:
            "$ref": "#/components/schemas/CaseDataStatusDataSourceResponseEntity"
      required:
      - code
      - createdDate
      - collectionMethod
      - dataSources
    CaseDataStatusV2ResponseEntity:
      type: object
      properties:
        data:
          type: array
          items:
            "$ref": "#/components/schemas/CaseDataStatusResponseSingleV2Entity"
      required:
      - data
    CaseDataStatusListTreeNodesResponseEntity:
      type: object
      properties:
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        kind:
          type: string
          example: financial
        sourceId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        sourceName:
          type: string
          nullable: true
          example: jane.doe@example.com
        plaidItemId:
          type: string
          nullable: true
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        institutionName:
          type: string
          nullable: true
          example: Example Bank
        itemsCount:
          type: number
          example: 3
        pendingOcApprovalCount:
          type: number
          description: 'Set only for an Originating-org Lawyer on an OC-enabled case:
            count of items uploaded but not yet OC-approved for this source node.
            Omitted for OC callers and for OL callers on non-OC cases.'
          example: 0
        backupStatus:
          type: string
          enum:
          - processing
          - completed
          - failed
          - na
          nullable: true
        children:
          type: array
          items:
            type: object
          example:
          - name: Messages
            count: 120
            subCount: 3
      required:
      - id
      - kind
    CaseDataStatusListTreeResponseEntity:
      type: object
      properties:
        data:
          type: array
          items:
            "$ref": "#/components/schemas/CaseDataStatusListTreeNodesResponseEntity"
      required:
      - data
    V3TreeSummaryChildEntity:
      type: object
      properties:
        name:
          type: string
          example: Messages
        count:
          type: number
          example: 320
        subCount:
          type: number
          example: 12
      required:
      - name
      - count
      - subCount
    V3ConversationChildEntity:
      type: object
      properties:
        id:
          type: string
          example: 507f1f77bcf86cd799439014
        name:
          type: string
          example: Jane Doe
        source:
          type: string
          example: imessage
        messagesCount:
          type: number
          example: 320
        lastMessageDate:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T14:30:00.000Z'
        isPinned:
          type: boolean
          example: false
      required:
      - id
      - name
      - source
      - messagesCount
      - lastMessageDate
      - isPinned
    V3FinancialChildEntity:
      type: object
      properties:
        name:
          type: string
          example: Everyday Checking
        accountId:
          type: string
          example: acc_8Xq2
        financialNumber:
          type: string
          nullable: true
          example: "****1234"
        financialInsights:
          type: number
          example: 12
        accountType:
          type: string
          nullable: true
          example: depository
        plaidItemId:
          type: string
          example: item_4Lm9
        financialAccountId:
          type: string
          example: 507f1f77bcf86cd799439013
      required:
      - name
      - accountId
      - financialNumber
      - financialInsights
      - accountType
      - plaidItemId
      - financialAccountId
    V3TreeNodeEntity:
      type: object
      properties:
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        kind:
          type: string
          example: device
        sourceId:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439012
        deviceName:
          type: string
          nullable: true
          example: iPhone 15
        sourceName:
          type: string
          nullable: true
          example: iMessage
        plaidItemId:
          type: string
          nullable: true
          example:
        institutionName:
          type: string
          nullable: true
          example:
        itemsCount:
          type: number
          example: 1250
        pendingOcApprovalCount:
          type: number
          description: 'Set only for an Originating-org Lawyer on an OC-enabled case:
            count of items uploaded but not yet OC-approved for this source node.
            Omitted for OC callers and for OL callers on non-OC cases.'
          example: 0
        children:
          type: array
          items:
            "$ref": "#/components/schemas/V3TreeSummaryChildEntity"
        conversationChildren:
          type: array
          items:
            "$ref": "#/components/schemas/V3ConversationChildEntity"
        conversationTotal:
          type: number
          example: 42
        financialChildren:
          type: array
          items:
            "$ref": "#/components/schemas/V3FinancialChildEntity"
        platform:
          type: string
          nullable: true
          example: ios
      required:
      - id
      - kind
      - sourceId
      - deviceName
      - sourceName
      - itemsCount
      - children
    CaseDataStatusTreeV3ResponseEntity:
      type: object
      properties:
        data:
          type: array
          items:
            "$ref": "#/components/schemas/V3TreeNodeEntity"
      required:
      - data
    CreateExtractionCodeDto:
      type: object
      properties:
        custodianEmail:
          type: string
          maxLength: 100
          minLength: 3
        custodianName:
          type: string
          maxLength: 100
          minLength: 3
        custodianPhone:
          type: string
          maxLength: 50
          minLength: 3
        sendEmail:
          type: boolean
        label:
          type: string
          maxLength: 100
          minLength: 3
        allowShareAll:
          type: boolean
          deprecated: true
          description: Use config instead
        config:
          "$ref": "#/components/schemas/ExtractionCodeConfigDto"
        requestedSources:
          type: array
          items:
            type: string
        dueDate:
          type: string
        sendReminders:
          "$ref": "#/components/schemas/ExtractionCodeSendRemindersDto"
        metadata:
          type: object
        watchlist:
          type: boolean
          default: false
        ocRequired:
          type: boolean
          default: true
          description: 'OC cases only: when false, the OL creates/keeps this code
            without the opposing-council approval gate (code stays `na`, immediately
            usable, and the custodian email is sent). Ignored on non-OC cases. Defaults
            to true (gate on).'
        paymentRequired:
          type: boolean
          description: Whether payment is required for this extraction code
        paymentAmount:
          type: number
          description: Payment amount in cents
        paymentCurrency:
          type: string
          description: Payment currency (defaults to usd)
          default: usd
        paymentSuccessRedirectUrl:
          type: string
          description: Payment success redirect URL
        productName:
          type: string
          description: Product name
        prepaidItems:
          description: Prepaid basket. Each entry becomes its own Stripe line item
            and grants units on payment. Prices are negotiable — unitAmountCents is
            what the customer is charged, and is never validated against the catalog
            default.
          type: array
          items:
            "$ref": "#/components/schemas/PrepaidItemDto"
      required:
      - dueDate
    UpdateExtractionCodeDto:
      type: object
      properties:
        custodianEmail:
          type: string
          maxLength: 100
          minLength: 3
        custodianName:
          type: string
          maxLength: 100
          minLength: 3
        custodianPhone:
          type: string
          maxLength: 50
          minLength: 3
        sendEmail:
          type: boolean
        label:
          type: string
          maxLength: 100
          minLength: 3
        allowShareAll:
          type: boolean
          deprecated: true
          description: Use config instead
        config:
          "$ref": "#/components/schemas/ExtractionCodeConfigDto"
        requestedSources:
          type: array
          items:
            type: string
        dueDate:
          type: string
        sendReminders:
          "$ref": "#/components/schemas/ExtractionCodeSendRemindersDto"
        metadata:
          type: object
        watchlist:
          type: boolean
          default: false
        ocRequired:
          type: boolean
          default: true
          description: 'OC cases only: when false, the OL creates/keeps this code
            without the opposing-council approval gate (code stays `na`, immediately
            usable, and the custodian email is sent). Ignored on non-OC cases. Defaults
            to true (gate on).'
        paymentRequired:
          type: boolean
          description: Whether payment is required for this extraction code
        paymentAmount:
          type: number
          description: Payment amount in cents
        paymentCurrency:
          type: string
          description: Payment currency (defaults to usd)
          default: usd
        paymentSuccessRedirectUrl:
          type: string
          description: Payment success redirect URL
        productName:
          type: string
          description: Product name
        prepaidItems:
          description: Prepaid basket. Each entry becomes its own Stripe line item
            and grants units on payment. Prices are negotiable — unitAmountCents is
            what the customer is charged, and is never validated against the catalog
            default.
          type: array
          items:
            "$ref": "#/components/schemas/PrepaidItemDto"
        lockCode:
          type: boolean
          description: Indicates if the extraction code is locked
      required:
      - dueDate
    UpdateCustodianContactDto:
      type: object
      properties:
        custodianEmail:
          type: string
          maxLength: 100
          minLength: 3
        custodianName:
          type: string
          maxLength: 100
          minLength: 3
        custodianPhone:
          type: string
          maxLength: 50
          minLength: 3
        sendEmail:
          type: boolean
          default: true
          description: Whether to re-send the extraction-code email after updating
            contact (default true). Even when true, the email only goes out if the
            code is usable (na/approved) and has a custodian email.
    SendExtractionCodeEmailDto:
      type: object
      properties:
        custodianEmail:
          type: string
          maxLength: 100
          minLength: 3
        custodianName:
          type: string
          maxLength: 100
          minLength: 3
    DeviceSearchTrackingEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        extractionCode:
          type: string
          example: 507f1f77bcf86cd799439011
        organization:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: string
          example: 507f1f77bcf86cd799439011
        device:
          type: string
          example: 507f1f77bcf86cd799439011
          nullable: true
        linkedConversations:
          example:
          - 507f1f77bcf86cd799439011
          type: array
          items:
            type: string
        searchData:
          type: array
          items:
            type: object
          example:
          - action: search
            value: Jane Doe
          - action: select_all
            value: true
      required:
      - createdAt
      - updatedAt
      - id
      - extractionCode
      - organization
      - case
      - device
      - linkedConversations
      - searchData
    DeviceActivityLogSingleEntity:
      type: object
      properties:
        uploadAttemptId:
          type: string
          example:
          nullable: true
        selectedMessageUpload:
          type: object
          example:
          nullable: true
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        extractionCode:
          type: string
          example: K7Q2-9XPL
        userName:
          type: string
          example: Jane Doe
        userEmail:
          type: string
          example: jane.doe@example.com
        type:
          type: string
          example: backup-completed
        log:
          type: string
          example: Backup completed
      required:
      - date
      - extractionCode
      - userName
      - userEmail
      - type
      - log
    DownloadConversationAttachmentsDto:
      type: object
      properties:
        conversationIds:
          type: array
          items:
            type: array
      required:
      - conversationIds
    CaseInvitationListItemEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        invitedUserEmail:
          type: string
          example: jane.doe@example.com
        invitedUserName:
          type: string
          example: Jane Doe
        invitedBy:
          nullable: true
          description: Who sent the invite
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        invitedUser:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439011
        acceptedBy:
          type: string
          nullable: true
          example:
        organization:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: string
          example: 507f1f77bcf86cd799439011
        accessOrganization:
          type: string
          nullable: true
          example:
          description: The opposing counsel's organization, for OC invites
        invitedUserRole:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
          example: member
        invitedUserAccessLevel:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
        status:
          type: string
          enum:
          - pending
          - accepted
          - rejected
          example: pending
        emailSent:
          type: boolean
          example: true
        emailSentAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T14:30:00.000Z'
      required:
      - createdAt
      - updatedAt
      - id
      - invitedUserEmail
      - invitedUserName
      - invitedBy
      - invitedUser
      - acceptedBy
      - organization
      - case
      - accessOrganization
      - invitedUserRole
      - invitedUserAccessLevel
      - status
      - emailSent
      - emailSentAt
    CaseMemberEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        user:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        level:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
        accessType:
          type: string
          enum:
          - org
          - opposing_council
        invitedBy:
          type: string
          nullable: true
          description: Id of the user who added this member
          example: 507f1f77bcf86cd799439011
        accessGrantedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        status:
          type: string
          enum:
          - active
          - inactive
          - revoked
        accessOrganization:
          type: string
          nullable: true
          example:
      required:
      - createdAt
      - updatedAt
      - id
      - user
      - level
      - accessType
      - invitedBy
      - accessGrantedAt
      - status
    AddAllOrgUsersToCaseDto:
      type: object
      properties:
        accessLevel:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
          default: viewer
      required:
      - accessLevel
    InviteCaseUsersDto:
      type: object
      properties:
        users:
          type: array
          items:
            "$ref": "#/components/schemas/InviteCaseUser"
      required:
      - users
    UserCaseAccessEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        user:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        invitedBy:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        case:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/CaseEntity"
        organization:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        status:
          type: string
          example: active
        level:
          type: string
          example: admin
        accessGrantedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        accessRevokedAt:
          format: date-time
          type: string
          example:
          nullable: true
        lastAccessedAt:
          format: date-time
          type: string
          example: '2026-01-16T09:12:00.000Z'
          nullable: true
        isActive:
          type: boolean
          example: true
        isOnline:
          type: boolean
          example: false
      required:
      - createdAt
      - updatedAt
      - id
      - user
      - invitedBy
      - case
      - organization
      - status
      - level
      - accessGrantedAt
      - accessRevokedAt
      - lastAccessedAt
      - isActive
      - isOnline
    DeviceConversationEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        messageSharing:
          type: object
          example:
            sharedMessageCount: 320
            totalMessageCount: 1200
            totalReportedAt: '2026-01-15T14:30:00.000Z'
        selectionCoverage:
          type: object
          example:
            app: message
            conversationId: 42
            mode: selected_messages
            selectedMessageCount: 320
            eligibleMessageCount: 1200
            includedMessageCount: 320
            omittedEligibleMessageCount: 880
            selectedNotIncludedCount: 0
            totalMessageCount: 1200
            deviceConversation: 507f1f77bcf86cd799439011
            ingestionStatus: completed
            ingestedMessageCount: 320
            verifiedAt: '2026-01-15T14:30:00.000Z'
            completedAt: '2026-01-15T14:30:00.000Z'
            failedAt:
            isPartial: true
            coverage: selected_messages
            eligibleScope: after_date_and_incremental_filters
            uploadAttemptId: a3e6a3ab-2c6e-469c-a3ae-ab1e926c93c2
            deviceBackupId: 65a1b2c3d4e5f6a7b8c9d0e2
            reportOrder: 2026-01-15T14:30:00.000Z:65a1b2c3d4e5f6a7b8c9d0e2
        name:
          type: string
          example: Jane Doe
        displayName:
          type: string
          description: "'Conversation with <stored name>' unless the name was edited,
            in which case the edited name as typed."
          example: Conversation with Jane Doe
        chatName:
          type: string
          example: "+15555550100"
        originalDisplayName:
          type: string
          example: Jane Doe
        source:
          type: string
          enum:
          - message
          - whatsapp
          - voicemail
          - call_log
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
          example: message
        linkedContacts:
          type: array
          items:
            type: object
          nullable: true
          example: []
          description: Not loaded by any route, so always null or an empty list.
        device:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceEntity"
        avatar:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileEntity"
        service:
          type: string
          example: iMessage
        lastMessageText:
          type: string
          example: See you at 6
        firstMessageDate:
          format: date-time
          type: string
          example: '2025-03-01T09:00:00.000Z'
          nullable: true
        lastMessageDate:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        displayNameEditedAt:
          format: date-time
          type: string
          nullable: true
          example:
        lastMessageExternalId:
          type: string
          example: '58231'
        isGroup:
          type: boolean
          default: false
          example: false
        owner:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        isReadonly:
          type: boolean
          example: false
        lastExportedAt:
          format: date-time
          type: string
          nullable: true
          example:
        messagesCount:
          type: number
          example: 320
        archivedBy:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        archivedAt:
          format: date-time
          type: string
          nullable: true
          example:
        starredAt:
          format: date-time
          type: string
          nullable: true
          example:
        tags:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/TagEntity"
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
        isPlaceholder:
          type: boolean
          example: false
        isArchived:
          type: boolean
          example: false
        isStarred:
          type: boolean
          example: false
        hasAllAttachmentProcessed:
          type: boolean
          example: true
        processedAttachmentCount:
          type: number
          example: 12
        conversationName:
          type: string
          example: Conversation with Jane Doe
        isNameEdited:
          type: boolean
          example: false
        participantCount:
          type: number
          example: 2
      required:
      - createdAt
      - updatedAt
      - id
      - messageSharing
      - name
      - displayName
      - chatName
      - originalDisplayName
      - source
      - linkedContacts
      - device
      - avatar
      - service
      - lastMessageText
      - firstMessageDate
      - lastMessageDate
      - displayNameEditedAt
      - lastMessageExternalId
      - isGroup
      - owner
      - isReadonly
      - lastExportedAt
      - messagesCount
      - archivedBy
      - archivedAt
      - starredAt
      - tags
      - isPlaceholder
      - isArchived
      - isStarred
      - hasAllAttachmentProcessed
      - processedAttachmentCount
      - conversationName
      - isNameEdited
      - participantCount
    CaseDetailsEntity:
      type: object
      properties:
        case:
          "$ref": "#/components/schemas/CaseEntity"
        access:
          "$ref": "#/components/schemas/UserCaseAccessEntity"
        usersWithAccess:
          type: array
          items:
            "$ref": "#/components/schemas/UserWithCaseAccessEntity"
        conversations:
          nullable: true
          type: array
          items:
            "$ref": "#/components/schemas/DeviceConversationEntity"
        noOfBackups:
          type: number
          example: 2
        pendingUsersInitials:
          example:
          - JD
          type: array
          items:
            type: string
        usersWithPendingAccess:
          nullable: true
          type: array
          items:
            "$ref": "#/components/schemas/CaseInvitationEntity"
        noOfMembers:
          type: number
          example: 4
        noOfPendingUsers:
          type: number
          example: 1
        exportCount:
          type: number
          example: 3
        conversationsCount:
          type: number
          example: 128
        extractionCodes:
          nullable: true
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeEntity"
        totalExtractionCodes:
          type: number
          example: 5
        dataUsedInBytes:
          type: number
          example: 52428800
        totalCaseFileSize:
          type: number
          example: 1048576
        hasCaseFiles:
          type: boolean
          example: true
        dataSources:
          type: array
          items:
            "$ref": "#/components/schemas/CaseDataSourceEntity"
        dataSourcesStatusCount:
          type: object
          example:
            completed: 4
            in_progress: 1
      required:
      - case
      - access
      - usersWithAccess
      - conversations
      - noOfBackups
      - pendingUsersInitials
      - usersWithPendingAccess
      - noOfMembers
      - noOfPendingUsers
      - exportCount
      - conversationsCount
      - extractionCodes
      - totalExtractionCodes
      - dataUsedInBytes
      - totalCaseFileSize
      - hasCaseFiles
      - dataSources
      - dataSourcesStatusCount
    CaseInvitationAcceptResponseEntity:
      type: object
      properties:
        login:
          "$ref": "#/components/schemas/LoginResponseEntity"
        case:
          "$ref": "#/components/schemas/CaseDetailsEntity"
      required:
      - case
    AcceptCaseInvitationDto:
      type: object
      properties:
        token:
          type: string
        email:
          type: string
        name:
          type: string
        password:
          type: string
      required:
      - token
      - email
    RejectCaseInvitationRequestDto:
      type: object
      properties:
        token:
          type: string
        email:
          type: string
        reason:
          type: string
      required:
      - token
      - email
    UpdateCaseUserAccess:
      type: object
      properties:
        id:
          type: string
        accessLevel:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
          default: viewer
      required:
      - id
      - accessLevel
    UpdateCaseUserAccessDto:
      type: object
      properties:
        users:
          type: array
          items:
            "$ref": "#/components/schemas/UpdateCaseUserAccess"
      required:
      - users
    CreateCaseTagDto:
      type: object
      properties:
        name:
          type: string
        backgroundColor:
          type: string
        textColor:
          type: string
        severity:
          type: string
          enum:
          - info
          - warning
          - danger
          default: info
      required:
      - name
    UpdateCaseTagDto:
      type: object
      properties:
        name:
          type: string
        backgroundColor:
          type: string
        textColor:
          type: string
        severity:
          type: string
          enum:
          - info
          - warning
          - danger
          default: info
    CaseCollectionEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: Key messages
        slug:
          type: string
          example: key-messages
        case:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439011
        user:
          type: string
          description: Id of the user who created the collection
          example: 507f1f77bcf86cd799439011
        lastUpdatedBy:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439011
        archivedAt:
          format: date-time
          type: string
          nullable: true
          example:
        archivedBy:
          type: string
          nullable: true
          example:
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - slug
      - case
      - user
      - lastUpdatedBy
      - archivedAt
      - archivedBy
    BulkCollectionCreateResponseEntity:
      type: object
      properties:
        createdCollectionsCount:
          type: number
          example: 3
      required:
      - createdCollectionsCount
    CreateBulkCollectionSearchTermDto:
      type: object
      properties:
        searchTerm:
          type: string
        searchFullWord:
          type: boolean
      required:
      - searchTerm
    CreateBulkCollectionsDto:
      type: object
      properties:
        searchTerms:
          type: array
          items:
            "$ref": "#/components/schemas/CreateBulkCollectionSearchTermDto"
        paddingMessagesCount:
          type: number
          description: Number of messages to add to the beginning and end of the collection
          default: 0
      required:
      - searchTerms
    TagResponseEntity:
      type: object
      properties:
        name:
          type: string
          example: Important
        nameLowercase:
          type: string
          example: important
      required:
      - name
      - nameLowercase
    UserFileResponseEntity:
      type: object
      properties:
        name:
          type: string
          example: IMG_0001.HEIC
        convertedName:
          type: string
          example: IMG_0001_display.png
          nullable: true
        transferState:
          type: number
          enum:
          - 0
          - 1
          - 5
          - 6
          - 7
          nullable: true
          example: 5
        totalBytes:
          type: number
          nullable: true
          example: 2483120
      required:
      - name
      - convertedName
      - transferState
      - totalBytes
    DeviceContactResponseEntity:
      type: object
      properties:
        fullName:
          type: string
          example: Jane Doe
        firstName:
          type: string
          example: Jane
        phoneNational:
          example:
          - "(555) 555-0100"
          type: array
          items:
            type: string
        avatar:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileResponseEntity"
        initials:
          type: string
          example: JD
        isEdited:
          type: boolean
          description: True when the contact name was edited after import. When it
            was not edited the value is usually false, but because of how it is computed
            it can also be null, an empty string, or missing.
          nullable: true
          example: false
      required:
      - fullName
      - firstName
      - phoneNational
      - avatar
      - initials
      - isEdited
    DeviceMessageResponseEntity:
      type: object
      properties:
        text:
          type: string
          example: See you at 6
          nullable: true
        tags:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/TagResponseEntity"
        deviceType:
          type: string
          example: ios
          nullable: true
        sender:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceContactResponseEntity"
        receiver:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceContactResponseEntity"
        isEmpty:
          type: boolean
          example: false
        iMessageType:
          type: string
          enum:
          - na
          - text
          - text-with-attachment
          - group_title_action
          - group_picture_action
          - location_share_start_stop
          - group_member_added
          - group_member_removed
          - face_time
          - message-layout
          - location
          - link
          - file_attached
          - unsent
        isFromMe:
          type: boolean
          example: true
        isDelivered:
          type: boolean
          example: true
        isDeleted:
          type: boolean
          example: false
        isMissingMedia:
          type: boolean
          example: false
        isSystemMessage:
          type: boolean
          example: false
        attachment:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileResponseEntity"
        additionalAttachments:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/UserFileResponseEntity"
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        sentAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        readAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        deletedAt:
          type: string
          format: date-time
          nullable: true
          example:
        recoveredAt:
          format: date-time
          type: string
          nullable: true
          example:
        parentMessageText:
          type: string
          example: Are we still on for dinner?
          description: Text of the message this one replies to; null when it is not
            a reply.
          nullable: true
        parentMessageAttachment:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileResponseEntity"
        parentMessageIndex:
          type: number
          description: Index of the message this one replies to; null when it is not
            a reply.
          nullable: true
          example: 41
        service:
          type: string
          example: iMessage
        mimeType:
          type: string
          example: image/jpeg
        editHistory:
          type: array
          items:
            type: object
          nullable: true
          example:
          - date: '2026-01-15T14:30:00.000Z'
            text: See you at 7
        deletedMessagesLogs:
          type: object
          example:
            total: 2
            minDate: '2026-01-15T14:05:00.000Z'
            maxDate: '2026-01-15T14:20:00.000Z'
            deletedMessages: []
          nullable: true
        commentsCount:
          type: number
          example: 2
        lastCommentByUserInitials:
          type: string
          example: JD
          nullable: true
        lastCommentByUserAvatarUrl:
          type: string
          example: https://files.example.com/avatars/jane-doe.png
          nullable: true
        messageIndex:
          type: number
          example: 42
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
        ocSharedStatus:
          type: string
          enum:
          - shared
          - pending
          - not_gated
          nullable: true
          description: 'Opposing-council only: how this conversation entry has been
            shared with the OL. Omitted (null) for non-OC callers.'
        collectionCount:
          type: number
          example: 1
        durationInSeconds:
          type: number
          description: Voicemail length in seconds; null unless the message is a voicemail.
          nullable: true
          example: 32
        callDurationInSeconds:
          type: number
          description: Call length in seconds; null unless the message is a call.
          nullable: true
          example: 125
        callType:
          type: string
          description: 'Call direction: ''incoming'', ''outgoing'', ''rejected'' or
            ''not_connected''; an empty string unless the message is a call.'
          example: incoming
        callSource:
          type: string
          nullable: true
          example: phone
      required:
      - text
      - tags
      - deviceType
      - sender
      - receiver
      - isEmpty
      - iMessageType
      - isFromMe
      - isDelivered
      - isDeleted
      - isMissingMedia
      - isSystemMessage
      - attachment
      - additionalAttachments
      - date
      - sentAt
      - readAt
      - deletedAt
      - recoveredAt
      - parentMessageText
      - parentMessageAttachment
      - parentMessageIndex
      - service
      - mimeType
      - editHistory
      - deletedMessagesLogs
      - commentsCount
      - lastCommentByUserInitials
      - lastCommentByUserAvatarUrl
      - messageIndex
      - collectionCount
      - durationInSeconds
      - callDurationInSeconds
      - callType
      - callSource
    CaseCollectionResponseEntryEntity:
      type: object
      properties:
        deviceMessage:
          "$ref": "#/components/schemas/DeviceMessageResponseEntity"
        text:
          type: string
          example: Key admission
        customType:
          type: string
          example:
          nullable: true
        customValue:
          type: string
          example:
          nullable: true
        position:
          type: number
          description: Index of the entry in the collection, starts from 0
          example: 0
        type:
          type: string
          enum:
          - message
          - text
          - line_break
          - custom
          default: message
      required:
      - deviceMessage
      - text
      - customType
      - customValue
      - position
      - type
    CreateCollectionEntryDto:
      type: object
      properties:
        type:
          type: string
          enum:
          - message
          - text
          - line_break
          - custom
          default: message
        deviceMessage:
          type: string
          description: Required, if type is set to message
        text:
          type: string
          description: Required if type is set to text
        customType:
          type: string
          description: Required if type is set to custom
        customValue:
          type: string
          description: Required if type is set to custom
      required:
      - type
      - deviceMessage
      - text
      - customType
      - customValue
    CreateCaseCollectionDto:
      type: object
      properties:
        name:
          type: string
        entries:
          type: array
          items:
            "$ref": "#/components/schemas/CreateCollectionEntryDto"
      required:
      - name
    UpdateCaseCollectionDto:
      type: object
      properties:
        name:
          type: string
      required:
      - name
    AddEntriesToCaseCollectionDto:
      type: object
      properties:
        entries:
          type: array
          items:
            "$ref": "#/components/schemas/CreateCollectionEntryDto"
      required:
      - entries
    AddAllMessagesToCollectionDto:
      type: object
      properties:
        sortOrder:
          type: string
          enum:
          - asc
          - desc
          default: asc
        conversations:
          description: Array of conversation ids
          maxItems: 100
          minItems: 1
          type: array
          items:
            type: string
        sortBy:
          type: string
          enum:
          - sentAt
          - date
          default: date
      required:
      - conversations
    RemoveEntriesFromCaseCollectionDto:
      type: object
      properties:
        entries:
          minItems: 1
          type: array
          items:
            type: array
      required:
      - entries
    CaseCollectionEntryEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        deviceMessage:
          type: string
          nullable: true
          description: Id of the device message this entry points to
          example: 507f1f77bcf86cd799439011
        text:
          type: string
          example: Key admission
          nullable: true
        customType:
          type: string
          example:
          nullable: true
        customValue:
          type: string
          example:
          nullable: true
        position:
          type: number
          description: Index of the entry in the collection, starts from 0
          example: 0
        type:
          type: string
          enum:
          - message
          - text
          - line_break
          - custom
          default: message
        createdBy:
          type: string
          example: 507f1f77bcf86cd799439011
        lastUpdatedBy:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439011
        caseCollection:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: string
          example: 507f1f77bcf86cd799439011
      required:
      - createdAt
      - updatedAt
      - id
      - deviceMessage
      - text
      - customType
      - customValue
      - position
      - type
      - createdBy
      - lastUpdatedBy
      - caseCollection
      - case
    InsertEntryAtPositionDto:
      type: object
      properties:
        entry:
          "$ref": "#/components/schemas/CreateCollectionEntryDto"
        position:
          type: number
          minimum: 0
      required:
      - entry
      - position
    DeviceContactEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        fullName:
          type: string
          example: Jane Doe
        firstName:
          type: string
          example: Jane
        lastName:
          type: string
          example: Doe
        originalFullName:
          type: string
          example: Jane Doe
        email:
          type: string
          example: jane.doe@example.com
        address:
          type: string
          example: 1 Main St, New York, NY
        phone:
          example:
          - "+15555550100"
          type: array
          items:
            type: string
        phoneNational:
          example:
          - "(555) 555-0100"
          type: array
          items:
            type: string
        source:
          type: string
          enum:
          - default
          - message
          - device_contact
          - whatsapp
          - voicemail
          - call_log
          - social
          - facebook
          - instagram
          - threads
        linkedContacts:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/DeviceContactEntity"
        avatar:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileEntity"
        device:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceEntity"
        owner:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        initials:
          type: string
          example: JD
        conversations:
          example:
          - 507f1f77bcf86cd799439014
          type: array
          items:
            type: string
        isEdited:
          type: boolean
          description: True when the name differs from the one on the device
          example: false
      required:
      - createdAt
      - updatedAt
      - id
      - fullName
      - firstName
      - lastName
      - originalFullName
      - email
      - address
      - phone
      - phoneNational
      - source
      - linkedContacts
      - avatar
      - device
      - owner
      - initials
      - isEdited
    UpdateCaseContactDto:
      type: object
      properties:
        firstName:
          type: string
          maxLength: 100
          minLength: 3
        middleName:
          type: string
        lastName:
          type: string
        email:
          type: string
        address:
          type: string
        city:
          type: string
        country:
          type: string
        note:
          type: string
        phone:
          type: array
          items:
            type: string
    ExportCaseContactsDto:
      type: object
      properties:
        searchText:
          type: string
          maxLength: 100
          minLength: 3
        conversation:
          type: string
        device:
          type: string
    ArchiveConversationsDto:
      type: object
      properties:
        conversationIds:
          description: Ids of the conversations to archive
          example:
          - 60a0fe4f5311236168a109ca
          type: array
          items:
            type: string
        deviceId:
          type: string
          description: The ID of the device associated with the conversation
          example: 60a0fe4f5311236168a109cb
        deleteImmediately:
          type: boolean
          description: Schedule to delete immediately
          example: false
          default: false
      required:
      - conversationIds
      - deviceId
      - deleteImmediately
    UnarchiveConversationsDto:
      type: object
      properties:
        conversationIds:
          description: Ids of the conversations to unarchive
          example:
          - 60a0fe4f5311236168a109ca
          type: array
          items:
            type: string
        deviceId:
          type: string
          description: The ID of the device associated with the conversation
          example: 60a0fe4f5311236168a109cb
      required:
      - conversationIds
      - deviceId
    EditConversationDto:
      type: object
      properties:
        displayName:
          type: string
        isStarred:
          type: boolean
        contacts:
          type: array
          items:
            type: string
    UpdateCaseFileTagsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Id of the case the file belongs to. Required so @CaseAccess
            can authorize the caller (an OC included) on the case.
        tags:
          minItems: 0
          maxItems: 100
          description: Array of tag ids or tag names. Replaces the existing tags with
            this list. Pass an empty array to remove all tags.
          type: array
          items:
            type: string
      required:
      - caseId
      - tags
    AddConversationDto:
      type: object
      properties:
        name:
          type: string
        chatName:
          type: string
        source:
          type: string
        deviceId:
          type: string
        deviceType:
          type: string
        caseId:
          type: string
        service:
          type: string
        isGroup:
          type: boolean
        externalId:
          type: string
        externalData:
          type: object
        contacts:
          type: array
          items:
            type: string
      required:
      - name
      - chatName
      - source
      - deviceId
      - deviceType
      - caseId
      - service
      - isGroup
      - externalId
    BrowserHistoryVisitsEntity:
      type: object
      properties:
        title:
          type: string
          example: Example News
        date:
          format: date-time
          type: string
          example: '2026-01-14T19:40:00.000Z'
        externalId:
          type: string
          example: '91234'
      required:
      - title
      - date
      - externalId
    BrowserHistoryEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        browser:
          type: string
          example: Safari
        url:
          type: string
          example: https://example.com/news
          nullable: true
        firstDate:
          format: date-time
          type: string
          example: '2025-11-02T08:15:00.000Z'
          nullable: true
        lastDate:
          format: date-time
          type: string
          example: '2026-01-14T19:40:00.000Z'
          nullable: true
        visitCount:
          type: number
          example: 17
        externalId:
          type: string
          example: hist-48213
        visits:
          type: array
          items:
            "$ref": "#/components/schemas/BrowserHistoryVisitsEntity"
      required:
      - createdAt
      - updatedAt
      - id
      - browser
      - url
      - firstDate
      - lastDate
      - visitCount
      - externalId
      - visits
    CaseFileEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: contract.pdf
        type:
          type: string
          example: document
        size:
          type: number
          description: Size in bytes
          example: 204800
        case:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439011
        file:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileEntity"
        extractionCode:
          type: string
          nullable: true
          description: Id of the extraction code the file was uploaded through
          example: 507f1f77bcf86cd799439011
        category:
          type: string
          nullable: true
          example: contracts
        tags:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/TagEntity"
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
        ocSharedStatus:
          type: string
          enum:
          - shared
          - pending
          - not_gated
          nullable: true
          description: 'Opposing-council only: how this file has been shared with
            the OL. Omitted (null) for non-OC callers, and for legal-integration documents
            (a separate OC gate not yet wired into this field).'
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - type
      - size
      - case
      - file
      - extractionCode
      - category
      - tags
    SingleAddCaseFilesDto:
      type: object
      properties:
        file:
          type: string
        type:
          type: string
        category:
          type: string
      required:
      - file
    AddCaseFilesViaCaseDto:
      type: object
      properties:
        files:
          type: array
          items:
            "$ref": "#/components/schemas/SingleAddCaseFilesDto"
      required:
      - files
    RequestCaseFilesZipDownloadDto:
      type: object
      properties:
        searchText:
          type: string
        extractionCode:
          type: string
        tags:
          type: array
          items:
            type: string
        category:
          type: string
        fileIds:
          description: CaseFile ObjectIds to include in the zip. If non-empty, takes
            precedence over filter fields. Maximum 50000 ids.
          type: array
          items:
            type: string
    UpdateCaseFileCategoryDto:
      type: object
      properties:
        category:
          type: string
          nullable: true
    DeviceMediaEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        externalId:
          type: number
          example: 48213
        fileName:
          type: string
          example: IMG_0001.HEIC
        fileCreatedAt:
          format: date-time
          type: string
          example: '2025-06-01T10:00:00.000Z'
          nullable: true
        type:
          type: number
          example: 1
          nullable: true
        duration:
          type: number
          example: 0
          nullable: true
        uniformType:
          type: string
          example: public.heic
          nullable: true
        trashedState:
          type: number
          example: 0
          nullable: true
        album:
          type: object
          example:
            id: 1
            name: Camera Roll
          nullable: true
        originalFile:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileEntity"
        thumbnail:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileEntity"
        metadata:
          type: object
          example: {}
      required:
      - createdAt
      - updatedAt
      - id
      - externalId
      - fileName
      - originalFile
      - thumbnail
    DeviceNotesEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        app:
          type: string
          example: Notes
        title:
          type: string
          example: To do
          nullable: true
        summary:
          type: string
          example: Call Sam about the invoice
          nullable: true
        rawData:
          type: string
          example: Call Sam about the invoice
          nullable: true
        htmlData:
          type: string
          example: "<p>Call Sam about the invoice</p>"
          nullable: true
        creationDate:
          format: date-time
          type: string
          example: '2025-05-20T08:00:00.000Z'
          nullable: true
        modificationDate:
          format: date-time
          type: string
          example: '2025-05-21T09:30:00.000Z'
          nullable: true
        isDeleted:
          type: boolean
          example: false
        externalId:
          type: string
          example: note-7731
        attachments:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/UserFileEntity"
      required:
      - createdAt
      - updatedAt
      - id
      - app
      - title
      - summary
      - rawData
      - htmlData
      - creationDate
      - modificationDate
      - isDeleted
      - externalId
      - attachments
    ExtractionCodeSuggestionMessageEntity:
      type: object
      properties:
        text:
          type: string
          example: Please include WhatsApp.
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:35:00.000Z'
        user:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439013
        statusUpdate:
          type: string
          enum:
          - open
          - approved
          - rejected
          - closed
          nullable: true
      required:
      - text
      - date
      - user
    ExtractionCodeSuggestionEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        status:
          type: string
          enum:
          - open
          - approved
          - rejected
          - closed
        suggestion:
          type: object
          example:
            type: share_all
        previousConfig:
          type: object
          nullable: true
          example:
            type: allow_client
        extractionCode:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439012
        case:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439011
        suggestedBy:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439013
        suggestedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        approvedBy:
          type: string
          nullable: true
          example:
        approvedAt:
          format: date-time
          type: string
          nullable: true
          example:
        rejectedBy:
          type: string
          nullable: true
          example:
        rejectedAt:
          format: date-time
          type: string
          nullable: true
          example:
        mergedAt:
          format: date-time
          type: string
          nullable: true
          example:
        messages:
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeSuggestionMessageEntity"
      required:
      - createdAt
      - updatedAt
      - id
      - status
      - suggestion
      - previousConfig
      - extractionCode
      - case
      - suggestedBy
      - suggestedAt
      - approvedBy
      - approvedAt
      - rejectedBy
      - rejectedAt
      - mergedAt
      - messages
    CreateExtractionCodeSuggestionDto:
      type: object
      properties:
        suggestion:
          "$ref": "#/components/schemas/ExtractionCodeConfigDto"
        message:
          type: string
          maxLength: 2000
          description: Optional message to attach alongside the suggestion
      required:
      - suggestion
    UpdateExtractionCodeSuggestionDto:
      type: object
      properties:
        suggestion:
          "$ref": "#/components/schemas/ExtractionCodeConfigDto"
      required:
      - suggestion
    CreateExtractionCodeSuggestionMessageDto:
      type: object
      properties:
        text:
          type: string
          maxLength: 2000
      required:
      - text
    RejectExtractionCodeSuggestionDto:
      type: object
      properties:
        message:
          type: string
          maxLength: 2000
    ReviewExtractionCodeDto:
      type: object
      properties:
        message:
          type: string
          description: Optional view-only note saved on the code alongside the review
            decision
    OcDataApprovalEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        dataProviderType:
          type: string
          enum:
          - Device
          - UserEmailDataProvider
          - CaseFileProvider
          - FinancialAccountProvider
          - LegalDocumentProvider
          - DiscordUserProvider
          - RedditUserProvider
          - LinkedInUserProvider
          - AiChatProvider
        dataProvider:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439014
        mode:
          type: string
          enum:
          - approve_all
          - approve_subset
        individualDataIds:
          example: []
          type: array
          items:
            type: string
        case:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439011
        originatingOrg:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439016
        opposingCouncilOrg:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439015
        approvedBy:
          type: string
          nullable: true
          example: 507f1f77bcf86cd799439013
        approvedAt:
          format: date-time
          type: string
          example: '2026-01-16T10:00:00.000Z'
      required:
      - createdAt
      - updatedAt
      - id
      - dataProviderType
      - dataProvider
      - mode
      - individualDataIds
      - case
      - originatingOrg
      - opposingCouncilOrg
      - approvedBy
      - approvedAt
    OcDataApprovalRequestDto:
      type: object
      properties:
        dataProviderType:
          type: string
          enum:
          - Device
          - UserEmailDataProvider
          - CaseFileProvider
          - FinancialAccountProvider
          - LegalDocumentProvider
          - DiscordUserProvider
          - RedditUserProvider
          - LinkedInUserProvider
          - AiChatProvider
          description: Type of data provider the approval applies to
        dataProviderId:
          type: string
          description: Id of the specific data provider document
        individualIds:
          description: Ids of individual data items. Empty/omitted means "all" for
            approve, or "deny all" for reject.
          type: array
          items:
            type: string
      required:
      - dataProviderType
      - dataProviderId
    InviteOcColleagueDto:
      type: object
      properties:
        email:
          type: string
          description: Email of the OCG colleague to add
        name:
          type: string
      required:
      - email
    IngestEventResultEntity:
      type: object
      properties:
        eventId:
          type: string
          nullable: true
          example: 7f3c2a10-5b4e-4d2a-9c1e-2f6b8a9d0e11
        status:
          type: string
          enum:
          - accepted
          - duplicate
          - rejected
          - retry
          example: accepted
        reason:
          type: string
      required:
      - eventId
      - status
    IngestCollectionEventsResponseEntity:
      type: object
      properties:
        results:
          type: array
          items:
            "$ref": "#/components/schemas/IngestEventResultEntity"
      required:
      - results
    IngestCollectionEventsDto:
      type: object
      properties:
        events:
          type: array
          items:
            type: object
      required:
      - events
    SnoozeInboxItemDto:
      type: object
      properties:
        until:
          type: string
          format: date-time
      required:
      - until
    SetWatchedDto:
      type: object
      properties:
        watched:
          type: boolean
      required:
      - watched
    SetOrgPriorityDto:
      type: object
      properties:
        isPriority:
          type: boolean
      required:
      - isPriority
    CertificateEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        certificateId:
          type: string
          example: HC-2026-05-15-A1B2-1F3
        organization:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e3
        case:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        sourceIds:
          example:
          - 65a1b2c3d4e5f6a7b8c9d0f4
          type: array
          items:
            type: string
        status:
          type: string
          enum:
          - verified
          - partial
          - revoked
          example: verified
        generatedBySide:
          type: string
          enum:
          - org
          - opposing_council
          example: org
        certificate:
          type: object
          example:
            generatedAt: '2026-05-15T12:00:00.000Z'
            dataSummary: {}
        revokedAt:
          format: date-time
          type: string
          nullable: true
          example:
        revocationReason:
          type: string
          nullable: true
          example:
        createdBy:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e4
      required:
      - createdAt
      - updatedAt
      - id
      - certificateId
      - organization
      - case
      - sourceIds
      - status
      - generatedBySide
      - certificate
      - revokedAt
      - revocationReason
      - createdBy
    CreateCertificateDto:
      type: object
      properties:
        certificateId:
          type: string
        certificate:
          type: object
        pdfBase64:
          type: string
        jsonString:
          type: string
        caseId:
          type: string
        sourceIds:
          type: array
          items:
            type: string
      required:
      - certificateId
      - certificate
      - pdfBase64
      - jsonString
      - caseId
    DocumentVerificationEntity:
      type: object
      properties:
        status:
          type: string
          example: authentic
        certificateId:
          type: string
          example: HC-2026-05-15-A1B2-1F3
        certificateStatus:
          type: string
          enum:
          - verified
          - partial
          - revoked
          example: verified
        signaturePresent:
          type: boolean
          example: true
        hashMatch:
          type: boolean
          example: true
        documentMatch:
          type: boolean
          example: true
        issuedAt:
          type: string
          nullable: true
          example: '2026-05-15T09:30:00.000Z'
        message:
          type: string
          example: This document is an authentic, unmodified certificate issued by
            Hearsay.
      required:
      - status
      - certificateId
      - certificateStatus
      - signaturePresent
      - hashMatch
      - documentMatch
      - issuedAt
      - message
    CertificateVerificationEntity:
      type: object
      properties:
        selectedMessageUpload:
          type: object
          example:
            version: 1
            conversations:
            - messageSharing:
                sharedMessageCount: 25
                totalMessageCount: 480
                totalReportedAt: '2026-05-14T16:05:00.000Z'
              selectionCoverage:
                app: message
                conversationId: 42
                mode: selected_messages
                selectedMessageCount: 25
                eligibleMessageCount: 480
                includedMessageCount: 25
                omittedEligibleMessageCount: 455
                selectedNotIncludedCount: 0
                totalMessageCount: 480
                deviceConversation: 507f1f77bcf86cd799439011
                ingestionStatus: completed
                ingestedMessageCount: 25
                verifiedAt: '2026-05-14T16:10:00.000Z'
                completedAt: '2026-05-14T16:10:00.000Z'
                failedAt:
                isPartial: true
                coverage: selected_messages
                eligibleScope: after_date_and_incremental_filters
                uploadAttemptId: 3f2b8c1e-9a4d-4e7b-8c2f-1d5e6a7b8c9d
                deviceBackupId: 65a1b2c3d4e5f6a7b8c9d0e2
                reportOrder: 2026-05-14T16:00:00.000Z:65a1b2c3d4e5f6a7b8c9d0e2
        certificateId:
          type: string
          example: HC-2026-05-15-A1B2-1F3
        status:
          type: string
          enum:
          - verified
          - partial
          - revoked
          example: verified
        generatedBySide:
          type: string
          enum:
          - org
          - opposing_council
          example: org
        collectionDate:
          type: string
          nullable: true
          example: '2026-05-14T16:02:00.000Z'
        custodianName:
          type: string
          nullable: true
          example: Jane Doe
        collectionId:
          type: string
          nullable: true
          example: A1B2C3
        dataHash:
          type: string
          nullable: true
          example: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
        itemsCollected:
          type: number
          nullable: true
          example: 1520
        issuedAt:
          type: string
          nullable: true
          example: '2026-05-15T09:30:00.000Z'
        requestingAttorney:
          type: string
          nullable: true
          example: John Smith
        deviceName:
          type: string
          nullable: true
          example: iPhone
        deviceModel:
          type: string
          nullable: true
          example: iPhone 15
        conversationCount:
          type: number
          nullable: true
          example: 12
        totalMessages:
          type: number
          nullable: true
          example: 1480
        dataDateRange:
          type: object
          nullable: true
          example:
            start: '2025-01-01'
            end: '2026-05-14'
        extractionCode:
          type: string
          nullable: true
          example: A1B2C3
      required:
      - certificateId
      - status
      - generatedBySide
      - collectionDate
      - custodianName
      - collectionId
      - dataHash
      - itemsCollected
      - issuedAt
      - requestingAttorney
      - deviceName
      - deviceModel
      - conversationCount
      - totalMessages
      - dataDateRange
      - extractionCode
    CertificateListEntity:
      type: object
      properties:
        total:
          type: number
          example: 42
        page:
          type: number
          example: 1
        perPage:
          type: number
          example: 20
        skip:
          type: number
          example: 0
        limit:
          type: number
          example: 20
        totalPages:
          type: number
          example: 3
        nextPage:
          type: number
          example: 2
          nullable: true
        previousPage:
          type: number
          example:
          nullable: true
        data:
          type: array
          items:
            "$ref": "#/components/schemas/CertificateEntity"
      required:
      - total
      - totalPages
      - nextPage
      - previousPage
      - data
    RevokeCertificateDto:
      type: object
      properties:
        revocationReason:
          type: string
      required:
      - revocationReason
    MessageSelectionConversationDto:
      type: object
      properties:
        app:
          type: string
          enum:
          - message
          - whatsapp
          - voicemail
          - call_log
          - facebook_dump
          - instagram_dump
          - threads_dump
        conversationId:
          type: number
        mode:
          type: string
        selectedMessageCount:
          type: number
        eligibleMessageCount:
          type: number
          nullable: true
        includedMessageCount:
          type: number
        omittedEligibleMessageCount:
          type: number
          nullable: true
        selectedNotIncludedCount:
          type: number
        totalMessageCount:
          type: number
          nullable: true
      required:
      - app
      - conversationId
      - mode
      - selectedMessageCount
      - eligibleMessageCount
      - includedMessageCount
      - omittedEligibleMessageCount
      - selectedNotIncludedCount
    PreparedMessageSelectionReportDto:
      type: object
      properties:
        version:
          type: number
        uploadAttemptId:
          type: string
        phase:
          type: object
        platformImportAccepted:
          type: boolean
        coverage:
          type: string
        eligibleScope:
          type: string
        conversations:
          type: array
          items:
            "$ref": "#/components/schemas/MessageSelectionConversationDto"
      required:
      - version
      - uploadAttemptId
      - phase
      - platformImportAccepted
      - coverage
      - eligibleScope
      - conversations
    UploadBulkConversationDto:
      type: object
      properties:
        messageSelectionReport:
          "$ref": "#/components/schemas/PreparedMessageSelectionReportDto"
        systemId:
          type: string
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        dateAppended:
          type: string
          example: DD_MM_YY_HH_mm_ss
        organizationId:
          type: string
        extractionCode:
          type: string
        case:
          type: string
        shareEmails:
          type: array
          items:
            type: string
        totalSize:
          type: number
        appVersion:
          type: string
        localBackupPath:
          type: string
        timezone:
          type: string
        searchData:
          type: array
          items:
            type: string
        messagesAlreadyExists:
          type: boolean
        isShareAll:
          type: boolean
      required:
      - systemId
      - deviceId
      - deviceType
      - dateAppended
      - organizationId
    SearchEntryMessageResponseEntity:
      type: object
      properties:
        text:
          type: string
          example: See you at 6
          nullable: true
        tags:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/TagResponseEntity"
        deviceType:
          type: string
          example: ios
          nullable: true
        sender:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceContactResponseEntity"
        receiver:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceContactResponseEntity"
        isEmpty:
          type: boolean
          example: false
        iMessageType:
          type: string
          enum:
          - na
          - text
          - text-with-attachment
          - group_title_action
          - group_picture_action
          - location_share_start_stop
          - group_member_added
          - group_member_removed
          - face_time
          - message-layout
          - location
          - link
          - file_attached
          - unsent
        isFromMe:
          type: boolean
          example: true
        isDelivered:
          type: boolean
          example: true
        isDeleted:
          type: boolean
          example: false
        isMissingMedia:
          type: boolean
          example: false
        isSystemMessage:
          type: boolean
          example: false
        attachment:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileResponseEntity"
        additionalAttachments:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/UserFileResponseEntity"
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        sentAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        readAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        deletedAt:
          type: string
          format: date-time
          nullable: true
          example:
        recoveredAt:
          format: date-time
          type: string
          nullable: true
          example:
        parentMessageText:
          type: string
          example: Are we still on for dinner?
          description: Text of the message this one replies to; null when it is not
            a reply.
          nullable: true
        parentMessageAttachment:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileResponseEntity"
        parentMessageIndex:
          type: number
          description: Index of the message this one replies to; null when it is not
            a reply.
          nullable: true
          example: 41
        service:
          type: string
          example: iMessage
        mimeType:
          type: string
          example: image/jpeg
        editHistory:
          type: array
          items:
            type: object
          nullable: true
          example:
          - date: '2026-01-15T14:30:00.000Z'
            text: See you at 7
        deletedMessagesLogs:
          type: object
          example:
            total: 2
            minDate: '2026-01-15T14:05:00.000Z'
            maxDate: '2026-01-15T14:20:00.000Z'
            deletedMessages: []
          nullable: true
        commentsCount:
          type: number
          example: 2
        messageIndex:
          type: number
          example: 42
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
        ocSharedStatus:
          type: string
          enum:
          - shared
          - pending
          - not_gated
          nullable: true
          description: 'Opposing-council only: how this conversation entry has been
            shared with the OL. Omitted (null) for non-OC callers.'
        collectionCount:
          type: number
          example: 1
        durationInSeconds:
          type: number
          description: Voicemail length in seconds; null unless the message is a voicemail.
          nullable: true
          example: 32
        callDurationInSeconds:
          type: number
          description: Call length in seconds; null unless the message is a call.
          nullable: true
          example: 125
        callType:
          type: string
          description: 'Call direction: ''incoming'', ''outgoing'', ''rejected'' or
            ''not_connected''; an empty string unless the message is a call.'
          example: incoming
        callSource:
          type: string
          nullable: true
          example: phone
      required:
      - text
      - tags
      - deviceType
      - sender
      - receiver
      - isEmpty
      - iMessageType
      - isFromMe
      - isDelivered
      - isDeleted
      - isMissingMedia
      - isSystemMessage
      - attachment
      - additionalAttachments
      - date
      - sentAt
      - readAt
      - deletedAt
      - recoveredAt
      - parentMessageText
      - parentMessageAttachment
      - parentMessageIndex
      - service
      - mimeType
      - editHistory
      - deletedMessagesLogs
      - commentsCount
      - messageIndex
      - collectionCount
      - durationInSeconds
      - callDurationInSeconds
      - callType
      - callSource
    CompareDeviceMessageResponseEntity:
      type: object
      properties:
        text:
          type: string
          example: See you at 6
          nullable: true
        tags:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/TagResponseEntity"
        deviceType:
          type: string
          example: ios
          nullable: true
        sender:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceContactResponseEntity"
        receiver:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceContactResponseEntity"
        isEmpty:
          type: boolean
          example: false
        iMessageType:
          type: string
          enum:
          - na
          - text
          - text-with-attachment
          - group_title_action
          - group_picture_action
          - location_share_start_stop
          - group_member_added
          - group_member_removed
          - face_time
          - message-layout
          - location
          - link
          - file_attached
          - unsent
        isFromMe:
          type: boolean
          example: true
        isDelivered:
          type: boolean
          example: true
        isDeleted:
          type: boolean
          example: false
        isMissingMedia:
          type: boolean
          example: false
        isSystemMessage:
          type: boolean
          example: false
        attachment:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileResponseEntity"
        additionalAttachments:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/UserFileResponseEntity"
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        sentAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        readAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        deletedAt:
          type: string
          format: date-time
          nullable: true
          example:
        recoveredAt:
          format: date-time
          type: string
          nullable: true
          example:
        parentMessageText:
          type: string
          example: Are we still on for dinner?
          description: Text of the message this one replies to; null when it is not
            a reply.
          nullable: true
        parentMessageAttachment:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileResponseEntity"
        parentMessageIndex:
          type: number
          description: Index of the message this one replies to; null when it is not
            a reply.
          nullable: true
          example: 41
        service:
          type: string
          example: iMessage
        mimeType:
          type: string
          example: image/jpeg
        editHistory:
          type: array
          items:
            type: object
          nullable: true
          example:
          - date: '2026-01-15T14:30:00.000Z'
            text: See you at 7
        deletedMessagesLogs:
          type: object
          example:
            total: 2
            minDate: '2026-01-15T14:05:00.000Z'
            maxDate: '2026-01-15T14:20:00.000Z'
            deletedMessages: []
          nullable: true
        messageIndex:
          type: number
          example: 42
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
        ocSharedStatus:
          type: string
          enum:
          - shared
          - pending
          - not_gated
          nullable: true
          description: 'Opposing-council only: how this conversation entry has been
            shared with the OL. Omitted (null) for non-OC callers.'
        collectionCount:
          type: number
          example: 1
        durationInSeconds:
          type: number
          description: Voicemail length in seconds; null unless the message is a voicemail.
          nullable: true
          example: 32
        callDurationInSeconds:
          type: number
          description: Call length in seconds; null unless the message is a call.
          nullable: true
          example: 125
        callType:
          type: string
          description: 'Call direction: ''incoming'', ''outgoing'', ''rejected'' or
            ''not_connected''; an empty string unless the message is a call.'
          example: incoming
        callSource:
          type: string
          nullable: true
          example: phone
        conversationId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
      required:
      - text
      - tags
      - deviceType
      - sender
      - receiver
      - isEmpty
      - iMessageType
      - isFromMe
      - isDelivered
      - isDeleted
      - isMissingMedia
      - isSystemMessage
      - attachment
      - additionalAttachments
      - date
      - sentAt
      - readAt
      - deletedAt
      - recoveredAt
      - parentMessageText
      - parentMessageAttachment
      - parentMessageIndex
      - service
      - mimeType
      - editHistory
      - deletedMessagesLogs
      - messageIndex
      - collectionCount
      - durationInSeconds
      - callDurationInSeconds
      - callType
      - callSource
      - conversationId
    CompareConversationMessagesDto:
      type: object
      properties:
        case:
          type: string
          description: Required if caseSlug not present
        caseSlug:
          type: string
          description: Required if case not present
        conversations:
          description: ID of the conversations to compare
          type: array
          items:
            type: string
        startDate:
          format: date-time
          type: string
        endDate:
          format: date-time
          type: string
        sortDirection:
          type: string
          enum:
          - asc
          - desc
          default: asc
        nextToken:
          type: string
      required:
      - conversations
    DeviceMessageTextSearchResponse:
      type: object
      properties:
        text:
          type: string
          example: See you at 6
          nullable: true
        tags:
          type: array
          items:
            "$ref": "#/components/schemas/TagResponseEntity"
        deviceConversation:
          "$ref": "#/components/schemas/DeviceConversationEntity"
        deviceType:
          type: string
          example: ios
          nullable: true
        sender:
          "$ref": "#/components/schemas/DeviceContactResponseEntity"
        receiver:
          "$ref": "#/components/schemas/DeviceContactResponseEntity"
        isEmpty:
          type: boolean
          example: false
        iMessageType:
          type: string
          enum:
          - na
          - text
          - text-with-attachment
          - group_title_action
          - group_picture_action
          - location_share_start_stop
          - group_member_added
          - group_member_removed
          - face_time
          - message-layout
          - location
          - link
          - file_attached
          - unsent
        isFromMe:
          type: boolean
          example: true
        isDelivered:
          type: boolean
          example: true
        isDeleted:
          type: boolean
          example: false
        isMissingMedia:
          type: boolean
          example: false
        isSystemMessage:
          type: boolean
          example: false
        attachment:
          "$ref": "#/components/schemas/UserFileResponseEntity"
        additionalAttachments:
          "$ref": "#/components/schemas/UserFileResponseEntity"
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        sentAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        readAt:
          format: date-time
          type: string
          example:
          nullable: true
        deletedAt:
          type: string
          format: date-time
          nullable: true
          example:
        recoveredAt:
          format: date-time
          type: string
          example:
          nullable: true
        parentMessageText:
          type: string
          example: Are we still on for tonight?
          description: Text of the message this one replies to; null when it is not
            a reply.
          nullable: true
        parentMessageAttachment:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileResponseEntity"
        parentMessageIndex:
          type: number
          description: Index of the message this one replies to; null when it is not
            a reply.
          nullable: true
          example: 41
        service:
          type: string
          example: iMessage
        mimeType:
          type: string
          example: image/jpeg
        editHistory:
          type: array
          items:
            type: object
          nullable: true
          example:
          - date: '2026-01-15T14:30:00.000Z'
            text: See you at 7
        deletedMessagesLogs:
          type: object
          example:
            total: 3
            minDate: '2026-01-14T09:00:00.000Z'
            maxDate: '2026-01-15T14:30:00.000Z'
            deletedMessages: []
        messageIndex:
          type: number
          example: 42
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
        collectionCount:
          type: number
          example: 1
        durationInSeconds:
          type: number
          description: Voicemail length in seconds; null unless the message is a voicemail.
          nullable: true
          example: 37
        callDurationInSeconds:
          type: number
          description: Call length in seconds; null unless the message is a call.
          nullable: true
          example: 125
        callType:
          type: string
          description: 'Call direction: ''incoming'', ''outgoing'', ''rejected'' or
            ''not_connected''; an empty string unless the message is a call.'
          example: outgoing
        highlightedText:
          type: string
          example: See you at [highlight=#FFFF00]6[/highlight]
      required:
      - text
      - tags
      - deviceConversation
      - deviceType
      - sender
      - receiver
      - isEmpty
      - iMessageType
      - isFromMe
      - isDelivered
      - isDeleted
      - isMissingMedia
      - isSystemMessage
      - attachment
      - additionalAttachments
      - date
      - sentAt
      - readAt
      - deletedAt
      - recoveredAt
      - parentMessageText
      - parentMessageAttachment
      - parentMessageIndex
      - service
      - mimeType
      - editHistory
      - deletedMessagesLogs
      - messageIndex
      - collectionCount
      - durationInSeconds
      - callDurationInSeconds
      - callType
      - highlightedText
    AdvanceTextSearchDto:
      type: object
      properties:
        page:
          type: number
          default: 1
        perPage:
          type: number
          default: 10
        skip:
          type: number
          description: If skip/limit not provided, page/perPage will take precedence
        limit:
          type: number
          description: If skip/limit not provided, page/perPage will take precedence
        case:
          type: string
          description: Required if caseSlug not present
        caseSlug:
          type: string
          description: Required if case not present
        keywords:
          type: array
          items:
            type: array
            maxLength: 100
            minLength: 3
        linkedContacts:
          type: array
          items:
            type: string
        devices:
          type: array
          items:
            type: string
        startDate:
          format: date-time
          type: string
        endDate:
          format: date-time
          type: string
        bbCode:
          type: string
          default: highlight="#FFFF00"
        operator:
          type: string
          enum:
          - AND
          - OR
          default: AND
        matchType:
          type: string
          enum:
          - FUZZY
          - EXACT
          default: FUZZY
      required:
      - keywords
    MessageTagCountEntity:
      type: object
      properties:
        tag:
          type: string
          description: Tag name.
          example: Important
        count:
          type: number
          description: Number of messages carrying this tag.
          example: 12
      required:
      - tag
      - count
    MessageTagsCountResponseEntity:
      type: object
      properties:
        total:
          type: number
          example: 42
        page:
          type: number
          example: 1
        perPage:
          type: number
          example: 20
        skip:
          type: number
          example: 0
        limit:
          type: number
          example: 20
        totalPages:
          type: number
          example: 3
        nextPage:
          type: number
          example: 2
          nullable: true
        previousPage:
          type: number
          example:
          nullable: true
        data:
          type: array
          items:
            "$ref": "#/components/schemas/MessageTagCountEntity"
      required:
      - total
      - totalPages
      - nextPage
      - previousPage
      - data
    MessageTagsCountDto:
      type: object
      properties:
        page:
          type: number
          default: 1
        perPage:
          type: number
          default: 10
        skip:
          type: number
          description: If skip/limit not provided, page/perPage will take precedence
        limit:
          type: number
          description: If skip/limit not provided, page/perPage will take precedence
        conversationIds:
          maxItems: 500
          description: Restrict to these conversation ids (must belong to the case).
            Omit to cover every conversation in the case.
          type: array
          items:
            type: string
        devices:
          maxItems: 50
          description: Restrict to conversations on these device ids.
          type: array
          items:
            type: string
        sources:
          type: array
          maxItems: 8
          description: Restrict to conversations of these sources.
          items:
            type: string
            enum:
            - message
            - whatsapp
            - voicemail
            - call_log
            - facebook_dump
            - instagram_dump
            - threads_dump
            - email
    UploadedConversation:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        messageSharing:
          type: object
        selectionCoverage:
          type: object
        name:
          type: string
        messagesCount:
          type: number
        displayName:
          type: string
        chatName:
          type: string
        source:
          type: string
          enum:
          - message
          - whatsapp
          - voicemail
          - call_log
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
          example: message
        lastMessageText:
          type: string
        lastMessageDate:
          format: date-time
          type: string
        lastMessageExternalId:
          type: string
        isGroup:
          type: boolean
          default: false
        externalId:
          type: string
        deviceExternalId:
          type: string
        callLogNumber:
          type: string
      required:
      - createdAt
      - updatedAt
      - id
      - messageSharing
      - name
      - messagesCount
      - displayName
      - chatName
      - source
      - lastMessageText
      - lastMessageDate
      - lastMessageExternalId
      - isGroup
      - externalId
      - deviceExternalId
      - callLogNumber
    SearchMessageByDateResponseEntity:
      type: object
      properties:
        messageIndex:
          type: number
          example: 42
        messageId:
          type: string
          example: 507f1f77bcf86cd799439011
        date:
          type: string
          example: '2026-01-15T14:30:00.000Z'
      required:
      - messageIndex
      - messageId
      - date
    DeviceMessageReactionsEntity:
      type: object
      properties:
        reaction:
          type: string
          example: "\U0001F44D"
          nullable: true
        contact:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
      required:
      - contact
    DeviceMessageEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        text:
          type: string
          example: See you at 6
          nullable: true
        tags:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/TagEntity"
        deviceConversation:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceConversationEntity"
        device:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        deviceType:
          type: string
          example: ios
          nullable: true
        sender:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceContactEntity"
        receiver:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceContactEntity"
        isEmpty:
          type: boolean
          example: false
        iMessageType:
          type: string
          enum:
          - na
          - text
          - text-with-attachment
          - group_title_action
          - group_picture_action
          - location_share_start_stop
          - group_member_added
          - group_member_removed
          - face_time
          - message-layout
          - location
          - link
          - file_attached
          - unsent
        isFromMe:
          type: boolean
          example: true
        isDelivered:
          type: boolean
          example: true
        isDeleted:
          type: boolean
          example: false
        isFinished:
          type: boolean
          example: false
        isArchive:
          type: boolean
          example: false
        isSpam:
          type: boolean
          example: false
        isSticker:
          type: boolean
          example: false
        isMissingMedia:
          type: boolean
          example: false
        isSystemMessage:
          type: boolean
          example: false
        attachment:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserFileEntity"
        additionalAttachments:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/UserFileEntity"
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        sentAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        readAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        playedAt:
          format: date-time
          type: string
          nullable: true
          example:
        editedAt:
          format: date-time
          type: string
          nullable: true
          example:
        deliveredAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        deletedAt:
          format: date-time
          type: string
          nullable: true
          example:
        recoveredAt:
          format: date-time
          type: string
          nullable: true
          example:
        retractedAt:
          format: date-time
          type: string
          nullable: true
          example:
        parentMessage:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/DeviceMessageEntity"
        messageFrom:
          type: string
          example: "+15555550100"
        messageTo:
          type: string
          example: "+15555550101"
        service:
          type: string
          example: iMessage
        mimeType:
          type: string
          example: image/jpeg
        commentsCount:
          type: number
          example: 2
        lastCommentByUserInitials:
          type: string
          example: JD
          nullable: true
        lastCommentByUserAvatarUrl:
          type: string
          example: https://files.example.com/avatars/jane-doe.png
          nullable: true
        messageIndex:
          type: number
          example: 42
        editHistory:
          type: array
          items:
            type: object
          nullable: true
          example:
          - date: '2026-01-15T14:30:00.000Z'
            text: See you at 7
        deletedMessagesLogs:
          type: object
          example:
            total: 2
            deletedMessages:
            - ids:
              - 1041
              - 1042
              startDate: '2026-01-15T14:05:00.000Z'
              endDate: '2026-01-15T14:20:00.000Z'
              count: 2
              previousId: 1040
              nextId: 1043
              prevChatId: 12
              nextChatId: 12
              isPrevMessageInSameChat: true
              isNextMessageInSameChat: true
            minDate: '2026-01-15T14:05:00.000Z'
            maxDate: '2026-01-15T14:20:00.000Z'
        guid:
          type: string
          example: 9F1C2B3A-4D5E-4F60-8A7B-1C2D3E4F5A6B
          nullable: true
        reactions:
          type: array
          items:
            "$ref": "#/components/schemas/DeviceMessageReactionsEntity"
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
        collectionCount:
          type: number
          example: 1
        durationInSeconds:
          type: number
          example: 32
        callDurationInSeconds:
          type: number
          example: 125
        callType:
          type: string
          example: incoming
      required:
      - createdAt
      - updatedAt
      - id
      - text
      - tags
      - deviceConversation
      - device
      - deviceType
      - sender
      - receiver
      - isEmpty
      - iMessageType
      - isFromMe
      - isDelivered
      - isDeleted
      - isFinished
      - isArchive
      - isSpam
      - isSticker
      - isMissingMedia
      - isSystemMessage
      - attachment
      - additionalAttachments
      - date
      - sentAt
      - readAt
      - playedAt
      - editedAt
      - deliveredAt
      - deletedAt
      - recoveredAt
      - retractedAt
      - parentMessage
      - messageFrom
      - messageTo
      - service
      - mimeType
      - lastCommentByUserInitials
      - lastCommentByUserAvatarUrl
      - messageIndex
      - editHistory
      - deletedMessagesLogs
      - guid
      - reactions
      - collectionCount
      - durationInSeconds
      - callDurationInSeconds
      - callType
    UpdateBulkMessageTagsResultEntity:
      type: object
      properties:
        data:
          "$ref": "#/components/schemas/DeviceMessageEntity"
      required:
      - data
    UpdateBulkMessageTagsDto:
      type: object
      properties:
        messages:
          minItems: 0
          maxItems: 1000
          description: Array of message ids
          type: array
          items:
            type: string
        tagNames:
          minItems: 0
          maxItems: 200
          description: array of tag text or id
          type: array
          items:
            type: string
      required:
      - messages
      - tagNames
    UpdateMessageTagsDto:
      type: object
      properties:
        tagNames:
          minItems: 0
          maxItems: 100
          description: Array of tag text or id
          type: array
          items:
            type: string
      required:
      - tagNames
    RemoveMessageTagsDto:
      type: object
      properties:
        tagNames:
          minItems: 0
          maxItems: 100
          description: Array of tag text or id
          type: array
          items:
            type: string
      required:
      - tagNames
    AwsTemporaryS3CredentialEntity:
      type: object
      properties:
        accessKeyId:
          type: string
          example: example-access-key-id
        secretAccessKey:
          type: string
          example: example-secret-access-key
        sessionToken:
          type: string
          example: example-session-token
        region:
          type: string
          example: us-east-1
        bucket:
          type: string
          example: acme-user-data
        apiVersion:
          type: string
          example: '2006-03-01'
        expiration:
          format: date-time
          type: string
          example: '2026-01-15T11:15:00.000Z'
      required:
      - accessKeyId
      - secretAccessKey
      - sessionToken
      - region
      - bucket
      - apiVersion
      - expiration
    AddFileDto:
      type: object
      properties:
        fileKey:
          type: string
          description: S3 file key (path in bucket)
        type:
          type: string
          description: Override content type (e.g. "application/pdf"). If not provided,
            uses the content type from S3.
      required:
      - fileKey
    AppFileLink:
      type: object
      properties:
        url:
          type: string
          example: Hearsay-3.14.0-arm64.dmg
        sha512:
          type: string
          example: q8Y0bYF0dGVzdGluZ0V4YW1wbGVTaGE1MTJCYXNlNjRWYWx1ZUZvckRvY3NPbmx5MDAwMDAwMDAwMDA9PQ==
        size:
          type: number
          example: 152043520
      required:
      - url
      - sha512
      - size
    LatestAppLinkForOsEntity:
      type: object
      properties:
        version:
          type: string
          example: 3.14.0
        files:
          type: array
          items:
            "$ref": "#/components/schemas/AppFileLink"
      required:
      - version
      - files
    LatestAppLinksEntity:
      type: object
      properties:
        mac:
          "$ref": "#/components/schemas/LatestAppLinkForOsEntity"
        windows:
          "$ref": "#/components/schemas/LatestAppLinkForOsEntity"
      required:
      - mac
      - windows
    DeviceFileHashDto:
      type: object
      properties:
        fileName:
          type: string
        fileHash:
          type: string
      required:
      - fileName
      - fileHash
    AddUpdateDeviceFileHashDto:
      type: object
      properties:
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        systemId:
          type: string
        files:
          type: array
          items:
            "$ref": "#/components/schemas/DeviceFileHashDto"
        appVersion:
          type: string
          description: App version
      required:
      - deviceId
      - deviceType
      - systemId
      - files
    MatchDeviceFileHashResultEntity:
      type: object
      properties:
        matched:
          type: boolean
          example: true
      required:
      - matched
    MatchDeviceFileHashDto:
      type: object
      properties:
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        systemId:
          type: string
        files:
          type: array
          items:
            "$ref": "#/components/schemas/DeviceFileHashDto"
        appVersion:
          type: string
          description: App version
      required:
      - deviceId
      - deviceType
      - systemId
      - files
    UpdateDeviceDto:
      type: object
      properties:
        name:
          type: string
          description: Name of the device
        manufacturer:
          type: string
          description: Manufacturer of the device
        brand:
          type: string
          description: Brand of the device
        model:
          type: string
          description: Model of the device
    MessageSelectionReportDto:
      type: object
      properties:
        version:
          type: number
        uploadAttemptId:
          type: string
        phase:
          type: object
        platformImportAccepted:
          type: boolean
        coverage:
          type: string
        eligibleScope:
          type: string
        conversations:
          type: array
          items:
            "$ref": "#/components/schemas/MessageSelectionConversationDto"
      required:
      - version
      - uploadAttemptId
      - phase
      - platformImportAccepted
      - coverage
      - eligibleScope
      - conversations
    OsDetailsDto:
      type: object
      properties:
        arch:
          type: string
        platform:
          type: string
        version:
          type: string
        osType:
          type: string
        machine:
          type: string
        totalMemory:
          type: number
        cpus:
          type: object
        extraInformation:
          type: object
      required:
      - cpus
      - extraInformation
    AddDeviceActivityLogDto:
      type: object
      properties:
        messageSelectionReport:
          "$ref": "#/components/schemas/MessageSelectionReportDto"
        systemId:
          type: string
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        type:
          type: string
          enum:
          - plugged-in
          - unplugged
          - backup-started
          - backup-completed
          - backup-failed
          - resync-started
          - resync-completed
          - resync-failed
          - conversation-upload-started
          - conversation-upload-completed
          - conversation-upload-failed
          - conversation-search
          - device-hash-saved
          - device-hash-matched
          - device-hash-not-matched
          - device-backup-deleted
          - case-export
          - conversation-added-to-case
          - conversation-removed-from-case
          - device-message-added-to-collection
          - device-message-removed-from-collection
          - comment-added-to-device-message
          - comment-removed-from-device-message
          - tag-added-to-device-message
          - tag-removed-from-device-message
          - device-backup-created
          - conversation-uploaded
        date:
          type: string
          description: ISO date string
        conversationNames:
          description: Required if type is either conversation-upload-completed or
            conversation-upload-started
          type: array
          items:
            type: string
        appVersion:
          type: string
          description: App version
        organizationId:
          type: string
        deviceMetadata:
          type: object
        backupMetadata:
          type: object
        extractionCode:
          type: string
        caseId:
          type: string
        osDetails:
          "$ref": "#/components/schemas/OsDetailsDto"
        addImmediate:
          type: boolean
        failedReason:
          type: string
        telemetryV2:
          type: boolean
          description: Set by app builds that already send the structured (v2) telemetry
            event for this activity, so the legacy activity-log translation is skipped
            and the activity is not recorded twice.
      required:
      - systemId
      - deviceId
      - deviceType
      - type
      - date
      - conversationNames
      - appVersion
    CommentEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        text:
          type: string
          example: Relevant to the timeline
        source:
          type: string
          nullable: true
          description: Id of the commented item (its kind is sourceType)
          example: 507f1f77bcf86cd799439011
        sourceType:
          type: string
          enum:
          - DeviceMessage
          - DeviceConversation
          - UserEmailData
          - FinancialAccount
          - CaseFile
          - UserEmailDataThread
          - LinkedInConversation
          - LinkedInJobApplication
          - AiChatConversation
        deviceMessage:
          type: string
          nullable: true
          description: Id of the commented message, for message comments
          example: 507f1f77bcf86cd799439011
        user:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserReferenceEntity"
        parent:
          type: string
          nullable: true
          description: Id of the comment this one replies to
          example: 507f1f77bcf86cd799439011
        replyCount:
          type: number
          example: 0
      required:
      - createdAt
      - updatedAt
      - id
      - text
      - source
      - sourceType
      - deviceMessage
      - user
      - parent
      - replyCount
    CreateCommentDto:
      type: object
      properties:
        text:
          type: string
      required:
      - text
    UpdateCommentDto:
      type: object
      properties:
        text:
          type: string
      required:
      - text
    ReplyCommentDto:
      type: object
      properties:
        text:
          type: string
      required:
      - text
    UserNotificationsEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        user:
          "$ref": "#/components/schemas/UserEntity"
        createdBy:
          "$ref": "#/components/schemas/UserEntity"
        conversation:
          "$ref": "#/components/schemas/DeviceConversationEntity"
        invitedUserName:
          type: string
        invitedUserEmail:
          type: string
        isRead:
          type: boolean
        readAt:
          format: date-time
          type: string
        type:
          type: string
          enum:
          - default
          - imported-new-conversation
          - added-conversation-to-existing-case
          - replied-to-comment
          - mentioned-in-comment
          - added-to-case
          - added-to-organization
          - new-comment
          - oc-suggestion-created-or-updated
          - oc-suggestion-approved
          - oc-suggestion-rejected
          - oc-suggestion-message
          - oc-data-approved
          - oc-member-added
          - oc-member-removed
        text:
          type: string
        caseId:
          type: string
        caseName:
          type: string
        conversationId:
          type: string
        importedConversationId:
          type: string
        importedConversationName:
          type: string
        caseSlug:
          type: string
        messageId:
          type: string
        commentId:
          type: string
        replyCommentId:
          type: string
        commentText:
          type: string
        replyText:
          type: string
        messageIndex:
          type: number
        commentIndex:
          type: number
        replyCommentIndex:
          type: number
      required:
      - createdAt
      - updatedAt
      - id
      - user
      - createdBy
      - conversation
      - invitedUserName
      - invitedUserEmail
      - isRead
      - readAt
      - type
      - text
      - caseId
      - caseName
      - conversationId
      - importedConversationId
      - importedConversationName
      - caseSlug
      - messageId
      - commentId
      - replyCommentId
      - commentText
      - replyText
      - messageIndex
      - commentIndex
      - replyCommentIndex
    DefaultPriceEntity:
      type: object
      properties:
        id:
          type: string
          example: price_1PExample
        active:
          type: boolean
          example: true
        billing_scheme:
          type: string
          example: per_unit
        created:
          type: number
          example: 1736935500
        currency:
          type: string
          example: usd
        custom_unit_amount:
          type: object
          example:
          nullable: true
        livemode:
          type: boolean
          example: false
        lookup_key:
          type: string
          example: firm_monthly
        metadata:
          type: object
          example: {}
        type:
          type: string
          example: recurring
        unit_amount:
          type: number
          example: 24900
        unit_amount_decimal:
          type: string
          example: '24900'
      required:
      - id
      - active
      - billing_scheme
      - created
      - currency
      - custom_unit_amount
      - livemode
      - lookup_key
      - metadata
      - type
      - unit_amount
      - unit_amount_decimal
    AvailablePlanEntity:
      type: object
      properties:
        id:
          type: string
          example: prod_Example
        active:
          type: boolean
          example: true
        attributes:
          example: []
          type: array
          items:
            type: string
        created:
          type: number
          example: 1736935500
        default_price:
          "$ref": "#/components/schemas/DefaultPriceEntity"
        features:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
          example:
          - name: Unlimited seats
        livemode:
          type: boolean
          example: false
        metadata:
          type: object
          example:
            plan: firm
        name:
          type: string
          example: Firm
        tax_code:
          type: object
          example:
          nullable: true
        unit_label:
          type: object
          example:
          nullable: true
        url:
          type: object
          example:
          nullable: true
        isPayAsYouGo:
          type: boolean
          example: false
        isCurrentPlan:
          type: boolean
          example: true
      required:
      - id
      - active
      - attributes
      - created
      - default_price
      - features
      - livemode
      - metadata
      - name
      - tax_code
      - unit_label
      - url
      - isPayAsYouGo
      - isCurrentPlan
    InvoiceLineItemEntity:
      type: object
      properties:
        id:
          type: string
          example: il_1PExample
        amount:
          type: number
          example: 24900
        amount_excluding_tax:
          type: number
          example: 24900
        currency:
          type: string
          example: usd
        description:
          type: string
          example: 1 x Firm (at $249.00 / month)
        discount_amounts:
          type: array
          items:
            type: object
          example: []
        discountable:
          type: boolean
          example: true
        discounts:
          example: []
          type: array
          items:
            type: string
        livemode:
          type: boolean
          example: false
        metadata:
          type: object
          example: {}
        period:
          type: object
          example:
            start: 1736935500
            end: 1739613900
        product_lookup_description:
          type: string
          example: Subscription
        plan:
          "$ref": "#/components/schemas/AvailablePlanEntity"
        price:
          "$ref": "#/components/schemas/DefaultPriceEntity"
        quantity:
          type: number
          example: 1
        unit_amount_excluding_tax:
          type: number
          example: 24900
      required:
      - id
      - amount
      - amount_excluding_tax
      - currency
      - description
      - discount_amounts
      - discountable
      - discounts
      - livemode
      - metadata
      - period
      - product_lookup_description
      - plan
      - price
      - quantity
      - unit_amount_excluding_tax
    UpcomingInvoiceEntity:
      type: object
      properties:
        account_country:
          type: string
          example: US
        account_name:
          type: string
          example: Acme Legal
        amount_due:
          type: number
          example: 24900
        amount_paid:
          type: number
          example: 0
        amount_remaining:
          type: number
          example: 24900
        attempted:
          type: boolean
          example: false
        billing_reason:
          type: string
          example: upcoming
        collection_method:
          type: string
          example: charge_automatically
        created:
          type: number
          example: 1736935500
        currency:
          type: string
          example: usd
        customer_address:
          type: object
          example:
          nullable: true
        customer_email:
          type: string
          example: billing@example.com
        customer_name:
          type: string
          example: Acme Legal
        customer_phone:
          type: string
          example:
          nullable: true
        description:
          type: string
          example:
          nullable: true
        discount:
          type: object
          example:
          nullable: true
        discounts:
          example: []
          type: array
          items:
            type: string
        due_date:
          type: object
          example:
          nullable: true
        livemode:
          type: boolean
          example: false
        period_end:
          type: number
          example: 1739613900
        period_start:
          type: number
          example: 1736935500
        status:
          type: string
          example: draft
        subtotal:
          type: number
          example: 24900
        subtotal_excluding_tax:
          type: number
          example: 24900
        tax:
          type: number
          example: 0
        total:
          type: number
          example: 24900
        total_discount_amounts:
          type: array
          items:
            type: object
          example: []
        total_excluding_tax:
          type: number
          example: 24900
        total_tax_amounts:
          type: array
          items:
            type: object
          example: []
        lines:
          type: array
          items:
            "$ref": "#/components/schemas/InvoiceLineItemEntity"
      required:
      - account_country
      - account_name
      - amount_due
      - amount_paid
      - amount_remaining
      - attempted
      - billing_reason
      - collection_method
      - created
      - currency
      - customer_address
      - customer_email
      - customer_name
      - customer_phone
      - description
      - discount
      - discounts
      - due_date
      - livemode
      - period_end
      - period_start
      - status
      - subtotal
      - subtotal_excluding_tax
      - tax
      - total
      - total_discount_amounts
      - total_excluding_tax
      - total_tax_amounts
      - lines
    BillingAlertEntity:
      type: object
      properties:
        pastDue:
          type: boolean
          description: Subscription is past due (in dunning)
          example: true
        graceUntil:
          format: date-time
          type: string
          description: End of the grace window while past due
          nullable: true
          example: '2026-01-22T10:15:00.000Z'
        blocked:
          type: boolean
          description: Access is blocked (grace expired, or subscription unpaid/canceled)
          example: false
      required:
      - pastDue
      - graceUntil
      - blocked
    AvailablePlansListEntity:
      type: object
      properties:
        data:
          type: array
          items:
            "$ref": "#/components/schemas/AvailablePlanEntity"
        attachedPaymentMethod:
          type: object
          description: Attached payment method
          example:
            id: pm_1PExample
            type: card
            card:
              brand: visa
              last4: '4242'
              exp_month: 4
              exp_year: 2030
        upcomingInvoice:
          description: Attached payment method
          allOf:
          - "$ref": "#/components/schemas/UpcomingInvoiceEntity"
        currentPlan:
          "$ref": "#/components/schemas/AvailablePlanEntity"
        billingAlert:
          nullable: true
          description: Dunning banner state; null when billing is healthy
          type: object
          allOf:
          - "$ref": "#/components/schemas/BillingAlertEntity"
      required:
      - data
      - attachedPaymentMethod
      - upcomingInvoice
      - currentPlan
      - billingAlert
    CouponCodeDetailsResponseEntity:
      type: object
      properties:
        isValid:
          type: boolean
          example: true
        id:
          type: string
          example: promo_1PExample
        type:
          type: string
          enum:
          - percentage
          - flat
          example: percentage
        value:
          type: number
          example: 20
      required:
      - isValid
      - id
      - type
      - value
    CheckCouponValidityDto:
      type: object
      properties:
        code:
          type: string
      required:
      - code
    CheckoutSessionResponseEntity:
      type: object
      properties:
        url:
          type: string
          example: https://checkout.stripe.com/c/pay/cs_test_a1Example
      required:
      - url
    CreateCheckoutSessionDto:
      type: object
      properties:
        priceId:
          type: string
        cancelUrl:
          type: string
        successUrl:
          type: string
        couponId:
          type: string
      required:
      - priceId
      - cancelUrl
      - successUrl
      - couponId
    StripeCustomerSessionEntity:
      type: object
      properties:
        clientSecret:
          type: string
          example: cuss_secret_Example
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
        expiresAt:
          format: date-time
          type: string
          example: '2026-01-15T10:45:00.000Z'
      required:
      - clientSecret
      - createdAt
      - expiresAt
    BillingPortalResponseEntity:
      type: object
      properties:
        url:
          type: string
          example: https://billing.stripe.com/p/session/test_Example
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
        customer:
          type: string
          example: cus_Example
      required:
      - url
      - createdAt
      - customer
    CreateBillingPortalSessionDto:
      type: object
      properties:
        type:
          type: string
          enum:
          - payment_method_update
          - subscription_cancel
          - subscription_update
          default: payment_method_update
        returnUrl:
          type: string
      required:
      - returnUrl
    CreateUpdateAppConfigDto:
      type: object
      properties:
        systemId:
          type: string
          maxLength: 99999
          minLength: 1
        defaultBackupPath:
          type: string
        lastUsedBackupPath:
          type: string
        otherSettings:
          type: object
        osDetails:
          "$ref": "#/components/schemas/OsDetailsDto"
        lastUsedAppVersion:
          type: string
        lastAppUsedAt:
          type: string
      required:
      - systemId
    AddUpdateAppConfigDeviceSettingsDto:
      type: object
      properties:
        systemId:
          type: string
          maxLength: 99999
          minLength: 1
        deviceId:
          type: string
          maxLength: 99999
          minLength: 1
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        defaultBackupPath:
          type: string
        lastUsedBackupPath:
          type: string
        autoBackupFrequency:
          type: string
          enum:
          - none
          - daily
          - weekly
          - monthly
        deviceMetadata:
          type: object
        otherSettings:
          type: object
        lastBackupAt:
          format: date-time
          type: string
        lastLocalBackupAt:
          format: date-time
          type: string
      required:
      - systemId
      - deviceId
      - deviceType
    DeleteAppConfigDeviceSettingsDto:
      type: object
      properties:
        systemId:
          type: string
          maxLength: 99999
          minLength: 1
        deviceId:
          type: string
          maxLength: 99999
          minLength: 1
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
      required:
      - systemId
      - deviceId
      - deviceType
    AddGoodFactCaseDto:
      type: object
      properties:
        lawyerEmail:
          type: string
          description: Lawyer Email
        organizationName:
          type: string
          description: Organization Name
        goodfactCaseName:
          type: string
          description: Goodfact Case Name
        goodfactEndpoint:
          type: string
          description: Integration Endpoint
        caseAPIKey:
          type: string
          description: Goodfact Case Integration API Key
      required:
      - lawyerEmail
      - organizationName
      - goodfactCaseName
      - goodfactEndpoint
      - caseAPIKey
    LegalMatterTypeEntity:
      type: object
      properties:
        id:
          type: string
          example: adoption
        name:
          type: string
          example: Adoption
      required:
      - id
      - name
    LegalMatterSummaryEntity:
      type: object
      properties:
        id:
          type: string
          example: '12345'
          description: Provider-assigned matter id (integer string for Clio, UUID
            for Smokeball).
        displayNumber:
          type: string
          example: 00123-Smith
        name:
          type: string
          example: Smith v. Acme Corp
        description:
          type: string
          example: Wrongful termination claim
        status:
          type: string
          enum:
          - open
          - pending
          - closed
          example: open
        matterType:
          description: Smokeball matter type; not populated for Clio.
          allOf:
          - "$ref": "#/components/schemas/LegalMatterTypeEntity"
        isLinked:
          type: boolean
          example: true
          description: True when this matter is already linked to a Hearsay case.
        linkedCaseId:
          type: string
          example: 64f0c2a1e3b9a40012ab34cd
          description: The linked Hearsay case id, present only when `isLinked` is
            true.
      required:
      - id
      - name
      - status
      - isLinked
    LegalMattersMetaEntity:
      type: object
      properties:
        page:
          type: number
          example: 1
          description: 1-based page number.
        limit:
          type: number
          example: 25
          description: Items per page.
        hasMore:
          type: boolean
          example: true
          description: True when more pages are available after this one.
      required:
      - page
      - limit
      - hasMore
    LegalMattersResponseEntity:
      type: object
      properties:
        matters:
          type: array
          items:
            "$ref": "#/components/schemas/LegalMatterSummaryEntity"
        meta:
          "$ref": "#/components/schemas/LegalMattersMetaEntity"
      required:
      - matters
      - meta
    LinkMatterDto:
      type: object
      properties:
        caseId:
          type: string
          description: CaseId for the case to link
      required:
      - caseId
    AutoCollectionRoleDto:
      type: object
      properties:
        role:
          type: string
          description: Collection role, e.g. "Ex-Spouse"
        hint:
          type: string
        required:
          type: boolean
        channels:
          description: Delivery channels, e.g. ["email", "slack_teams"]
          type: array
          items:
            type: string
      required:
      - role
      - required
      - channels
    AutoCollectionDateRangeDto:
      type: object
      properties:
        start:
          format: date-time
          type: string
          nullable: true
        end:
          format: date-time
          type: string
          nullable: true
    AutoCollectionTemplateDto:
      type: object
      properties:
        caseType:
          type: string
          description: Front case-type vocabulary (e.g. "employment")
        sources:
          type: array
          description: Pre-mapped MasterDataSourceName values
          items:
            type: string
            enum:
            - ios
            - android
            - facebook_dump
            - instagram_dump
            - threads_dump
            - gmail
            - yahoo
            - outlook
            - icloud
            - imap
            - ews
            - files
            - bank-docs
            - linkedin_dump
            - claude_dump
            - chatgpt_dump
            - gemini_dump
        roles:
          type: array
          items:
            "$ref": "#/components/schemas/AutoCollectionRoleDto"
        documentRequests:
          type: array
          items:
            type: string
        dateRange:
          "$ref": "#/components/schemas/AutoCollectionDateRangeDto"
      required:
      - caseType
      - sources
      - roles
      - documentRequests
    AutoCollectionSettingsDto:
      type: object
      properties:
        enabled:
          type: boolean
          description: Pre-configure the auto-created case + attach the client as
            custodian (fires the collection email). Requires autoCreateFromWebhook.
        template:
          "$ref": "#/components/schemas/AutoCollectionTemplateDto"
    UpdateLegalCasesSettingsDto:
      type: object
      properties:
        autoCreateFromWebhook:
          type: boolean
          description: Auto-create a Hearsay case when a matter is created in Clio
        autoCollection:
          "$ref": "#/components/schemas/AutoCollectionSettingsDto"
    UpdateLegalDocumentsSettingsDto:
      type: object
      properties:
        autoSync:
          type: boolean
          description: Enable automatic document sync from Clio
    UpdateDataSourceSettingsDto:
      type: object
      properties:
        enabled:
          type: boolean
    UpdateEmailDataSourceSettingsDto:
      type: object
      properties:
        enabled:
          type: boolean
        format:
          type: string
          enum:
          - pdf
          - eml
    UpdateDeviceDataSourceSettingsDto:
      type: object
      properties:
        enabled:
          type: boolean
        format:
          type: string
          enum:
          - pdf
          - rsf
    FinancialExportTypesDto:
      type: object
      properties:
        transactions:
          type: boolean
          description: Export bank transactions
        recurringTransactions:
          type: boolean
          description: Export recurring transactions
        holdings:
          type: boolean
          description: Export holdings
        liabilities:
          type: boolean
          description: Export liabilities
        investmentTransactions:
          type: boolean
          description: Export investment transactions
        statements:
          type: boolean
          description: Export account statements as a ZIP bundle (out-of-band). Drives
            the default (no-filter) export; an explicit financial filter with a statements-bundle
            item overrides this toggle.
    UpdateFinancialDataSourceSettingsDto:
      type: object
      properties:
        enabled:
          type: boolean
        format:
          type: string
          enum:
          - pdf
          - csv
        types:
          "$ref": "#/components/schemas/FinancialExportTypesDto"
    DataSourcesSettingsDto:
      type: object
      properties:
        file:
          "$ref": "#/components/schemas/UpdateDataSourceSettingsDto"
        email:
          "$ref": "#/components/schemas/UpdateEmailDataSourceSettingsDto"
        device:
          "$ref": "#/components/schemas/UpdateDeviceDataSourceSettingsDto"
        financial:
          "$ref": "#/components/schemas/UpdateFinancialDataSourceSettingsDto"
        reddit:
          "$ref": "#/components/schemas/UpdateDataSourceSettingsDto"
        discord:
          "$ref": "#/components/schemas/UpdateDataSourceSettingsDto"
    UpdateLegalSettingsDto:
      type: object
      properties:
        cases:
          "$ref": "#/components/schemas/UpdateLegalCasesSettingsDto"
        documents:
          "$ref": "#/components/schemas/UpdateLegalDocumentsSettingsDto"
        dataSources:
          "$ref": "#/components/schemas/DataSourcesSettingsDto"
    ExportToProviderResultEntity:
      type: object
      properties:
        queued:
          type: boolean
          description: False when nothing matched the request — no job was enqueued.
          example: true
        jobId:
          type: string
          nullable: true
          description: 'Bull job id. One in-flight export per (case, provider): a
            second request while the first is still running collapses onto it.'
          example: legal-export-65a1b2c3d4e5f6a7b8c9d0e2-clio
        matched:
          type: object
          description: Records matched per source type. A source the caller named
            explicitly is rejected with 409 when it matches nothing, so a zero here
            only ever appears for an unscoped request.
          example:
            Device: 3
            UserEmailDataProvider: 1
        totalMatched:
          type: number
          example: 4
      required:
      - queued
      - matched
      - totalMatched
    ExportAccountTypesFilterDto:
      type: object
      properties:
        include:
          type: array
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
        exclude:
          type: array
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
    FinancialExportItemDto:
      type: object
      properties:
        exportType:
          type: string
          enum:
          - transactions
          - holdings
          - liabilities
          - recurring-transactions
          - investment-transactions
          - statements-bundle
        accountTypes:
          "$ref": "#/components/schemas/ExportAccountTypesFilterDto"
        startDate:
          type: string
        endDate:
          type: string
        category:
          type: string
        transactionType:
          type: string
          enum:
          - debit
          - credit
        minAmount:
          type: number
        maxAmount:
          type: number
        type:
          type: string
        frequency:
          type: string
        isActive:
          type: boolean
        liabilityType:
          type: string
          enum:
          - credit
          - mortgage
          - student
        isOverdue:
          type: boolean
        tags:
          type: array
          items:
            type: array
      required:
      - exportType
    ExportSourceFiltersDto:
      type: object
      properties:
        conversations:
          type: array
          items:
            type: array
        excludeConversations:
          type: array
          items:
            type: array
        startDate:
          type: string
        endDate:
          type: string
        tags:
          type: array
          items:
            type: array
        threads:
          description: Specific email threads to export. Omit to export every thread
            under the extraction code (minus `excludeThreads`).
          type: array
          items:
            type: array
        excludeThreads:
          type: array
          items:
            type: array
        includeHidden:
          type: boolean
          default: false
          description: When true, threads the user hid in the platform (hiddenAt !=
            null) are exported too. Default false, matching the download export. Ignored
            when `threads` is set — an explicit id selection is honored regardless
            of hidden state.
        participants:
          description: Correspondent emails (to/from) — matches thread participants
          type: array
          items:
            type: array
        sourceEmail:
          type: string
        folders:
          type: array
          items:
            type: array
        searchText:
          type: string
        receivedAfter:
          type: string
        receivedBefore:
          type: string
        financial:
          type: array
          items:
            "$ref": "#/components/schemas/FinancialExportItemDto"
    ExportRenderOptionsDto:
      type: object
      properties:
        exportConfig:
          type: array
          items:
            type: string
            enum:
            - phone-numbers
            - comments
            - tags
            - app-icons
            - device-name
            - images
            - hide-attachment-links
            - extra-texts
            - line-break
            - device-logs
            - custom-entry
            - recently-deleted
            - deleted-messages-inline
            - deleted-messages-page
            - deleted-messages-inline-page
            - ignore-conversations
            - search-history-page
            - hide-page-numbers
            - print-footer-work-timestamp
        sortBy:
          type: string
          enum:
          - old-to-new
          - new-to-old
        chunkType:
          type: string
          enum:
          - none
          - day
          - week
          - month
          - year
        timezone:
          type: string
          description: IANA timezone (e.g. "America/New_York") for timestamps
        tagsPadding:
          type: number
          minimum: 0
          maximum: 20
        includeAttachments:
          type: boolean
          description: Email only. Bundle each email's attachments alongside the rendered
            output instead of linking back to the platform. Changes the delivered
            artifact to a ZIP; subject to the files-zip size limits.
    ExportSourceDto:
      type: object
      properties:
        sourceType:
          type: string
          enum:
          - Device
          - UserEmailDataProvider
          - CaseFileProvider
          - FinancialAccountProvider
          - LegalDocumentProvider
          - DiscordUserProvider
          - RedditUserProvider
          - LinkedInUserProvider
          - AiChatProvider
        format:
          type: string
          enum:
          - pdf
          - csv
          - rsmf
          - eml
        filters:
          "$ref": "#/components/schemas/ExportSourceFiltersDto"
        options:
          "$ref": "#/components/schemas/ExportRenderOptionsDto"
    ExportToProviderDto:
      type: object
      properties:
        provider:
          type: string
          enum:
          - clio
          - smokeball
          - dropbox
          default: clio
        includeFailed:
          type: boolean
          default: false
        includeSkipped:
          type: boolean
          default: false
          description: Also export records previously skipped because the source was
            not enabled / the case was not linked when the data arrived. Requires
            `sources` to be set — see SkippedRequiresExplicitSources.
        extractionCodeIds:
          description: Custodian (extraction code) scope — orthogonal to filters.
          type: array
          items:
            type: array
        sources:
          description: Per-source selection + filters. OMIT to export every pending
            source with no filters. Each entry only accepts the filter keys valid
            for its sourceType (others are rejected) — see the example.
          example:
          - sourceType: Device
            format: pdf
            filters:
              conversations:
              - 665f0a1b2c3d4e5f00000001
              startDate: '2021-01-01'
              endDate: '2021-12-31'
              tags:
              - 665f0a1b2c3d4e5f00000002
          - sourceType: UserEmailDataProvider
            filters:
              participants:
              - jane@example.com
              receivedAfter: '2021-01-01'
              receivedBefore: '2021-12-31'
          - sourceType: FinancialAccountProvider
            filters:
              financial:
              - exportType: transactions
                startDate: '2021-01-01'
                endDate: '2021-12-31'
                category: Travel
                transactionType: debit
                accountTypes:
                  include:
                  - depository
              - exportType: statements-bundle
                startDate: '2021-01-01'
                endDate: '2021-06-30'
          type: array
          items:
            "$ref": "#/components/schemas/ExportSourceDto"
    LegalSyncRecordEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f3
        destination:
          type: string
          enum:
          - clio
          - smokeball
          - dropbox
          example: clio
        integrationType:
          type: string
          enum:
          - goodfact
          - clio
          - smokeball
          nullable: true
          deprecated: true
          description: Deprecated alias of `destination`, present only for CMP destinations.
            Null for storage destinations, which have no integration type. Use `destination`.
          example: clio
        sourceType:
          type: string
          example: CaseFileProvider
        status:
          type: string
          enum:
          - pending
          - processing
          - completed
          - failed
          - disabled
          - skipped
          example: completed
        fileName:
          type: string
          nullable: true
          example: text-messages-export.pdf
        target:
          type: string
          nullable: true
          description: 'Where the document was filed: a matter id for a CMP, a folder
            path for storage.'
          example: '12345'
        matterId:
          type: string
          nullable: true
          deprecated: true
          description: Deprecated alias of `target`. Use `target`.
          example: '12345'
        errorMessage:
          type: string
          nullable: true
          example:
        requiresExport:
          type: boolean
          example: false
        remoteDocumentId:
          type: string
          nullable: true
          description: Destination document id once uploaded (null until sent).
          example: '778899'
        startedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:15:00.000Z'
        completedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:20:00.000Z'
        failedAt:
          format: date-time
          type: string
          nullable: true
          example:
        createdAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:15:00.000Z'
        updatedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:20:00.000Z'
      required:
      - id
      - destination
      - sourceType
      - status
      - requiresExport
    CreateLegalExpenseDto:
      type: object
      properties:
        amount:
          type: number
          example: 49
          description: Expense amount in the matter currency.
        description:
          type: string
          example: 'Hearsay: phone extraction upload'
          description: Description shown on the provider expense line item.
        date:
          type: string
          example: '2026-05-29'
          description: ISO-8601 date the expense was incurred. Defaults to today when
            omitted.
        expenseCategoryId:
          type: string
          description: Optional provider expense-category / activity-code id used
            to categorize the expense.
      required:
      - amount
      - description
    SetCaseDestinationFolderDto:
      type: object
      properties:
        path:
          type: string
          example: "/Clients/Smith v. Acme"
          description: Absolute folder path inside the connected account, as returned
            by the folder listing.
      required:
      - path
    DataClioWebhook:
      type: object
      properties:
        webhook_id:
          type: number
          description: Webhook ID
        id:
          type: number
          description: Record id
        etag:
          type: string
          description: Etag of the record
      required:
      - webhook_id
      - id
      - etag
    MetaClioWebhook:
      type: object
      properties:
        event:
          type: string
          description: Event type (created, updated, deleted)
        model:
          type: string
          description: Model name (matter, document, etc)
        webhook_id:
          type: number
          description: Webhook ID
      required:
      - event
      - webhook_id
    ClioMatterWebHookDto:
      type: object
      properties:
        data:
          description: Object with the data
          allOf:
          - "$ref": "#/components/schemas/DataClioWebhook"
        meta:
          description: Metadata of the webhook event
          allOf:
          - "$ref": "#/components/schemas/MetaClioWebhook"
      required:
      - data
      - meta
    ClioDocumentWebhookDto:
      type: object
      properties:
        data:
          description: Object with the data
          allOf:
          - "$ref": "#/components/schemas/DataClioWebhook"
        meta:
          description: Metadata of the webhook event
          allOf:
          - "$ref": "#/components/schemas/MetaClioWebhook"
      required:
      - data
      - meta
    SmokeballWebhookDto:
      type: object
      properties:
        type:
          type: string
          description: Event type, e.g. 'matter.created'
        subscriptionId:
          type: string
          description: Subscription that produced this delivery
        accountId:
          type: string
          description: Smokeball firm/account id
        userId:
          type: string
          description: Smokeball user that triggered the event
        source:
          type: string
          description: Origin of the change
        payload:
          type: object
          description: Event-specific payload (ids of the changed resource)
        timestamp:
          type: string
          description: Event timestamp (provider-supplied)
      required:
      - type
      - subscriptionId
      - accountId
    CaseEventUserEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: Jane Doe
        email:
          type: string
          example: jane.doe@example.com
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - email
    CaseEventEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        type:
          type: string
          enum:
          - financial_connection_removed
          - oc_invited
          - oc_accepted
          - oc_member_added
          - oc_member_removed
          - oc_ec_created
          - oc_suggestion_created
          - oc_suggestion_updated
          - oc_suggestion_message
          - oc_suggestion_rejected
          - oc_ec_approved
          - oc_data_approved
          - oc_data_revoked
          - oc_code_released
          - oc_code_unreleased
          - member_invited
          - member_accepted
          - case_created
          - case_status_changed
          - extraction_code_created
          - extraction_code_updated
          - extraction_code_locked
          - custodian_updated
          - device_attached
          - data_uploaded
          - export_started
          - export_finished
          - export_errored
          - export_cancelled
        category:
          type: string
          enum:
          - general
          - case_data
          - oc
        description:
          type: string
          example: Export finished
        createdBy:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/CaseEventUserEntity"
        metadata:
          type: object
          example: {}
      required:
      - createdAt
      - updatedAt
      - id
      - type
      - category
      - description
      - createdBy
      - metadata
    CreateCaseForUserDto:
      type: object
      properties:
        custodianEmail:
          type: string
          maxLength: 100
          minLength: 3
        custodianName:
          type: string
          maxLength: 100
          minLength: 3
        custodianPhone:
          type: string
          maxLength: 50
          minLength: 3
        sendEmail:
          type: boolean
        label:
          type: string
          maxLength: 100
          minLength: 3
        allowShareAll:
          type: boolean
          deprecated: true
          description: Use config instead
        config:
          "$ref": "#/components/schemas/ExtractionCodeConfigDto"
        requestedSources:
          type: array
          items:
            type: string
        dueDate:
          type: string
        sendReminders:
          "$ref": "#/components/schemas/ExtractionCodeSendRemindersDto"
        metadata:
          type: object
        watchlist:
          type: boolean
          default: false
        ocRequired:
          type: boolean
          default: true
          description: 'OC cases only: when false, the OL creates/keeps this code
            without the opposing-council approval gate (code stays `na`, immediately
            usable, and the custodian email is sent). Ignored on non-OC cases. Defaults
            to true (gate on).'
        paymentRequired:
          type: boolean
          description: Whether payment is required for this extraction code
        paymentAmount:
          type: number
          description: Payment amount in cents
        paymentCurrency:
          type: string
          description: Payment currency (defaults to usd)
          default: usd
        paymentSuccessRedirectUrl:
          type: string
          description: Payment success redirect URL
        productName:
          type: string
          description: Product name
        prepaidItems:
          description: Prepaid basket. Each entry becomes its own Stripe line item
            and grants units on payment. Prices are negotiable — unitAmountCents is
            what the customer is charged, and is never validated against the catalog
            default.
          type: array
          items:
            "$ref": "#/components/schemas/PrepaidItemDto"
        name:
          type: string
          maxLength: 100
          minLength: 3
        description:
          type: string
        matterId:
          type: string
          maxLength: 100
          description: The firm's own client/matter reference. Printed on Stripe invoice
            line items and carried in invoice metadata.
        tags:
          type: array
          items:
            type: string
        status:
          type: string
          enum:
          - open
          - closed
          - archived
          - pending
          default: pending
        priority:
          type: string
          enum:
          - low
          - medium
          - high
          - urgent
          default: low
        isDefault:
          type: boolean
          default: false
        createExtractionCode:
          type: boolean
          default: false
        addExistingUsersToCase:
          type: boolean
          default: false
        addExistingUsersAccessLevel:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
          default: admin
        users:
          type: array
          items:
            "$ref": "#/components/schemas/InviteCaseUser"
        userId:
          type: string
          description: required if userEmail not sent
        userEmail:
          type: string
          description: required if userId not sent
        organization:
          type: string
      required:
      - dueDate
      - name
      - organization
    DeleteCaseConversationsDto:
      type: object
      properties:
        caseId:
          type: string
          description: required if caseSlug not sent
        caseSlug:
          type: string
          description: required if caseId not sent
        orgId:
          type: string
          description: required if orgSlug not sent
        orgSlug:
          type: string
          description: required if orgId not sent
        deleteCaseIfNoConversationsLeft:
          type: boolean
          default: false
    ExtractionCodeCreateSingleResponseEntity:
      type: object
      properties:
        caseId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        code:
          type: string
          example: K7Q2MZ
      required:
      - caseId
      - code
    CreateExtractionCodeResponseEntity:
      type: object
      properties:
        data:
          example:
          - caseId: 65a1b2c3d4e5f6a7b8c9d0e2
            code: K7Q2MZ
          type: array
          items:
            "$ref": "#/components/schemas/ExtractionCodeCreateSingleResponseEntity"
      required:
      - data
    SingleExtractionCodeCreateDto:
      type: object
      properties:
        custodianEmail:
          type: string
          maxLength: 100
          minLength: 3
        custodianName:
          type: string
          maxLength: 100
          minLength: 3
        custodianPhone:
          type: string
          maxLength: 50
          minLength: 3
        sendEmail:
          type: boolean
        label:
          type: string
          maxLength: 100
          minLength: 3
        allowShareAll:
          type: boolean
          deprecated: true
          description: Use config instead
        config:
          "$ref": "#/components/schemas/ExtractionCodeConfigDto"
        requestedSources:
          type: array
          items:
            type: string
        dueDate:
          type: string
        sendReminders:
          "$ref": "#/components/schemas/ExtractionCodeSendRemindersDto"
        metadata:
          type: object
        watchlist:
          type: boolean
          default: false
        ocRequired:
          type: boolean
          default: true
          description: 'OC cases only: when false, the OL creates/keeps this code
            without the opposing-council approval gate (code stays `na`, immediately
            usable, and the custodian email is sent). Ignored on non-OC cases. Defaults
            to true (gate on).'
        paymentRequired:
          type: boolean
          description: Whether payment is required for this extraction code
        paymentAmount:
          type: number
          description: Payment amount in cents
        paymentCurrency:
          type: string
          description: Payment currency (defaults to usd)
          default: usd
        paymentSuccessRedirectUrl:
          type: string
          description: Payment success redirect URL
        productName:
          type: string
          description: Product name
        prepaidItems:
          description: Prepaid basket. Each entry becomes its own Stripe line item
            and grants units on payment. Prices are negotiable — unitAmountCents is
            what the customer is charged, and is never validated against the catalog
            default.
          type: array
          items:
            "$ref": "#/components/schemas/PrepaidItemDto"
        caseId:
          type: string
      required:
      - dueDate
      - caseId
    CreateMultipleExtractionCodeDto:
      type: object
      properties:
        ecCodeData:
          type: array
          items:
            "$ref": "#/components/schemas/SingleExtractionCodeCreateDto"
      required:
      - ecCodeData
    UpdateExtractionCodeAdminDto:
      type: object
      properties:
        custodianEmail:
          type: string
          maxLength: 100
          minLength: 3
        custodianName:
          type: string
          maxLength: 100
          minLength: 3
        custodianPhone:
          type: string
          maxLength: 50
          minLength: 3
        sendEmail:
          type: boolean
        label:
          type: string
          maxLength: 100
          minLength: 3
        allowShareAll:
          type: boolean
          deprecated: true
          description: Use config instead
        config:
          "$ref": "#/components/schemas/ExtractionCodeConfigDto"
        requestedSources:
          type: array
          items:
            type: string
        dueDate:
          type: string
        sendReminders:
          "$ref": "#/components/schemas/ExtractionCodeSendRemindersDto"
        metadata:
          type: object
        watchlist:
          type: boolean
          default: false
        ocRequired:
          type: boolean
          default: true
          description: 'OC cases only: when false, the OL creates/keeps this code
            without the opposing-council approval gate (code stays `na`, immediately
            usable, and the custodian email is sent). Ignored on non-OC cases. Defaults
            to true (gate on).'
        paymentRequired:
          type: boolean
          description: Whether payment is required for this extraction code
        paymentAmount:
          type: number
          description: Payment amount in cents
        paymentCurrency:
          type: string
          description: Payment currency (defaults to usd)
          default: usd
        paymentSuccessRedirectUrl:
          type: string
          description: Payment success redirect URL
        productName:
          type: string
          description: Product name
        prepaidItems:
          description: Prepaid basket. Each entry becomes its own Stripe line item
            and grants units on payment. Prices are negotiable — unitAmountCents is
            what the customer is charged, and is never validated against the catalog
            default.
          type: array
          items:
            "$ref": "#/components/schemas/PrepaidItemDto"
        caseId:
          type: string
        extractionCodeId:
          type: string
          description: required if code is not provided
        code:
          type: string
          description: required if extractionCodeId is not provided
      required:
      - dueDate
      - caseId
    CreateBulkExtractionCodeDto:
      type: object
      properties:
        createIfEvenExists:
          type: boolean
    RerunExportDto:
      type: object
      properties:
        exportId:
          type: string
      required:
      - exportId
    RerunExportsResponseEntity:
      type: object
      properties:
        batchId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f2
        totalCount:
          type: number
          example: 3
        invalidCount:
          type: number
          example: 0
      required:
      - batchId
      - totalCount
      - invalidCount
    RerunExportsDto:
      type: object
      properties:
        exportIds:
          type: array
          items:
            type: string
      required:
      - exportIds
    CaseExportRerunBatchItemEntity:
      type: object
      properties:
        export:
          type: string
        status:
          type: string
          enum:
          - pending
          - running
          - completed
          - failed
          - timeout
          - cancelled
          - invalid
        startedAt:
          format: date-time
          type: string
          nullable: true
        finishedAt:
          format: date-time
          type: string
          nullable: true
        errorMessage:
          type: string
          nullable: true
      required:
      - export
      - status
    CaseExportRerunBatchEntity:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
          enum:
          - queued
          - in-progress
          - completed
        totalCount:
          type: number
        successCount:
          type: number
        failureCount:
          type: number
        items:
          type: array
          items:
            "$ref": "#/components/schemas/CaseExportRerunBatchItemEntity"
        startedAt:
          format: date-time
          type: string
          nullable: true
        completedAt:
          format: date-time
          type: string
          nullable: true
      required:
      - id
      - status
      - totalCount
      - successCount
      - failureCount
      - items
    FetchMessagesCountResponseEntity:
      type: object
      properties:
        messagesCount:
          type: number
          description: The number of messages
          example: 100
        conversationsCount:
          type: number
          description: The number of conversations
          example: 100
        exportMessagesCount:
          type: number
          description: The number of conversations
          example: 100
      required:
      - messagesCount
      - conversationsCount
      - exportMessagesCount
    MoveCaseToOrgDto:
      type: object
      properties:
        caseId:
          type: string
          description: The case ID to move
        targetOrgId:
          type: string
          description: The target organization ID
        emails:
          description: The emails to move
          type: array
          items:
            type: string
      required:
      - caseId
      - targetOrgId
      - emails
    MoveDeviceToCaseDto:
      type: object
      properties:
        deviceId:
          type: string
          description: The device ID to move
        sourceCaseId:
          type: string
          description: The source case ID (where the device currently is)
        targetCaseId:
          type: string
          description: The target case ID (where to move the device)
        dryRun:
          type: boolean
          description: If true, validate and report what would change without writing
          default: false
      required:
      - deviceId
      - sourceCaseId
      - targetCaseId
    AdminDeleteDiscordProfileDto:
      type: object
      properties:
        discordProviderId:
          type: string
          description: DiscordUserProvider ID
        forceDelete:
          type: boolean
          description: If true, permanently deletes all data. If false/omitted, soft-deletes
            (archives).
      required:
      - discordProviderId
    AdminDeleteRedditProfileDto:
      type: object
      properties:
        redditProviderId:
          type: string
          description: RedditUserProvider ID
        forceDelete:
          type: boolean
          description: If true, permanently deletes all data. If false/omitted, soft-deletes
            (archives).
      required:
      - redditProviderId
    OrganizationDirectoryUserEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e4
        name:
          type: string
          example: Jane Doe
        email:
          type: string
          example: jane.doe@example.com
        role:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
          example: admin
        isActive:
          type: boolean
          example: true
      required:
      - id
      - name
      - email
      - role
      - isActive
    OrganizationDirectoryEntryEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          example: Example Firm
        slug:
          type: string
          example: example-firm
        isActive:
          type: boolean
          example: true
        users:
          description: Members of this organization, excluding claimable placeholder
            accounts and memberships whose user no longer exists. An empty array therefore
            means "no listable members", not necessarily "no memberships".
          type: array
          items:
            "$ref": "#/components/schemas/OrganizationDirectoryUserEntity"
        membershipCount:
          type: number
          description: Total memberships for this organization, counted before truncation
            and before claimable/deleted users are filtered out. Always >= users.length.
          example: 12
        hasMoreUsers:
          type: boolean
          description: True when this organization has more memberships than the response
            cap and `users` was truncated. For the full member list use GET /admin/user?organizationId=<id>&skip=0&limit=100
            — page it with skip/limit rather than page/perPage, and note that endpoint
            does not exclude claimable accounts.
          example: false
      required:
      - createdAt
      - updatedAt
      - id
      - name
      - slug
      - isActive
      - users
      - membershipCount
      - hasMoreUsers
    OrganizationStorageStatsItemEntity:
      type: object
      properties:
        organizationName:
          type: string
          example: Example Firm
        organizationId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e3
        totalUsagesInBytes:
          type: number
          example: 104857600
        cases:
          type: array
          items:
            "$ref": "#/components/schemas/OrganizationStorageStatsCaseEntity"
      required:
      - organizationName
      - organizationId
      - totalUsagesInBytes
      - cases
    AddAllUsersToAllOrgCasesDto:
      type: object
      properties:
        orgId:
          type: string
        caseAccessLevel:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
          default: commenter
      required:
      - orgId
      - caseAccessLevel
    AddUserToAllOrgCasesDto:
      type: object
      properties:
        orgId:
          type: string
        caseAccessLevel:
          type: string
          enum:
          - owner
          - admin
          - commenter
          - viewer
          default: commenter
        email:
          type: string
          description: Email of an existing organization member
      required:
      - orgId
      - caseAccessLevel
      - email
    AddNewUserToOrgDto:
      type: object
      properties:
        email:
          type: string
        name:
          type: string
        invitedUserRole:
          type: string
          enum:
          - user
          - client
          - employee
          - billing
          - full_access
          - admin
          - owner
          - member
          default: member
        orgId:
          type: string
          description: required if orgName not sent
        orgName:
          type: string
          description: required if orgId not sent
        password:
          type: string
          description: If not set a random password is set
        sendResetPasswordEmail:
          type: boolean
          default: true
      required:
      - email
      - name
    RemoveUserFromOrgDto:
      type: object
      properties:
        email:
          type: string
          description: required if userId not sent
        userId:
          type: string
          description: required if email not sent
        orgId:
          type: string
          description: required if orgName not sent
        orgName:
          type: string
          description: required if orgId not sent
        deleteUser:
          type: boolean
          default: false
    TransferOrgOwnershipDto:
      type: object
      properties:
        currentOwnerEmail:
          type: string
          description: Email of the current owner
        newOwnerEmail:
          type: string
          description: Email of the new owner
      required:
      - currentOwnerEmail
      - newOwnerEmail
    UpdateAdminOrganizationDto:
      type: object
      properties:
        name:
          type: string
        website:
          type: string
        logo:
          type: string
        contact:
          type: string
        addressLine1:
          type: string
        addressLine2:
          type: string
        city:
          type: string
        state:
          type: string
        country:
          type: string
        zipCode:
          type: string
        email:
          type: string
        description:
          type: string
        phone:
          type: array
          items:
            type: string
            maximum: 20
            minimum: 1
        metadata:
          type: object
        version:
          type: string
          enum:
          - v1
          - v2
          - eu
          - ca
    SetCacheValueDto:
      type: object
      properties:
        value:
          type: object
        ttl:
          type: number
      required:
      - value
    RegisterResponseEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        accessToken:
          type: string
          example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2NWExYjJjMyJ9.c2lnbmF0dXJl
        userProfile:
          "$ref": "#/components/schemas/UserEntity"
        organizationInfo:
          "$ref": "#/components/schemas/OrganizationDetailsEntity"
      required:
      - createdAt
      - updatedAt
      - id
      - accessToken
      - userProfile
    AddUserDto:
      type: object
      properties:
        email:
          type: string
        password:
          type: string
        name:
          type: string
          maxLength: 200
          minLength: 3
        organizationName:
          type: string
        inviteEmails:
          type: array
          items:
            type: array
        phone:
          type: array
          items:
            type: string
            maximum: 20
            minimum: 1
        userType:
          type: string
        termsAccepted:
          type: boolean
        emailVerified:
          type: boolean
        claimable:
          type: boolean
      required:
      - email
      - password
      - name
      - userType
    SetUserPasswordDto:
      type: object
      properties:
        email:
          type: string
          description: required if userId not sent
        userId:
          type: string
          description: required if email not sent
        password:
          type: string
      required:
      - password
    SendPasswordResetEmailDto:
      type: object
      properties:
        email:
          type: string
          description: required if userId not sent
        userId:
          type: string
          description: required if email not sent
    LoginAsDto:
      type: object
      properties:
        email:
          type: string
      required:
      - email
    DeleteUserAccountDto:
      type: object
      properties:
        email:
          type: string
      required:
      - email
    UpdateEmailDto:
      type: object
      properties:
        oldEmail:
          type: string
        newEmail:
          type: string
      required:
      - oldEmail
      - newEmail
    LockUserByEmailDto:
      type: object
      properties:
        email:
          type: string
      required:
      - email
    UnlockUserByEmailDto:
      type: object
      properties:
        email:
          type: string
      required:
      - email
    FetchDeviceUploadReportDto:
      type: object
      properties:
        startDate:
          type: string
        endDate:
          type: string
    StatCountResponseEntity:
      type: object
      properties:
        totalSharedMessages:
          type: number
          example: 1250000
        totalMessages:
          type: number
          example: 4800000
        totalWhatsappMessages:
          type: number
          example: 900000
        totalVoicemailMessages:
          type: number
          example: 1200
        totalCallLogs:
          type: number
          example: 86000
        totalContacts:
          type: number
          example: 410000
        totalDevices:
          type: number
          example: 3200
        totalOrganizations:
          type: number
          example: 640
      required:
      - totalSharedMessages
      - totalMessages
      - totalWhatsappMessages
      - totalVoicemailMessages
      - totalCallLogs
      - totalContacts
      - totalDevices
      - totalOrganizations
    NewExtractionsReportResponseEntity:
      type: object
      properties:
        data:
          type: array
          items:
            type: string
        total:
          type: number
          example: 18
      required:
      - data
      - total
    FetchNewDevicesReportDto:
      type: object
      properties: {}
    OrganizationReportEntity:
      type: object
      properties:
        organization:
          "$ref": "#/components/schemas/OrganizationEntity"
        otherData:
          type: object
          example:
            accountAgeInDays: 420
            totalCases: 12
            totalExtractionCodes: 30
            unusedExtractionCodes: 4
            totalUniqueDevices: 22
      required:
      - organization
      - otherData
    ExtractionStatusItemEntity:
      type: object
      properties:
        extractionCode:
          type: string
          example: AB12CD
        extractionCodeId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e8
        sourceId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e9
        status:
          type: string
          enum:
          - pending
          - in_progress
          - staged
          - completed
          - failed
          example: in_progress
        sourceType:
          type: string
          example: device
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
        lastStatusChangedAt:
          format: date-time
          type: string
          example: '2026-01-15T11:00:00.000Z'
        organization:
          type: string
          example: Example Firm
        organizationEmail:
          type: string
          example: admin@example.com
        watchlist:
          type: boolean
          example: false
      required:
      - extractionCode
      - extractionCodeId
      - sourceId
      - status
      - sourceType
      - createdAt
      - lastStatusChangedAt
      - organization
      - organizationEmail
      - watchlist
    FetchUserEmailDataProviderReportDto:
      type: object
      properties:
        startDate:
          type: string
        endDate:
          type: string
        extractionCode:
          type: string
        caseId:
          type: string
    PhoneDeviceStorageLogItemEntity:
      type: object
      properties:
        _id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f3
        deviceId:
          type: string
          example: '00008110-001A2B3C4D5E6F70'
        osVersion:
          type: string
          nullable: true
          example: '17.4'
        phoneHardwareVersion:
          type: string
          nullable: true
          example: iPhone15,2
        phoneOtherInfo:
          type: object
          nullable: true
        calculatedTotalSpaceUsed:
          type: number
          nullable: true
          example: 96000000000
        userReportedTotalSpaceUsed:
          type: number
          nullable: true
          example: 98000000000
        calculatedTotalDiskSpace:
          type: number
          nullable: true
          example: 128000000000
        userReportedTotalDiskSpace:
          type: number
          nullable: true
          example: 128000000000
        otherStorageMetadata:
          type: object
          nullable: true
        oldData:
          type: array
          items:
            type: object
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
      required:
      - _id
      - deviceId
      - osVersion
      - phoneHardwareVersion
      - phoneOtherInfo
      - calculatedTotalSpaceUsed
      - userReportedTotalSpaceUsed
      - calculatedTotalDiskSpace
      - userReportedTotalDiskSpace
      - otherStorageMetadata
      - oldData
      - createdAt
      - updatedAt
    NotificationThreadEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        notificationTs:
          type: string
        channelName:
          type: string
        deviceId:
          type: string
        deviceType:
          type: string
        email:
          type: string
        case:
          "$ref": "#/components/schemas/CaseEntity"
        organization:
          "$ref": "#/components/schemas/OrganizationEntity"
        extractionCode:
          "$ref": "#/components/schemas/ExtractionCodeEntity"
        device:
          "$ref": "#/components/schemas/DeviceEntity"
        channelType:
          type: string
          enum:
          - account_management
          - backup
          - import
          - backup_logs
          - export
          - desktop_client
          - email_logs
          - other
          - case
        ecCode:
          type: string
        systemId:
          type: string
        connectedChannels:
          type: object
          default:
      required:
      - createdAt
      - updatedAt
      - id
      - notificationTs
      - channelName
      - deviceId
      - deviceType
      - email
      - case
      - organization
      - extractionCode
      - device
      - channelType
      - ecCode
      - systemId
      - connectedChannels
    DesktopClientEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        systemId:
          type: string
        ecCodes:
          type: array
          items:
            type: string
        extractionCodes:
          description: Ids of the extraction codes used on this computer
          example:
          - 65a1b2c3d4e5f6a7b8c9d0e1
          type: array
          items:
            type: string
        isOnline:
          type: boolean
        lastPingAt:
          format: date-time
          type: string
        lastSeenIp:
          type: string
        lastSeenLocation:
          type: string
        lastUsedAppVersion:
          type: string
        osDetails:
          "$ref": "#/components/schemas/OSDetailsEntity"
        roomName:
          type: string
        connectedDevices:
          description: Ids of the devices connected to this computer
          example:
          - 65a1b2c3d4e5f6a7b8c9d0e2
          type: array
          items:
            type: string
        latestState:
          type: string
      required:
      - createdAt
      - updatedAt
      - id
      - systemId
      - ecCodes
      - extractionCodes
      - isOnline
      - lastPingAt
      - lastSeenIp
      - lastSeenLocation
      - lastUsedAppVersion
      - osDetails
      - roomName
      - connectedDevices
      - latestState
    NotificationEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        notificationThreadTs:
          type: string
        blocks:
          type: object
        mainNotificationThread:
          "$ref": "#/components/schemas/NotificationThreadEntity"
        case:
          "$ref": "#/components/schemas/CaseEntity"
        organization:
          "$ref": "#/components/schemas/OrganizationEntity"
        device:
          "$ref": "#/components/schemas/DeviceEntity"
        client:
          "$ref": "#/components/schemas/DesktopClientEntity"
        deviceId:
          type: string
        deviceType:
          type: string
        texts:
          default: []
          type: array
          items:
            type: string
        links:
          default: []
          type: array
          items:
            type: string
        channelName:
          type: string
        otherData:
          type: object
        systemId:
          type: string
        source:
          "$ref": "#/components/schemas/MasterDataSourceEntity"
      required:
      - createdAt
      - updatedAt
      - id
      - notificationThreadTs
      - blocks
      - mainNotificationThread
      - case
      - organization
      - device
      - client
      - deviceId
      - deviceType
      - texts
      - links
      - channelName
      - otherData
      - systemId
      - source
    UpdateLatestSubscriptionForOrgDto:
      type: object
      properties:
        orgId:
          type: string
          description: required if orgOwnerEmail not sent
        orgOwnerEmail:
          type: string
          description: required if orgId not sent
    UpdateOrgPlanDto:
      type: object
      properties:
        orgId:
          type: string
          description: required if orgOwnerEmail not sent
        orgOwnerEmail:
          type: string
          description: required if orgId not sent
        productId:
          type: string
      required:
      - productId
    TagMigrationCollectionStatusEntity:
      type: object
      properties:
        name:
          type: string
          example: devicemessages
        total:
          type: number
          example: 50000
        processed:
          type: number
          example: 12000
        skipped:
          type: number
          example: 40
        errored:
          type: number
          example: 0
        startedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:15:00.000Z'
        completedAt:
          format: date-time
          type: string
          nullable: true
          example:
      required:
      - name
      - total
      - processed
      - skipped
      - errored
    TagMigrationStatusEntity:
      type: object
      properties:
        jobId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f2
        status:
          type: string
          example: running
        collections:
          type: array
          items:
            "$ref": "#/components/schemas/TagMigrationCollectionStatusEntity"
        lastError:
          type: string
          nullable: true
          example:
      required:
      - jobId
      - status
      - collections
    TriggerTagMigrationDto:
      type: object
      properties:
        collections:
          description: Specific collections to migrate. Empty/omitted = all 20.
          type: array
          items:
            type: string
        batchSize:
          type: number
          default: 1000
        dryRun:
          type: boolean
          default: false
    BackfillTagSourcesDto:
      type: object
      properties:
        caseId:
          type: string
          description: Restrict the backfill to a single case. Omit to backfill all
            cases.
    FixSearchTagAttributionDto:
      type: object
      properties:
        providerId:
          type: string
          description: Restrict the fix to a single email provider (mailbox) on the
            case.
    DeleteCaseFilesResponseEntity:
      type: object
      properties:
        matched:
          type: number
          description: Case files in scope, including ones already deleted
          example: 4
        deleted:
          type: number
          description: Case files soft-deleted by this call (0 on a retry)
          example: 4
      required:
      - matched
      - deleted
    DeleteCaseFilesDto:
      type: object
      properties:
        caseFileIds:
          description: CaseFile ids to delete (max 1000). Every id must be an uploaded
            file on the case (not a CMP-synced legal document). Omit when sending
            `all`.
          type: array
          items:
            type: string
        all:
          type: boolean
          enum:
          - true
          description: Delete every uploaded case file on the case. Omit when sending
            `caseFileIds`.
    AdminBillingProfileResponseEntity:
      type: object
      properties:
        organization:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e3
        tier:
          type: string
          enum:
          - public
          - solo
          - firm
          - firm-plus
          example: firm
        billingMode:
          type: string
          enum:
          - payg
          - per-source
          - flat-case
          example: payg
        cadence:
          type: string
          enum:
          - monthly
          - annual
          example: monthly
        billingEmail:
          type: string
          nullable: true
          example: billing@example.com
        paymentMethodOnFile:
          type: boolean
          example: true
        chargeTrigger:
          type: string
          enum:
          - backup
          - import
          nullable: true
          description: When this org's phone collections are charged. `null` means
            it follows the platform-wide flag rather than pinning a value.
          example:
        overrides:
          type: object
          nullable: true
          example:
            storageIncludedGB: 100
            skuUnitAmountCents:
              collection.phone: 10000
        warnings:
          description: Non-blocking notices, e.g. a per-SKU override the tier/mode
            already includes
          example: []
          type: array
          items:
            type: string
      required:
      - organization
      - tier
      - billingMode
      - cadence
      - billingEmail
      - paymentMethodOnFile
      - chargeTrigger
      - overrides
      - warnings
    AdminBillingProfileOverridesDto:
      type: object
      properties:
        storageIncludedGB:
          type: number
        skuUnitAmountCents:
          type: object
          description: SKU key -> cents. 0 = free for this org; null = remove the
            override and fall back to the catalog/tier price.
        perCaseSourceCap:
          type: number
          description: Provisioned only — NOT enforced in v1 (spec locked decision);
            enforcement is a later phase
    UpdateAdminBillingProfileDto:
      type: object
      properties:
        tier:
          type: string
          enum:
          - public
          - solo
          - firm
          - firm-plus
        billingMode:
          type: string
          enum:
          - payg
          - per-source
          - flat-case
        cadence:
          type: string
          enum:
          - monthly
          - annual
        billingEmail:
          type: string
        paymentMethodOnFile:
          type: boolean
        chargeTrigger:
          type: string
          enum:
          - backup
          - import
          nullable: true
          description: When this org's phone collections are charged. `null` clears
            the override and returns the org to the platform-wide flag; `import` PINS
            it to import-time charging even after that flag flips.
        overrides:
          "$ref": "#/components/schemas/AdminBillingProfileOverridesDto"
        reason:
          type: string
          description: Required audit reason — persisted on the BillingEvent
      required:
      - reason
    AdminOrgBillingMigrationResponseEntity:
      type: object
      properties:
        profileCreated:
          type: boolean
          description: Always true — the org is now on case billing
          example: true
        paymentMethodOnFile:
          type: boolean
          description: Whether the org had a Stripe default payment method
          example: true
        legacySubscriptionCanceled:
          type: boolean
          description: Whether a live legacy Stripe subscription was canceled by this
            migration
          example: true
      required:
      - profileCreated
      - paymentMethodOnFile
      - legacySubscriptionCanceled
    MigrateOrgBillingDto:
      type: object
      properties:
        tier:
          type: string
          enum:
          - public
          - solo
          - firm
          - firm-plus
        billingMode:
          type: string
          enum:
          - payg
          - per-source
          - flat-case
        cadence:
          type: string
          enum:
          - monthly
          - annual
        overrides:
          "$ref": "#/components/schemas/AdminBillingProfileOverridesDto"
        billingEmail:
          type: string
      required:
      - tier
      - billingMode
      - cadence
    AdminBillingItemResponseEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e9
        caseId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        skuKey:
          type: string
          example: addon.shipping
        status:
          type: string
          example: PENDING
        description:
          type: string
          example: 'Admin add-on: addon.shipping'
        quantity:
          type: number
          example: 1
        unitAmountCents:
          type: number
          example: 2500
        amountCents:
          type: number
          example: 2500
        informational:
          type: boolean
          example: false
        listAmountCents:
          type: number
          nullable: true
          example:
        adminNote:
          type: string
          nullable: true
          example:
      required:
      - id
      - caseId
      - skuKey
      - status
      - description
      - quantity
      - unitAmountCents
      - amountCents
      - informational
      - listAmountCents
      - adminNote
    CreateAdminBillingItemDto:
      type: object
      properties:
        skuKey:
          type: string
          enum:
          - collection.phone
          - collection.social
          - collection.email
          - collection.ai-chat
          - collection.financial
          - collection.discord
          - collection.reddit
          - collection.linkedin
          - collection.legal
          - collection.file
          - collection.other
          - case.flat
          - case.pro-upgrade
          - addon.kit-rental
          - addon.laptop-rental
          - addon.laptop-rental-setup
          - addon.managed-service
          - addon.shipping
          - addon.live-bank
          - plan.base
          - source.per
          - ai.tokens-overage
          - adjustment.manual
          - adjustment.discount
        quantity:
          type: number
          minimum: 1
        requestId:
          type: string
          description: Client-supplied dedup token — repeating the same requestId
            for the same case+SKU returns the existing row untouched
        reason:
          type: string
          description: Required audit reason — persisted on the BillingEvent
        comped:
          type: boolean
          description: Create the row already COMPED (never swept/invoiced) instead
            of billable
        unitAmountCentsOverride:
          type: number
          description: Per-item price override — only honored for addon.shipping/adjustment.manual
            (negative allowed on adjustment.manual only)
      required:
      - skuKey
      - quantity
      - requestId
      - reason
    AdminProUpgradeDto:
      type: object
      properties:
        comped:
          type: boolean
          enum:
          - true
        reason:
          type: string
          description: Required audit reason — persisted on the BillingEvent
      required:
      - comped
      - reason
    AdminBillingItemActionDto:
      type: object
      properties:
        reason:
          type: string
          description: Required audit reason — persisted as adminNote + on the BillingEvent
      required:
      - reason
    AdminTokenGrantResponseEntity:
      type: object
      properties:
        caseId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        granted:
          type: number
          example: 1000000
        allowance:
          type: number
          example: 3000000
        used:
          type: number
          example: 250000
      required:
      - caseId
      - granted
      - allowance
      - used
    GrantAiTokensDto:
      type: object
      properties:
        caseId:
          type: string
          description: Id of the case to grant tokens on
        tokens:
          type: number
          minimum: 1
        reason:
          type: string
          description: Required audit reason — persisted on the BillingEvent
        requestId:
          type: string
          description: Client-supplied dedup token — repeating the same requestId
            for the same case returns the prior grant untouched instead of granting
            a second time. Requires the organization to already have a BillingProfile
            to be enforced.
      required:
      - caseId
      - tokens
      - reason
    AdminPlanChangeResponseEntity:
      type: object
      properties:
        plan:
          "$ref": "#/components/schemas/SelectedPlanResponseEntity"
        prorationCents:
          type: number
          description: Net cents charged (positive) or credited (negative) now to
            reconcile the base fee on a plan change; 0 on a first-time plan assignment.
          example: 0
        paymentRequired:
          type: boolean
          description: 'Informational only: true if the org''s (new) tier is paid
            and it has no card on file yet. Staff cannot capture a card here — the
            org must add one via its own self-serve flow.'
          example: false
      required:
      - plan
      - prorationCents
      - paymentRequired
    AdminChangePlanDto:
      type: object
      properties:
        tier:
          type: string
          enum:
          - public
          - solo
          - firm
          - firm-plus
        cadence:
          type: string
          enum:
          - monthly
          - annual
      required:
      - tier
    GrantCreditDto:
      type: object
      properties:
        amountCents:
          type: number
          minimum: 1
          description: Credit to add, in integer cents
        reason:
          type: string
          description: Required audit reason — persisted on the ledger
        requestId:
          type: string
          description: Client-supplied dedup token — repeating the same requestId
            returns the prior grant instead of double-crediting
      required:
      - amountCents
      - reason
    PrepaidProfileStateResponseEntity:
      type: object
      properties:
        enabled:
          type: boolean
          description: Whether this org has prepaid settlement at all
          example: true
        allowUnpaid:
          type: boolean
          description: 'Support override: lets collections through unpaid'
          example: false
        allowUnpaidReason:
          type: string
          nullable: true
          description: Required when allowUnpaid is true; null otherwise
          example:
      required:
      - enabled
      - allowUnpaid
      - allowUnpaidReason
    PrepaidBalanceResponseEntity:
      type: object
      properties:
        skuKey:
          type: string
          enum:
          - collection.phone
          - collection.social
          - collection.email
          - collection.ai-chat
          - collection.financial
          - collection.discord
          - collection.reddit
          - collection.linkedin
          - collection.legal
          - collection.file
          - collection.other
          - case.flat
          - case.pro-upgrade
          - addon.kit-rental
          - addon.laptop-rental
          - addon.laptop-rental-setup
          - addon.managed-service
          - addon.shipping
          - addon.live-bank
          - plan.base
          - source.per
          - ai.tokens-overage
          - adjustment.manual
          - adjustment.discount
          example: collection.phone
        unitsRemaining:
          type: number
          example: 3
      required:
      - skuKey
      - unitsRemaining
    PrepaidLedgerEntryResponseEntity:
      type: object
      properties:
        skuKey:
          type: string
          enum:
          - collection.phone
          - collection.social
          - collection.email
          - collection.ai-chat
          - collection.financial
          - collection.discord
          - collection.reddit
          - collection.linkedin
          - collection.legal
          - collection.file
          - collection.other
          - case.flat
          - case.pro-upgrade
          - addon.kit-rental
          - addon.laptop-rental
          - addon.laptop-rental-setup
          - addon.managed-service
          - addon.shipping
          - addon.live-bank
          - plan.base
          - source.per
          - ai.tokens-overage
          - adjustment.manual
          - adjustment.discount
          example: collection.phone
        deltaUnits:
          type: number
          description: Positive for a grant, negative for a consume/revoke
          example: 5
        balanceAfterUnits:
          type: number
          example: 5
        balanceApplied:
          type: boolean
          description: 'False means the row is RESERVED: its ledger append landed
            but its balance move has not. `balanceAfterUnits` is a placeholder 0 on
            such a row.'
          example: true
        reason:
          type: string
          enum:
          - grant
          - consume
          - revoke
          - restore
          - admin_grant
          example: grant
        description:
          type: string
          example: Purchased 5 phone collections
        productKey:
          type: string
          nullable: true
          example:
        quantity:
          type: number
          nullable: true
          example: 5
        amountPaidCents:
          type: number
          nullable: true
          description: Integer cents
          example: 50000
        currency:
          type: string
          example: usd
        sourceCase:
          type: string
          nullable: true
          example:
        sourceExtractionCode:
          type: string
          nullable: true
          example:
        stripeChargeId:
          type: string
          nullable: true
          example: ch_3Q2w3E4r5T6y7U8i
        createdBy:
          type: string
          nullable: true
          example:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
      required:
      - skuKey
      - deltaUnits
      - balanceAfterUnits
      - balanceApplied
      - reason
      - description
      - productKey
      - quantity
      - amountPaidCents
      - currency
      - sourceCase
      - sourceExtractionCode
      - stripeChargeId
      - createdBy
      - createdAt
    PrepaidStateResponseEntity:
      type: object
      properties:
        prepaid:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/PrepaidProfileStateResponseEntity"
        balances:
          type: array
          items:
            "$ref": "#/components/schemas/PrepaidBalanceResponseEntity"
        ledger:
          type: array
          items:
            "$ref": "#/components/schemas/PrepaidLedgerEntryResponseEntity"
      required:
      - prepaid
      - balances
      - ledger
    UpdatePrepaidOverrideDto:
      type: object
      properties:
        allowUnpaid:
          type: boolean
          description: Waive the prepaid unit requirement for this org when true;
            setting it back to false also clears the stored reason
        reason:
          type: string
          description: Required audit reason — persisted on the profile while the
            override is set and shown on the statement. The override lets collections
            through unpaid, so an unexplained one is indistinguishable from a bug
            six months later.
      required:
      - allowUnpaid
      - reason
    AdminBillingSweepRunResponseEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f0
        startedAt:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
        finishedAt:
          format: date-time
          type: string
          example: '2026-01-15T10:20:00.000Z'
        durationMs:
          type: number
          example: 300000
        status:
          type: string
          enum:
          - succeeded
          - failed
          example: succeeded
        manual:
          type: boolean
          description: true for an admin-triggered run, false for the nightly cron
          example: false
        organizationId:
          type: string
          nullable: true
          description: Set when the run was scoped to one org
          example:
        invoicesCreated:
          type: number
          example: 3
        totalCents:
          type: number
          example: 45000
        groupsFailed:
          type: number
          example: 0
        stuckReset:
          type: number
          example: 0
        baseFeeRowsCreated:
          type: number
          example: 2
        recurringAddonRowsCreated:
          type: number
          example: 1
        failedGroups:
          description: Which group failed and why — the detail behind groupsFailed
          example: []
          type: array
          items:
            type: string
        error:
          type: string
          nullable: true
          description: Set only when the sweep itself threw
          example:
      required:
      - id
      - startedAt
      - finishedAt
      - durationMs
      - status
      - manual
      - organizationId
      - invoicesCreated
      - totalCents
      - groupsFailed
      - stuckReset
      - baseFeeRowsCreated
      - recurringAddonRowsCreated
      - failedGroups
      - error
    TriggerBillingSweepDto:
      type: object
      properties:
        asOf:
          type: string
          description: 'ISO date the sweep bills for (default: now)'
        organizationId:
          type: string
          description: 'Scope the sweep to one organization (default: all)'
    CloseBillingMonthDto:
      type: object
      properties:
        yearMonth:
          type: string
          description: 'UTC month YYYY-MM (default: current month)'
        organizationId:
          type: string
          description: 'Scope the close to one organization (default: all)'
    TriggerReconciliationDto:
      type: object
      properties:
        asOf:
          type: string
          description: 'ISO date to reconcile as of (default: now)'
    AdminCouponResponseEntity:
      type: object
      properties:
        couponId:
          type: string
          description: Stripe coupon id — the redeemable code
          example: SPRING20
        name:
          type: string
          nullable: true
          example: Spring 20% off
        percentOff:
          type: number
          nullable: true
          example: 20
        amountOffCents:
          type: number
          nullable: true
          example:
        currency:
          type: string
          nullable: true
          example:
        duration:
          type: string
          enum:
          - once
          - repeating
          - forever
          example: repeating
        durationInMonths:
          type: number
          nullable: true
          example: 3
        maxRedemptions:
          type: number
          nullable: true
          example: 50
        redeemBy:
          format: date-time
          type: string
          nullable: true
          example: '2026-06-30T23:59:59.000Z'
        timesRedeemed:
          type: number
          example: 4
        status:
          type: string
          enum:
          - active
          - expired
          - deleted
          description: Derived from deletedAt/redeemBy at read time, never stored
          example: active
        reason:
          type: string
          nullable: true
          example: Spring promotion approved by sales
        deletedAt:
          format: date-time
          type: string
          nullable: true
          example:
      required:
      - couponId
      - name
      - percentOff
      - amountOffCents
      - currency
      - duration
      - durationInMonths
      - maxRedemptions
      - redeemBy
      - timesRedeemed
      - status
      - reason
      - deletedAt
    CreateCouponDto:
      type: object
      properties:
        couponId:
          type: string
          description: Stripe coupon id — THIS IS THE REDEEMABLE CODE customers type.
            Stripe generates a random one when omitted.
        name:
          type: string
          description: Human-readable name shown on the invoice
        percentOff:
          type: number
          description: Percent discount, 1-100. Mutually exclusive with amountOffCents.
        amountOffCents:
          type: number
          description: Fixed discount in cents. Requires currency.
        currency:
          type: string
          description: ISO currency, required with amountOffCents
          example: usd
        duration:
          type: string
          enum:
          - once
          - repeating
          - forever
        durationInMonths:
          type: number
          description: Months to repeat for — required when duration is "repeating"
        maxRedemptions:
          type: number
          description: Total redemptions allowed across all customers
        redeemBy:
          format: date-time
          type: string
          description: Cannot be applied after this instant (ISO 8601)
        appliesToProducts:
          description: Restrict the discount to these Stripe product ids
          type: array
          items:
            type: string
        metadata:
          type: object
        reason:
          type: string
          description: Audit note — why this coupon was issued
      required:
      - duration
    AdminCouponListResponseEntity:
      type: object
      properties:
        coupons:
          type: array
          items:
            "$ref": "#/components/schemas/AdminCouponResponseEntity"
        total:
          type: number
          description: Total matching the filter, ignoring limit/skip
          example: 1
      required:
      - coupons
      - total
    AdminCouponStackEntryResponseEntity:
      type: object
      properties:
        couponId:
          type: string
          example: SPRING20
        pushedAt:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
        name:
          type: string
          nullable: true
          example: Spring 20% off
        percentOff:
          type: number
          nullable: true
          example: 20
        amountOffCents:
          type: number
          nullable: true
          example:
        duration:
          type: string
          nullable: true
          example: repeating
        status:
          type: string
          description: active/expired/deleted, or "unknown" when the coupon is missing
            from the local mirror
          example: active
        eligible:
          type: boolean
          description: Whether this entry could be the active discount
          example: true
        active:
          type: boolean
          description: The ONE entry currently mirrored onto the Stripe customer —
            the top eligible coupon
          example: true
      required:
      - couponId
      - pushedAt
      - name
      - percentOff
      - amountOffCents
      - duration
      - status
      - eligible
      - active
    AdminCouponStackResponseEntity:
      type: object
      properties:
        stack:
          description: Top of the stack first
          type: array
          items:
            "$ref": "#/components/schemas/AdminCouponStackEntryResponseEntity"
        activeCouponId:
          type: string
          nullable: true
          description: The coupon currently discounting this org, if any
          example: SPRING20
      required:
      - stack
      - activeCouponId
    ApplyCouponDto:
      type: object
      properties:
        couponId:
          type: string
          description: Stripe coupon id (the redeemable code)
      required:
      - couponId
    ReleaseDraftBillingItemDto:
      type: object
      properties:
        reason:
          type: string
    BillingReportStatusResponseEntity:
      type: object
      properties:
        reportId:
          type: string
        status:
          type: string
          enum:
          - QUEUED
          - PROCESSING
          - COMPLETED
          - FAILED
        downloadUrl:
          type: string
          nullable: true
        error:
          type: string
          nullable: true
        skippedOrganizations:
          type: array
          items:
            type: string
      required:
      - reportId
      - status
      - skippedOrganizations
    AdminBillingReportQueryDto:
      type: object
      properties:
        caseIds:
          description: Defaults to all cases
          type: array
          items:
            type: string
        startDate:
          format: date-time
          type: string
          description: Defaults to the start of the organization's current billing
            cadence period
        endDate:
          format: date-time
          type: string
          description: Defaults to now
        format:
          type: string
          enum:
          - json
          - pdf
          default: json
        orgIds:
          description: Defaults to every organization that has a BillingProfile (i.e.
            is on case billing)
          type: array
          items:
            type: string
    AdminRecurringAddonResponseEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f1
        organization:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e3
        case:
          type: string
          nullable: true
          example:
        skuKey:
          type: string
          enum:
          - collection.phone
          - collection.social
          - collection.email
          - collection.ai-chat
          - collection.financial
          - collection.discord
          - collection.reddit
          - collection.linkedin
          - collection.legal
          - collection.file
          - collection.other
          - case.flat
          - case.pro-upgrade
          - addon.kit-rental
          - addon.laptop-rental
          - addon.laptop-rental-setup
          - addon.managed-service
          - addon.shipping
          - addon.live-bank
          - plan.base
          - source.per
          - ai.tokens-overage
          - adjustment.manual
          - adjustment.discount
          example: addon.laptop-rental
        recurrence:
          type: string
          enum:
          - monthly
          - semiannual
          example: monthly
        startedAt:
          format: date-time
          type: string
          description: Period anchor as well as the start date — every period falls
            on this day-of-month, not the 1st
          example: '2026-01-15T10:15:00.000Z'
        canceledAt:
          format: date-time
          type: string
          nullable: true
          example:
        status:
          type: string
          enum:
          - active
          - canceled
          example: active
        externalRef:
          type: string
          description: Caller-supplied dedup token
          example: rental-2026-001
      required:
      - id
      - organization
      - case
      - skuKey
      - recurrence
      - startedAt
      - canceledAt
      - status
      - externalRef
    ActivateRecurringAddonDto:
      type: object
      properties:
        skuKey:
          type: string
          enum:
          - collection.phone
          - collection.social
          - collection.email
          - collection.ai-chat
          - collection.financial
          - collection.discord
          - collection.reddit
          - collection.linkedin
          - collection.legal
          - collection.file
          - collection.other
          - case.flat
          - case.pro-upgrade
          - addon.kit-rental
          - addon.laptop-rental
          - addon.laptop-rental-setup
          - addon.managed-service
          - addon.shipping
          - addon.live-bank
          - plan.base
          - source.per
          - ai.tokens-overage
          - adjustment.manual
          - adjustment.discount
          description: Must be a recurring add-on (kit rental, laptop rental, live
            bank). One-time charges go through the billing-item endpoint.
        externalRef:
          type: string
          description: Caller-supplied dedup token. Repeating it for the same org+SKU
            returns the existing subscription instead of starting a second one. Reactivating
            after a cancel needs a NEW value.
        reason:
          type: string
          description: Required audit reason — persisted on the BillingEvent
        case:
          type: string
          description: Case this add-on belongs to. Rentals are case-level; live banks
            may be org-level (omit).
        startedAt:
          format: date-time
          type: string
          description: When the subscription starts, ISO-8601. Defaults to now. Also
            the period anchor — every later period falls on this day-of-month, not
            the 1st.
      required:
      - skuKey
      - externalRef
      - reason
    AdminRecurringAddonListResponseEntity:
      type: object
      properties:
        addons:
          type: array
          items:
            "$ref": "#/components/schemas/AdminRecurringAddonResponseEntity"
        total:
          type: number
          example: 1
      required:
      - addons
      - total
    CancelRecurringAddonDto:
      type: object
      properties:
        reason:
          type: string
          description: Required audit reason — persisted on the BillingEvent
      required:
      - reason
    AdminFeatureFlagResponseEntity:
      type: object
      properties:
        key:
          type: string
          description: The flag key, e.g. "clio-integration"
          example: clio-integration
        enabled:
          type: boolean
          description: Effective value. An UNSEEDED flag reads false because no row
            exists.
          example: true
        description:
          type: string
          nullable: true
          example: Turns on the Clio integration
        status:
          type: string
          enum:
          - SEEDED
          - UNSEEDED
          - ORPHANED
          description: SEEDED = defined in code and backed by a row; UNSEEDED = defined
            in code, no row yet (toggling creates it); ORPHANED = a leftover row this
            build no longer defines, so it cannot be toggled. Derived at read time,
            never stored.
          example: SEEDED
        updatedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:15:00.000Z'
        updatedByEmail:
          type: string
          nullable: true
          description: Admin who last toggled the flag. Null on rows a seed migration
            created and no one has changed since.
          example: admin@example.com
      required:
      - key
      - enabled
      - description
      - status
      - updatedAt
      - updatedByEmail
    AdminFeatureFlagListResponseEntity:
      type: object
      properties:
        flags:
          type: array
          items:
            "$ref": "#/components/schemas/AdminFeatureFlagResponseEntity"
        total:
          type: number
          description: Number of flags returned
          example: 24
      required:
      - flags
      - total
    UpdateFeatureFlagDto:
      type: object
      properties:
        enabled:
          type: boolean
          description: Desired state of the flag. Sending the value it already has
            is a no-op and leaves the audit fields untouched.
      required:
      - enabled
    UploadLogOpenDto:
      type: object
      properties:
        systemId:
          type: string
        date:
          type: string
          description: ISO date string
        appVersion:
          type: string
          description: App version
        message:
          type: string
          description: Message
        osDetails:
          type: string
        extractionCode:
          type: string
        deviceId:
          type: string
        deviceType:
          type: string
      required:
      - systemId
      - date
    UploadDeviceLogResponseEntity:
      type: object
      properties:
        message:
          type: string
        isSuccess:
          type: boolean
        threadTs:
          type: string
      required:
      - message
      - isSuccess
      - threadTs
    UploadAppLogDto:
      type: object
      properties:
        systemId:
          type: string
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        date:
          type: string
          description: ISO date string
        appVersion:
          type: string
          description: App version
        message:
          type: string
          description: Message
        osDetails:
          type: string
        deviceMetadata:
          type: string
        threadTs:
          type: string
        replyBroadcast:
          type: boolean
        backupMetadata:
          type: object
        errorMetadata:
          type: string
        backupPath:
          type: string
      required:
      - systemId
      - deviceId
      - deviceType
      - date
    UploadLogOpenDtoV2:
      type: object
      properties:
        systemId:
          type: string
        date:
          type: string
          description: ISO date string
        appVersion:
          type: string
          description: App version
        message:
          type: string
          description: Message
        osDetails:
          type: object
        extractionCode:
          type: string
        deviceId:
          type: string
        deviceType:
          type: string
        fileProviderId:
          type: string
      required:
      - systemId
      - date
      - fileProviderId
    UploadAppLogDtoV2:
      type: object
      properties:
        systemId:
          type: string
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        date:
          type: string
          description: ISO date string
        appVersion:
          type: string
          description: App version
        message:
          type: string
          description: Message
        osDetails:
          type: object
        deviceMetadata:
          type: object
        threadTs:
          type: string
        replyBroadcast:
          type: boolean
        backupMetadata:
          type: object
        errorMetadata:
          type: object
        backupPath:
          type: string
        fileProviderId:
          type: string
      required:
      - systemId
      - deviceId
      - deviceType
      - date
      - fileProviderId
    SingleDeviceExtractionStatusResponseEntity:
      type: object
      properties:
        lastBackupAt:
          format: date-time
          type: string
        hasNewEcCodeConfig:
          type: boolean
        deviceId:
          type: string
        deviceType:
          type: string
      required:
      - lastBackupAt
      - hasNewEcCodeConfig
      - deviceId
      - deviceType
    DevicesExtractionStatusResponseEntity:
      type: object
      properties:
        devices:
          type: array
          items:
            "$ref": "#/components/schemas/SingleDeviceExtractionStatusResponseEntity"
      required:
      - devices
    FetchSingleDeviceExtractionStatusDto:
      type: object
      properties:
        deviceId:
          type: string
        deviceType:
          type: string
        systemId:
          type: string
      required:
      - deviceId
      - deviceType
      - systemId
    FetchDevicesExtractionStatusDto:
      type: object
      properties:
        devices:
          type: array
          items:
            "$ref": "#/components/schemas/FetchSingleDeviceExtractionStatusDto"
      required:
      - devices
    LoginExtractionCodeDto:
      type: object
      properties:
        extractionCode:
          type: string
        appConfig:
          "$ref": "#/components/schemas/CreateUpdateAppConfigDto"
        app:
          type: string
        os:
          type: string
        arch:
          type: string
        isEmailLogin:
          type: boolean
        client:
          type: string
          enum:
          - desktop
          - portal
          description: Caller type; the web portal sends "portal".
      required:
      - extractionCode
      - appConfig
      - app
      - os
      - arch
      - isEmailLogin
    DeviceBackupUpdateResponseEntity:
      type: object
      properties:
        recorded:
          type: boolean
          description: Whether the lifecycle report was persisted.
        status:
          type: string
          enum:
          - started
          - completed
          - failed
          - cancelled
        billingOutcome:
          type: string
          enum:
          - not-applicable
          - trigger-is-import
          - no-organization
          - not-a-phone
          - device-not-registered
          - no-data-source-status
          - dispatching
          - dispatched
          - failed
          - report-failed
          description: What the backup-time charge dispatch did. Anything other than
            `dispatched` leaves the import-time charge as the backstop.
      required:
      - recorded
      - status
      - billingOutcome
    DeviceBackupUpdateDto:
      type: object
      properties:
        systemId:
          type: string
        deviceId:
          type: string
          description: Stable hardware id, not the Mongo _id
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        status:
          type: string
          enum:
          - started
          - completed
          - failed
          - cancelled
          description: Lifecycle state being reported. Only `completed` can result
            in a charge.
        reason:
          type: string
          description: Cause for failed/cancelled
        metadata:
          type: object
        appVersion:
          type: string
      required:
      - deviceId
      - deviceType
      - status
    UpdateDeviceStorageDto:
      type: object
      properties:
        deviceId:
          type: string
        osVersion:
          type: string
        phoneHardwareVersion:
          type: string
        phoneOtherInfo:
          type: object
        calculatedTotalSpaceUsed:
          type: number
        calculatedTotalDiskSpace:
          type: number
        otherStorageMetadata:
          type: object
      required:
      - deviceId
    DeviceStorageFeedbackDto:
      type: object
      properties:
        deviceId:
          type: string
        userReportedTotalSpaceUsed:
          type: number
        userReportedTotalDiskSpace:
          type: number
      required:
      - deviceId
      - userReportedTotalSpaceUsed
    AddUpdateDeviceResponseEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        deviceId:
          type: string
          example: 3F2504E0-4F89-11D3-9A0C-0305E82C3301
        serialNumber:
          type: string
          example: F2LXK0QJHG7F
        name:
          type: string
          example: Jane's iPhone
        type:
          type: string
          example: ios
        nonDeviceType:
          type: string
          nullable: true
          example:
        model:
          type: string
          example: iPhone 15
        timeZone:
          type: string
          example: America/New_York
        osVersion:
          type: string
          example: '18.0'
        owner:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        devicePhoneNumber:
          type: string
          example: "+15555550100"
        availableData:
          type: object
          description: Which data types exist for this device (optionally scoped by
            query filters).
          example:
            messages: true
            calls: false
      required:
      - createdAt
      - updatedAt
      - id
      - deviceId
      - serialNumber
      - name
      - type
      - nonDeviceType
      - model
      - timeZone
      - osVersion
      - owner
      - devicePhoneNumber
    AddUpdateDeviceDto:
      type: object
      properties:
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        deviceMetadata:
          type: object
      required:
      - deviceId
      - deviceType
    UpdateExtractionCodeMetadataDto:
      type: object
      properties:
        metadata:
          type: object
      required:
      - metadata
    SlackNotificationResponseEntity:
      type: object
      properties:
        ts:
          type: string
          example: '1700000000.000100'
      required:
      - ts
    PostUpdateNotificationDto:
      type: object
      properties:
        messages:
          type: array
          items:
            type: string
            minLength: 1
        deviceId:
          type: string
        deviceType:
          type: string
        shouldSendNotification:
          type: boolean
      required:
      - messages
    PostTrackingNotificationDto:
      type: object
      properties:
        messages:
          type: array
          items:
            type: string
            minLength: 1
        threadTs:
          type: string
        systemId:
          type: string
        date:
          type: string
          description: ISO date string
        appVersion:
          type: string
          description: App version
        osDetails:
          type: object
      required:
      - messages
      - systemId
      - date
    UploadConversationsNotificationsDto:
      type: object
      properties:
        messageSelectionReport:
          "$ref": "#/components/schemas/MessageSelectionReportDto"
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        extractionCode:
          type: string
        appVersion:
          type: string
        noOfConversations:
          type: number
        message:
          type: string
        threadTs:
          type: string
      required:
      - deviceId
      - deviceType
      - extractionCode
      - appVersion
      - noOfConversations
      - message
    PostDebugLogNotificationDto:
      type: object
      properties:
        systemId:
          type: string
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        appVersion:
          type: string
          description: App version
        logPath:
          type: string
        message:
          type: string
          description: Message
      required:
      - systemId
      - deviceId
      - deviceType
      - logPath
    AppSettingsResponseEntity:
      type: object
      properties:
        localConversionEnabled:
          type: boolean
          description: Local conversion status
          example: true
        localCompressionEnabled:
          type: boolean
          description: Local compression status
          example: true
        collectionEventsEnabled:
          type: boolean
          description: Send collection events + heartbeats
          example: false
      required:
      - localConversionEnabled
      - localCompressionEnabled
      - collectionEventsEnabled
    AddCaseFilesDto:
      type: object
      properties:
        files:
          type: array
          items:
            type: string
        category:
          type: string
      required:
      - files
    ImportCaseMediaAlbumDto:
      type: object
      properties:
        id:
          type: number
        name:
          type: string
      required:
      - id
      - name
    ImportCaseMediaFileDto:
      type: object
      properties:
        id:
          type: number
        fileName:
          type: string
        createdAt:
          type: string
        type:
          type: number
        duration:
          type: number
        uniformType:
          type: string
        trashedState:
          type: number
        directory:
          type: string
        album:
          "$ref": "#/components/schemas/ImportCaseMediaAlbumDto"
        originalFileProviderName:
          type: string
        thumbnailFileProviderName:
          type: string
        metadata:
          type: object
      required:
      - id
      - fileName
    ImportCaseMediaDto:
      type: object
      properties:
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        deviceMetadata:
          type: object
        systemId:
          type: string
        appVersion:
          type: string
        osDetails:
          "$ref": "#/components/schemas/OsDetailsDto"
        files:
          type: array
          items:
            "$ref": "#/components/schemas/ImportCaseMediaFileDto"
      required:
      - deviceId
      - deviceType
      - systemId
      - appVersion
      - files
    ImportCaseNoteItemDto:
      type: object
      properties:
        id:
          type: number
        title:
          type: string
        summary:
          type: string
        createdAt:
          type: string
        modifiedAt:
          type: string
        htmlContent:
          type: string
        metadata:
          type: object
        attachments:
          type: array
          items:
            type: string
      required:
      - id
      - title
    ImportCaseNotesDto:
      type: object
      properties:
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        deviceMetadata:
          type: object
        systemId:
          type: string
        appVersion:
          type: string
        osDetails:
          "$ref": "#/components/schemas/OsDetailsDto"
        notes:
          type: array
          items:
            "$ref": "#/components/schemas/ImportCaseNoteItemDto"
      required:
      - deviceId
      - deviceType
      - systemId
      - appVersion
      - notes
    ImportCaseBrowserHistoryDto:
      type: object
      properties:
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        deviceMetadata:
          type: object
        systemId:
          type: string
        appVersion:
          type: string
        osDetails:
          "$ref": "#/components/schemas/OsDetailsDto"
        safariHistoryFileKey:
          type: string
          description: S3 key for the uploaded Safari History.db file
      required:
      - deviceId
      - deviceType
      - systemId
      - appVersion
      - safariHistoryFileKey
    ImportCallLogContactDto:
      type: object
      properties:
        id:
          type: object
        fullName:
          type: string
        name:
          type: string
        first:
          type: string
        middle:
          type: string
        last:
          type: string
        email:
          type: string
        address:
          type: string
        city:
          type: string
        country:
          type: string
        phoneNumbers:
          type: string
        iphone:
          type: string
        phoneWork:
          type: string
        phoneMobile:
          type: string
        phoneHome:
          type: string
        profileImagePath:
          type: string
      required:
      - id
    ImportCallLogMessageDto:
      type: object
      properties:
        id:
          type: object
        duration:
          type: object
        date:
          type: string
        sender:
          type: string
        address:
          type: string
        callSource:
          type: string
        contact:
          type: object
        isFromMe:
          type: boolean
        isDelivered:
          type: boolean
        isDeleted:
          type: boolean
        isArchive:
          type: boolean
        isFinished:
          type: boolean
        isSpam:
          type: boolean
        isSticker:
          type: boolean
        isEmpty:
          type: boolean
      required:
      - id
      - date
      - isFromMe
    ImportCallLogConversationDto:
      type: object
      properties:
        id:
          type: object
        guid:
          type: string
        chatName:
          type: string
        displayName:
          type: string
        name:
          type: string
        number:
          type: string
        lastMessageId:
          type: object
        date:
          type: object
        contacts:
          type: array
          items:
            "$ref": "#/components/schemas/ImportCallLogContactDto"
        sendersNotInContact:
          type: array
          items:
            type: string
        messages:
          type: array
          items:
            "$ref": "#/components/schemas/ImportCallLogMessageDto"
      required:
      - id
      - messages
    ImportCaseCallLogsDto:
      type: object
      properties:
        deviceId:
          type: string
        deviceType:
          type: string
          enum:
          - unknown
          - android
          - ios
          - facebook_dump
          - instagram_dump
          - threads_dump
          - email
        deviceMetadata:
          type: object
        systemId:
          type: string
        appVersion:
          type: string
        osDetails:
          "$ref": "#/components/schemas/OsDetailsDto"
        conversations:
          type: array
          items:
            "$ref": "#/components/schemas/ImportCallLogConversationDto"
      required:
      - deviceId
      - deviceType
      - systemId
      - appVersion
      - conversations
    CaseFilesImportedCountEntity:
      type: object
      properties:
        totalFilesImported:
          type: number
          description: Total number of files imported
          example: 100
        totalSizeImported:
          type: number
          description: Total size of files imported
          example: 1000000
      required:
      - totalFilesImported
      - totalSizeImported
    UserEmailDataEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        emailDataProvider:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        sourceEmail:
          type: string
          example: jane.doe@example.com
        body:
          type: string
          example: "<p>Please find the signed agreement attached.</p>"
          nullable: true
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        attachments:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/UserFileEntity"
        bcc:
          example: []
          type: array
          items:
            type: object
        cc:
          example:
          - name: Sam Lee
            email: sam.lee@example.com
          type: array
          items:
            type: object
        from:
          example:
          - name: John Roe
            email: john.roe@example.org
          type: array
          items:
            type: object
        replyTo:
          example: []
          type: array
          items:
            type: object
        to:
          example:
          - name: Jane Doe
            email: jane.doe@example.com
          type: array
          items:
            type: object
        folders:
          example:
          - INBOX
          type: array
          items:
            type: string
        grantId:
          type: string
          example: 5f0c8b3e-2a41-4d7c-9a51-1c2b3d4e5f60
        externalId:
          type: string
          example: 18d1f2a3b4c5d6e7
        object:
          type: string
          example: message
          nullable: true
        snippet:
          type: string
          example: Please find the signed agreement attached.
          nullable: true
        starred:
          type: boolean
          example: false
        unread:
          type: boolean
          example: false
        subject:
          type: string
          example: Signed agreement
          nullable: true
        threadId:
          type: string
          example: 18d1f2a3b4c5d6e0
        threadIndex:
          type: number
          example: 0
        tags:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/TagEntity"
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
      required:
      - createdAt
      - updatedAt
      - id
      - emailDataProvider
      - sourceEmail
      - body
      - date
      - attachments
      - bcc
      - cc
      - from
      - replyTo
      - to
      - folders
      - grantId
      - externalId
      - object
      - snippet
      - starred
      - unread
      - subject
      - threadId
      - threadIndex
      - tags
    UserEmailDataThreadEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        sourceEmail:
          type: string
          example: jane.doe@example.com
        grantId:
          type: string
          example: 5f0c8b3e-2a41-4d7c-9a51-1c2b3d4e5f60
        externalId:
          type: string
          example: 18d1f2a3b4c5d6e0
        lastMessageOrDraft:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: object
          allOf:
          - "$ref": "#/components/schemas/UserEmailDataEntity"
        emailCount:
          type: number
          description: Number of emails in the thread
          example: 3
        hasAttachments:
          type: boolean
          example: true
        hasDrafts:
          type: boolean
          example: false
        earliestMessageDate:
          format: date-time
          type: string
          example: '2026-01-12T09:05:00.000Z'
          nullable: true
        latestMessageReceivedDate:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        latestMessageSentDate:
          format: date-time
          type: string
          example: '2026-01-14T16:45:00.000Z'
          nullable: true
        participants:
          example:
          - name: Jane Doe
            email: jane.doe@example.com
          - name: John Roe
            email: john.roe@example.org
          type: array
          items:
            type: object
        folders:
          example:
          - INBOX
          type: array
          items:
            type: string
        messageIds:
          example:
          - 18d1f2a3b4c5d6e0
          - 18d1f2a3b4c5d6e5
          - 18d1f2a3b4c5d6e7
          type: array
          items:
            type: string
        draftIds:
          example: []
          type: array
          items:
            type: string
        snippet:
          type: string
          example: Please find the signed agreement attached.
          nullable: true
        starred:
          type: boolean
          example: false
        unread:
          type: boolean
          example: true
        subject:
          type: string
          example: Signed agreement
          nullable: true
        tags:
          nullable: true
          description: The full record on routes that load it; null (or an empty list)
            on routes that do not.
          type: array
          items:
            "$ref": "#/components/schemas/TagEntity"
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
        ocSharedStatus:
          type: string
          nullable: true
          enum:
          - shared
          - pending
          - not_gated
          description: 'Opposing-council only: whether this thread has been shared
            with the OL. `null` for OL/OG callers — same convention as cases/files/:caseId
            and conversation/search-entries.'
      required:
      - createdAt
      - updatedAt
      - id
      - sourceEmail
      - grantId
      - externalId
      - lastMessageOrDraft
      - emailCount
      - hasAttachments
      - hasDrafts
      - earliestMessageDate
      - latestMessageReceivedDate
      - latestMessageSentDate
      - participants
      - folders
      - messageIds
      - draftIds
      - snippet
      - starred
      - unread
      - subject
      - tags
    EmailDomainEntity:
      type: object
      properties:
        domain:
          type: string
          description: The email domain (part after @), lowercased.
          example: example.org
        count:
          type: number
          description: Number of emails with this domain on a from/to/cc/bcc.
          example: 128
      required:
      - domain
      - count
    EmailDomainsResponseEntity:
      type: object
      properties:
        data:
          type: array
          items:
            "$ref": "#/components/schemas/EmailDomainEntity"
        totalDomains:
          type: number
          description: Number of distinct domains.
          example: 12
      required:
      - data
      - totalDomains
    ThreadByEmailResponseEntity:
      type: object
      properties:
        thread:
          "$ref": "#/components/schemas/UserEmailDataThreadEntity"
        emailIds:
          example:
          - 65a1b2c3d4e5f6a7b8c9d0c3
          - 65a1b2c3d4e5f6a7b8c9d0c2
          type: array
          items:
            type: string
      required:
      - thread
      - emailIds
    LinkTokenResponseEntity:
      type: object
      properties:
        linkToken:
          type: string
          description: Temporary token to initialize Plaid Link
          example: link-sandbox-af1a0311-da53-4636-b754-dd15cc058176
        expiration:
          type: string
          description: Expiration date (ISO 8601)
          example: '2026-01-15T18:30:00Z'
        requestId:
          type: string
          description: ID for troubleshooting with Plaid support
          example: XQVgFigpGHXkb0b
        hostedLinkUrl:
          type: string
          description: Only present when using Hosted Link
          example: https://secure.plaid.com/hl/example-session
        userId:
          type: string
          description: Returned if Plaid created a new user
          example: usr_8c3ZbDBYjaqUXZ
      required:
      - linkToken
      - expiration
      - requestId
    PlaidLinkUserDto:
      type: object
      properties:
        legalName:
          type: string
        phoneNumber:
          type: string
        emailAddress:
          type: string
        dateOfBirth:
          type: string
          description: 'Format: YYYY-MM-DD'
    PlaidStatementsConfigDto:
      type: object
      properties:
        startDate:
          type: string
          description: Start date for statements extraction (YYYY-MM-DD). Maximum
            2 years back. Defaults to 2 years ago.
        endDate:
          type: string
          description: End date for statements extraction (YYYY-MM-DD). Maximum 2
            years range. Defaults to today.
    PlaidAccountSubtypesDto:
      type: object
      properties:
        accountSubtypes:
          type: array
          items:
            type: string
    PlaidAccountFiltersDto:
      type: object
      properties:
        depository:
          "$ref": "#/components/schemas/PlaidAccountSubtypesDto"
        credit:
          "$ref": "#/components/schemas/PlaidAccountSubtypesDto"
        loan:
          "$ref": "#/components/schemas/PlaidAccountSubtypesDto"
        investment:
          "$ref": "#/components/schemas/PlaidAccountSubtypesDto"
    CreateLinkTokenDto:
      type: object
      properties:
        products:
          type: array
          default:
          - transactions
          description: Plaid products to require. Defaults to [transactions, liabilities].
          items:
            type: string
            enum:
            - assets
            - auth
            - beacon
            - employment
            - identity
            - income_verification
            - identity_verification
            - investments
            - liabilities
            - payment_initiation
            - standing_orders
            - signal
            - statements
            - transactions
            - transfer
            - cra_base_report
            - cra_income_insights
            - cra_cashflow_insights
            - cra_lend_score
            - cra_partner_insights
            - cra_network_insights
            - cra_monitoring
            - layer
            - protect_linked_bank
        language:
          type: string
          default: en
          description: Language for Plaid Link UI (en, es, fr, etc.)
        countryCodes:
          default:
          - US
          description: ISO-3166-1 alpha-2 country codes.
          type: array
          items:
            type: string
        webhook:
          type: string
          description: URL to receive Plaid webhooks
        redirectUri:
          type: string
          description: Redirect URI for OAuth flows
        linkCustomizationName:
          type: string
        accessToken:
          type: string
          description: Existing access token — use only for update mode
        enableMultiItemLink:
          type: boolean
          default: false
        user:
          "$ref": "#/components/schemas/PlaidLinkUserDto"
        requiredIfSupportedProducts:
          type: array
          default:
          - investments
          - liabilities
          description: Products to enable only if supported by the institution. Defaults
            to [investments, statements]. These will not block Link if the institution
            does not support them.
          items:
            type: string
            enum:
            - assets
            - auth
            - beacon
            - employment
            - identity
            - income_verification
            - identity_verification
            - investments
            - liabilities
            - payment_initiation
            - standing_orders
            - signal
            - statements
            - transactions
            - transfer
            - cra_base_report
            - cra_income_insights
            - cra_cashflow_insights
            - cra_lend_score
            - cra_partner_insights
            - cra_network_insights
            - cra_monitoring
            - layer
            - protect_linked_bank
        statementsConfig:
          description: Date range for statements extraction. Plaid maximum is 2 years.
            Defaults to 2 years ago → today.
          allOf:
          - "$ref": "#/components/schemas/PlaidStatementsConfigDto"
        optionalProducts:
          type: array
          description: Products that improve UX but do not block item creation
          items:
            type: string
            enum:
            - assets
            - auth
            - beacon
            - employment
            - identity
            - income_verification
            - identity_verification
            - investments
            - liabilities
            - payment_initiation
            - standing_orders
            - signal
            - statements
            - transactions
            - transfer
            - cra_base_report
            - cra_income_insights
            - cra_cashflow_insights
            - cra_lend_score
            - cra_partner_insights
            - cra_network_insights
            - cra_monitoring
            - layer
            - protect_linked_bank
        additionalConsentedProducts:
          type: array
          description: Products to collect consent for without immediate billing
          items:
            type: string
            enum:
            - assets
            - auth
            - beacon
            - employment
            - identity
            - income_verification
            - identity_verification
            - investments
            - liabilities
            - payment_initiation
            - standing_orders
            - signal
            - statements
            - transactions
            - transfer
            - cra_base_report
            - cra_income_insights
            - cra_cashflow_insights
            - cra_lend_score
            - cra_partner_insights
            - cra_network_insights
            - cra_monitoring
            - layer
            - protect_linked_bank
        accountFilters:
          "$ref": "#/components/schemas/PlaidAccountFiltersDto"
    ExchangePublicTokenResponseEntity:
      type: object
      properties:
        financialAccountProviderId:
          type: string
          description: FinancialAccountProvider document ID
          example: 507f1f77bcf86cd799439011
        plaidItemId:
          type: string
          description: PlaidItem document ID
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        plaidExternalItemId:
          type: string
          description: Unique Plaid identifier for the Item
          example: M5eVJqLnv3tbzdngLDp9FL5OlDNxlNhlE55op
        requestId:
          type: string
          description: Request identifier for debugging and support
          example: Aim3b
      required:
      - financialAccountProviderId
      - plaidItemId
      - plaidExternalItemId
      - requestId
    ExchangePublicTokenDto:
      type: object
      properties:
        publicToken:
          type: string
          description: Temporary token generated by Plaid Link (expires after 30 minutes).
      required:
      - publicToken
    AiChatImportJobEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        extractionCode:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
          nullable: true
        file:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        createdBy:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        platform:
          type: string
          enum:
          - claude
          - chatgpt
          - gemini
          example: claude
        status:
          type: string
          enum:
          - not_started
          - in_progress
          - completed
          - failed
          example: completed
        startedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        completedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:32:10.000Z'
          nullable: true
        failedAt:
          format: date-time
          type: string
          nullable: true
          example:
        message:
          type: string
          example: Upload file not found
          nullable: true
        aiChatProvider:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
          nullable: true
        stats:
          type: object
          example:
            conversations: 12
            messages: 348
            artifacts: 0
            activities: 0
            attachments: 5
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - case
      - extractionCode
      - file
      - createdBy
      - platform
      - status
      - startedAt
      - completedAt
      - failedAt
      - message
      - aiChatProvider
      - stats
    ImportAiChatDataDto:
      type: object
      properties:
        fileId:
          type: string
          description: UserFile ID of the uploaded zip
        platform:
          type: string
          enum:
          - claude
          - chatgpt
          - gemini
          description: 'AI platform: claude, chatgpt, or gemini'
        extractionCode:
          type: string
          description: Extraction code string (optional)
      required:
      - fileId
      - platform
    LinkedInImportJobEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        extractionCode:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        file:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        createdBy:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        status:
          type: string
          enum:
          - not_started
          - in_progress
          - completed
          - failed
        startedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T14:25:00.000Z'
        completedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T14:30:00.000Z'
        failedAt:
          format: date-time
          type: string
          nullable: true
          example:
        message:
          type: string
          example: Import failed
          nullable: true
        linkedInUserProvider:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        selectedDataTypes:
          example:
          - messages
          - connections
          type: array
          items:
            type: string
        selectedConversationIds:
          description: LinkedIn conversation ids the import was limited to; empty
            means all conversations.
          example:
          - 2-ZGVmYXVsdA==
          type: array
          items:
            type: string
        stats:
          type: object
          example:
            conversations: 14
            messages: 320
            connections: 250
            comments: 12
            richMedia: 3
            savedItems: 5
            savedJobAlerts: 1
            savedJobs: 2
            jobApplications: 4
      required:
      - createdAt
      - updatedAt
      - id
      - case
      - extractionCode
      - file
      - createdBy
      - status
      - startedAt
      - completedAt
      - failedAt
      - message
      - linkedInUserProvider
      - selectedDataTypes
      - selectedConversationIds
      - stats
    LinkedInImportSkippedResponseEntity:
      type: object
      properties:
        imported:
          type: boolean
          enum:
          - false
          description: Always false for a no-op
        message:
          type: string
      required:
      - imported
      - message
    ImportLinkedInDataExtractionDto:
      type: object
      properties:
        fileId:
          type: string
          description: S3 file key of the uploaded zip
        extractionCode:
          type: string
          description: Extraction code string
        selectedDataTypes:
          description: Selected data types to import
          type: array
          items:
            type: string
        selectedConversationIds:
          description: Specific conversation IDs to import
          type: array
          items:
            type: string
      required:
      - fileId
    FetchClientLogDto:
      type: object
      properties:
        clientId:
          type: string
          description: The client ID
          example: 64a7672e5462512345678901
        logType:
          type: string
          description: The log type
          example: app_log
          enum:
          - app_log
          - device_log
        deviceId:
          type: string
          description: The device ID
          example: 64a7672e5462512345678901
        deviceType:
          type: string
          description: The device type
          example: ios | android
      required:
      - clientId
      - logType
    SendIntercomSignalDto:
      type: object
      properties:
        extractionCode:
          type: string
          description: The extraction code
        type:
          type: string
          description: The log type
          example: open_conversation
          enum:
          - open_conversation
        conversationId:
          type: string
          description: The conversaton ID
          example: 64a7672e5462512345678901
      required:
      - extractionCode
      - type
    CreateOAuthUrlResponseEntity:
      type: object
      properties:
        url:
          type: string
          example: https://accounts.example.com/oauth/authorize?client_id=abc123&state=xyz
      required:
      - url
    CreateOAuthUrlDto:
      type: object
      properties:
        redirectUri:
          type: string
        provider:
          type: string
          enum:
          - google
          - yahoo
          - imap
          - microsoft
          - icloud
          - ews
      required:
      - redirectUri
      - provider
    CreateEmailProviderDto:
      type: object
      properties:
        redirectUri:
          type: string
        code:
          type: string
        extractionCode:
          type: string
      required:
      - redirectUri
      - code
      - extractionCode
    CreateImapEmailProviderDto:
      type: object
      properties:
        extractionCode:
          type: string
        host:
          type: string
        port:
          type: number
        user:
          type: string
        pass:
          type: string
      required:
      - extractionCode
      - host
      - port
      - user
      - pass
    SingleRemoteEmailEntity:
      type: object
      properties:
        starred:
          type: boolean
        unread:
          type: boolean
        folders:
          type: array
          items:
            type: string
        subject:
          type: string
        threadId:
          type: string
        body:
          type: string
        grantId:
          type: string
        id:
          type: string
        object:
          type: string
        snippet:
          type: string
        bcc:
          type: array
          items:
            type: object
        cc:
          type: array
          items:
            type: string
        attachments:
          type: array
          items:
            type: object
        from:
          type: array
          items:
            type: string
        to:
          type: array
          items:
            type: string
        replyTo:
          type: array
          items:
            type: string
        date:
          format: date-time
          type: string
        trackingOptions:
          type: object
        hasAttachment:
          type: boolean
        isDraft:
          type: boolean
      required:
      - starred
      - unread
      - folders
      - subject
      - threadId
      - body
      - grantId
      - id
      - object
      - snippet
      - bcc
      - cc
      - attachments
      - from
      - to
      - replyTo
      - date
      - trackingOptions
      - hasAttachment
      - isDraft
    RemoteEmailSearchResponseEntity:
      type: object
      properties:
        data:
          type: array
          items:
            "$ref": "#/components/schemas/SingleRemoteEmailEntity"
        nextPageToken:
          type: string
      required:
      - data
      - nextPageToken
    SearchRemoteEmailsDto:
      type: object
      properties:
        anyEmail:
          type: array
          items:
            type: string
        to:
          type: array
          items:
            type: string
        bcc:
          type: array
          items:
            type: string
        cc:
          type: array
          items:
            type: string
        from:
          type: array
          items:
            type: string
        hasAttachment:
          type: boolean
        receivedAfter:
          type: string
        receivedBefore:
          type: string
        keywords:
          type: array
          items:
            type: string
        subject:
          type: string
        contactNames:
          type: array
          items:
            type: string
        folders:
          description: Restrict the search to these folders (folder id/path as returned
            by fetch-folders). Optional; when omitted, all non-system folders are
            searched.
          type: array
          items:
            type: string
        providerId:
          type: string
        pageToken:
          type: string
        limit:
          type: number
      required:
      - providerId
    SingleRemoteEmailThreadEntity:
      type: object
      properties:
        grantId:
          type: string
        id:
          type: string
        object:
          type: string
        latestDraftOrMessage:
          "$ref": "#/components/schemas/SingleRemoteEmailEntity"
        hasAttachments:
          type: boolean
        hasDrafts:
          type: boolean
        earliestMessageDate:
          format: date-time
          type: string
        latestMessageReceivedDate:
          format: date-time
          type: string
        latestMessageSentDate:
          format: date-time
          type: string
        folders:
          type: array
          items:
            type: string
        participants:
          type: array
          items:
            type: string
        snippet:
          type: string
        subject:
          type: string
        lastImportedAt:
          format: date-time
          type: string
          nullable: true
          description: When this thread (any of its messages) was first imported into
            the case. Null when none of the thread messages have been imported.
        importedMessageIds:
          description: Subset of the thread message ids that have already been imported.
          type: array
          items:
            type: string
        isDraft:
          type: boolean
        importedMessageCount:
          type: number
        isImported:
          type: boolean
      required:
      - grantId
      - id
      - object
      - latestDraftOrMessage
      - hasAttachments
      - hasDrafts
      - earliestMessageDate
      - latestMessageReceivedDate
      - latestMessageSentDate
      - folders
      - participants
      - snippet
      - subject
      - importedMessageIds
      - isDraft
      - importedMessageCount
      - isImported
    RemoteEmailThreadSearchResponseEntity:
      type: object
      properties:
        data:
          type: array
          items:
            "$ref": "#/components/schemas/SingleRemoteEmailThreadEntity"
        nextPageToken:
          type: string
      required:
      - data
      - nextPageToken
    UserEmailImportJobEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        emailDataProvider:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        extractionCode:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        case:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        notificationEmails:
          example:
          - jane.doe@example.com
          type: array
          items:
            type: string
        status:
          type: string
          enum:
          - not_started
          - in_progress
          - completed
          - failed
        type:
          type: string
          enum:
          - by_thread
          - by_search
          - import_all
          - by_multi_query
        startedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        completedAt:
          format: date-time
          type: string
          example:
          nullable: true
        failedAt:
          format: date-time
          type: string
          example:
          nullable: true
        message:
          type: string
          example: Importing threads
          nullable: true
        jobData:
          type: object
          example:
            providerId: 65a1b2c3d4e5f6a7b8c9d0e1
          nullable: true
        importedThreadsCount:
          type: number
          example: 120
        importedEmailsCount:
          type: number
          example: 348
        totalThreadsCount:
          type: number
          example: 400
        totalEmailsCount:
          type: number
          example: 1150
        isCancelled:
          type: boolean
          example: false
        scheduledFor:
          format: date-time
          type: string
          nullable: true
          example:
      required:
      - createdAt
      - updatedAt
      - id
      - emailDataProvider
      - extractionCode
      - case
      - notificationEmails
      - status
      - type
      - startedAt
      - completedAt
      - failedAt
      - message
      - jobData
      - importedThreadsCount
      - importedEmailsCount
      - totalThreadsCount
      - totalEmailsCount
      - isCancelled
    StartImportEmailSingleDto:
      type: object
      properties:
        threadId:
          type: string
      required:
      - threadId
    SearchRemoteEmailsDtoBase:
      type: object
      properties:
        anyEmail:
          type: array
          items:
            type: string
        to:
          type: array
          items:
            type: string
        bcc:
          type: array
          items:
            type: string
        cc:
          type: array
          items:
            type: string
        from:
          type: array
          items:
            type: string
        hasAttachment:
          type: boolean
        receivedAfter:
          type: string
        receivedBefore:
          type: string
        keywords:
          type: array
          items:
            type: string
        subject:
          type: string
        contactNames:
          type: array
          items:
            type: string
        folders:
          description: Restrict the search to these folders (folder id/path as returned
            by fetch-folders). Optional; when omitted, all non-system folders are
            searched.
          type: array
          items:
            type: string
    MultiQueryImportDto:
      type: object
      properties:
        queries:
          description: Raw Gmail boolean query strings (e.g. "a OR b OR c", "x AND
            (y OR z)"). Passed verbatim to Gmail q=, with date range appended.
          type: array
          items:
            type: string
        emails:
          description: Email addresses to search for as any participant. Each address
            is expanded into (from:e OR to:e OR cc:e OR bcc:e) and run as its own
            query.
          type: array
          items:
            type: string
        dateStart:
          type: string
          description: Optional inclusive lower bound of received date for all queries
            (ISO date). Omit for no lower bound.
        dateEnd:
          type: string
          description: Optional inclusive upper bound of received date for all queries
            (ISO date). Omit for no upper bound.
    StartImportEmailsDto:
      type: object
      properties:
        providerId:
          type: string
        extractionCode:
          type: string
        notificationEmails:
          description: Additional email addresses to send email to when export completes
            (other than user email)
          type: array
          items:
            type: string
        type:
          type: string
          enum:
          - by_thread
          - by_search
          - import_all
          - by_multi_query
          default: by_thread
        emails:
          type: array
          items:
            "$ref": "#/components/schemas/StartImportEmailSingleDto"
        searchData:
          "$ref": "#/components/schemas/SearchRemoteEmailsDtoBase"
        multiQueryData:
          "$ref": "#/components/schemas/MultiQueryImportDto"
        folderIds:
          description: Restrict the import to these provider folders (folder `id`
            as returned by fetch-folders). Optional. Applies to import_all, by_search
            and by_multi_query; ignored for by_thread.
          type: array
          items:
            type: string
        importAllSkipPages:
          type: number
          description: Number of pages to skip for import all
        scheduledFor:
          type: string
          description: If set, schedule the import to start at this time instead of
            immediately. Must be in the future.
      required:
      - providerId
      - extractionCode
    TagCountEntity:
      type: object
      properties:
        tag:
          type: string
          description: Tag name.
          example: Responsive
        count:
          type: number
          description: Number of emails carrying this tag.
          example: 64
      required:
      - tag
      - count
    EmailTagsCountResponseEntity:
      type: object
      properties:
        total:
          type: number
          example: 42
        page:
          type: number
          example: 1
        perPage:
          type: number
          example: 20
        skip:
          type: number
          example: 0
        limit:
          type: number
          example: 20
        totalPages:
          type: number
          example: 3
        nextPage:
          type: number
          example: 2
          nullable: true
        previousPage:
          type: number
          example:
          nullable: true
        data:
          type: array
          items:
            "$ref": "#/components/schemas/TagCountEntity"
      required:
      - total
      - totalPages
      - nextPage
      - previousPage
      - data
    QuerySearchCountEntity:
      type: object
      properties:
        query:
          type: string
          description: The query string, echoed back.
          example: agreement AND signed
        count:
          type: number
          description: Number of emails matching the query.
          example: 17
        error:
          type: string
          description: Set when the query could not be counted (parse error or search
            failure). When present, `count` is 0 and is not meaningful.
          example: search_failed
      required:
      - query
      - count
    EmailSearchCountEntity:
      type: object
      properties:
        email:
          type: string
          description: The email address, echoed back.
          example: john.roe@example.org
        count:
          type: number
          description: Number of emails where this address is a participant (from/to/cc/bcc).
          example: 42
      required:
      - email
      - count
    EmailSearchCountsResponseEntity:
      type: object
      properties:
        queries:
          type: array
          items:
            "$ref": "#/components/schemas/QuerySearchCountEntity"
        emails:
          type: array
          items:
            "$ref": "#/components/schemas/EmailSearchCountEntity"
      required:
      - queries
      - emails
    EmailSearchCountsDto:
      type: object
      properties:
        queries:
          description: Boolean query strings. Each is counted independently against
            the indexed email fields (subject, body, snippet, from/to/cc/bcc). Supports
            the same AND / OR / NOT / -prefix, parentheses, "quoted phrases" and wildcards
            as the boolean query parser.
          example:
          - '"confidentiality agreement"'
          - "(Nigeria OR Cyprus) AND NDA"
          type: array
          items:
            type: string
        emails:
          description: Email addresses. Each is counted as the number of emails where
            the address appears as any participant (from/to/cc/bcc), case-insensitive.
          type: array
          items:
            type: string
        providerId:
          type: string
          description: 'Optional: narrow the counts to a single email provider (mailbox)
            within the case. When omitted, counts span the whole case.'
    EmailDomainAddressEntity:
      type: object
      properties:
        email:
          type: string
          description: A participant email address of the domain, lowercased.
          example: john.roe@example.org
        count:
          type: number
          description: Number of emails (documents) containing this address in from/to/cc/bcc.
          example: 42
      required:
      - email
      - count
    EmailDomainAddressesResponseEntity:
      type: object
      properties:
        addresses:
          type: array
          items:
            "$ref": "#/components/schemas/EmailDomainAddressEntity"
        total:
          type: number
          description: 'Distinct addresses in the domain (for pagination). Note: the
            sum of per-address counts may exceed the domain document count, since
            one email can contain multiple addresses of the same domain.'
          example: 5
        page:
          type: number
          example: 1
        perPage:
          type: number
          example: 20
        skip:
          type: number
          example: 0
        limit:
          type: number
          example: 20
      required:
      - addresses
      - total
      - skip
      - limit
    BulkTagResponseEntity:
      type: object
      properties:
        mode:
          type: string
          enum:
          - by_id
          - by_search
        status:
          type: string
          description: "'completed' for by_id; 'queued' for by_search."
          example: completed
        requested:
          type: number
          example: 2
        matchedDocuments:
          type: number
          example: 2
        skippedDocuments:
          type: number
          example: 0
        resolvedTagIds:
          example:
          - 65a1b2c3d4e5f6a7b8c9d0a1
          type: array
          items:
            type: string
        attachmentsCreated:
          type: number
          example: 2
        emailsMatched:
          type: number
          description: by_id threads + autoTagEmails
          example: 0
        emailAttachmentsCreated:
          type: number
          description: by_id threads + autoTagEmails
          example: 0
        jobId:
          type: string
          description: by_search job id
          example: 65a1b2c3d4e5f6a7b8c9d0f2
      required:
      - mode
      - status
    SearchEmailsDto:
      type: object
      properties:
        page:
          type: number
          default: 1
        perPage:
          type: number
          default: 10
        skip:
          type: number
          description: If skip/limit not provided, page/perPage will take precedence
        limit:
          type: number
          description: If skip/limit not provided, page/perPage will take precedence
        sortOrder:
          type: string
          enum:
          - asc
          - desc
          default: asc
        searchText:
          type: string
          description: Text search applied to all indexed fields (subject, body, snippet,
            from/to/cc/bcc). Supports the same AND / OR / NOT / -prefix, parentheses,
            "quoted phrases" and wildcards as the boolean query parser.
          example: (discriminate OR racist) AND ("Larry Graham" OR Robinson)
        folders:
          type: array
          items:
            type: string
        domains:
          description: Filter by participant domain(s) (part after @), any-of. Case-insensitive.
            Requires the `domains` field on the emailtext Atlas index.
          type: array
          items:
            type: string
        excludeDomains:
          description: Exclude items whose domain(s) match any of these (any-of).
            Case-insensitive. Must not overlap `domains`.
          type: array
          items:
            type: string
        tags:
          type: array
          items:
            type: string
        excludeTags:
          description: Exclude items carrying any of these tag ids. Must not overlap
            `tags`.
          type: array
          items:
            type: string
        untagged:
          type: boolean
          description: When truthy (1/true), return only emails with no tags attached.
            Takes precedence over the `tags` filter.
        participants:
          description: Filter by participant email address(es), any-of.
          type: array
          items:
            type: string
        excludeEmails:
          description: Exclude items whose participant address matches any of these
            (any-of). Must not overlap `participants`.
          type: array
          items:
            type: string
        startDate:
          type: string
          format: date-time
          description: Only return emails whose `date` is at/after this instant (inclusive).
            Accepts an ISO-8601 date-time.
          example: '2024-01-01T00:00:00.000Z'
        endDate:
          type: string
          format: date-time
          description: Only return emails whose `date` is at/before this instant (inclusive).
            Accepts an ISO-8601 date-time.
          example: '2024-12-31T23:59:59.999Z'
        sourceEmails:
          type: array
          items:
            type: string
        sortBy:
          type: string
          enum:
          - date
          - subject
          default: date
    BulkTagEmailsDto:
      type: object
      properties:
        addMode:
          type: string
          enum:
          - by_id
          - by_search
        tags:
          minItems: 1
          maxItems: 100
          description: Tag ids or names to ADD (additive union). Names created in
            the case if absent.
          type: array
          items:
            type: string
        ids:
          maxItems: 500
          description: Target ids (required when addMode=by_id).
          type: array
          items:
            type: string
        search:
          description: Search query (required when addMode=by_search).
          allOf:
          - "$ref": "#/components/schemas/SearchEmailsDto"
      required:
      - addMode
      - tags
    ListEmailThreadsDto:
      type: object
      properties:
        page:
          type: number
          default: 1
        perPage:
          type: number
          default: 10
        skip:
          type: number
          description: If skip/limit not provided, page/perPage will take precedence
        limit:
          type: number
          description: If skip/limit not provided, page/perPage will take precedence
        sortOrder:
          type: string
          enum:
          - asc
          - desc
          default: asc
        folders:
          type: array
          items:
            type: string
        domains:
          description: Filter by participant domain(s) (part after @), any-of. Case-insensitive.
          type: array
          items:
            type: string
        excludeDomains:
          description: Exclude items whose domain(s) match any of these (any-of).
            Case-insensitive. Must not overlap `domains`.
          type: array
          items:
            type: string
        tags:
          type: array
          items:
            type: string
        excludeTags:
          description: Exclude items carrying any of these tag ids. Must not overlap
            `tags`.
          type: array
          items:
            type: string
        participants:
          description: Filter by participant email address(es), any-of.
          type: array
          items:
            type: string
        excludeEmails:
          description: Exclude items whose participant address matches any of these
            (any-of). Must not overlap `participants`.
          type: array
          items:
            type: string
        starred:
          type: boolean
        unread:
          type: boolean
        untagged:
          type: boolean
          description: When truthy (1/true), return only threads with no tags attached.
            Takes precedence over the `tags` filter.
        startDate:
          type: string
          format: date-time
          description: Only return threads whose `latestMessageReceivedDate` is at/after
            this instant (inclusive). Accepts an ISO-8601 date-time.
          example: '2024-01-01T00:00:00.000Z'
        endDate:
          type: string
          format: date-time
          description: Only return threads whose `latestMessageReceivedDate` is at/before
            this instant (inclusive). Accepts an ISO-8601 date-time.
          example: '2024-12-31T23:59:59.999Z'
        sourceEmails:
          type: array
          items:
            type: string
        sortBy:
          type: string
          enum:
          - subject
          - latestMessageSentDate
          - latestMessageReceivedDate
          default: latestMessageReceivedDate
        includeLastMessage:
          type: boolean
          description: When truthy (1/true), populate lastMessageOrDraft (id/subject);
            otherwise it is returned as null. The message body is never included —
            use the thread `snippet` for previews, or fetch the body from the thread-emails
            endpoint.
        includeHidden:
          type: boolean
          default: false
          description: 'When true, include hidden threads (hiddenAt != null). Default
            false: hidden threads are excluded from the listing.'
        sharedStatus:
          type: string
          enum:
          - shared
          - pending
          - not_gated
          description: Opposing-council only. Narrows to threads in one sharing state;
            ignored for OL/OG callers.
    BulkTagThreadsDto:
      type: object
      properties:
        addMode:
          type: string
          enum:
          - by_id
          - by_search
        tags:
          minItems: 1
          maxItems: 100
          description: Tag ids or names to ADD (additive union).
          type: array
          items:
            type: string
        ids:
          maxItems: 500
          type: array
          items:
            type: string
        search:
          "$ref": "#/components/schemas/ListEmailThreadsDto"
        autoTagEmails:
          type: boolean
          default: false
          description: When true, additively tag every email of the affected thread(s).
      required:
      - addMode
      - tags
    BulkThreadVisibilityResponseEntity:
      type: object
      properties:
        action:
          type: string
          enum:
          - hide
          - unhide
        requested:
          type: number
          description: Number of ids in the request.
          example: 3
        matched:
          type: number
          description: Ids that belong to the case.
          example: 3
        skipped:
          type: number
          description: Ids skipped (not in the case).
          example: 0
        modified:
          type: number
          description: Documents whose hiddenAt actually changed.
          example: 3
      required:
      - action
      - requested
      - matched
      - skipped
      - modified
    BulkThreadVisibilityDto:
      type: object
      properties:
        action:
          type: string
          enum:
          - hide
          - unhide
        ids:
          minItems: 1
          maxItems: 1000
          description: Thread ids to hide/unhide. Ids not in the case are skipped.
          type: array
          items:
            type: string
      required:
      - action
      - ids
    BulkTagJobEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f2
        hostType:
          type: string
          enum:
          - emails
          - threads
        status:
          type: string
          enum:
          - queued
          - in_progress
          - completed
          - failed
        matchedDocuments:
          type: number
          example: 250
        attachmentsCreated:
          type: number
          example: 250
        autoTagEmails:
          type: boolean
          example: false
        emailsMatched:
          type: number
          example: 0
        emailAttachmentsCreated:
          type: number
          example: 0
        resolvedTagIds:
          example:
          - 65a1b2c3d4e5f6a7b8c9d0a1
          type: array
          items:
            type: string
        message:
          type: string
          nullable: true
          example:
        startedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T14:30:05.000Z'
        completedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T14:30:41.000Z'
        failedAt:
          format: date-time
          type: string
          nullable: true
          example:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
      required:
      - id
      - hostType
      - status
      - matchedDocuments
      - attachmentsCreated
      - autoTagEmails
      - emailsMatched
      - emailAttachmentsCreated
      - resolvedTagIds
      - message
      - startedAt
      - completedAt
      - failedAt
      - createdAt
    UpdateUserEmailDataTagsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Id of the case the email belongs to. Required so @CaseAccess
            can authorize the caller (an OC included) on the case.
        tags:
          minItems: 0
          maxItems: 100
          description: Array of tag ids or tag names. Replaces the email's tags with
            this list. Pass an empty array to remove all tags.
          type: array
          items:
            type: string
      required:
      - caseId
      - tags
    UpdateEmailThreadTagsDto:
      type: object
      properties:
        tags:
          minItems: 0
          maxItems: 100
          description: Tag ids or names. Replaces the thread tags with this list.
            Empty array removes all thread tags.
          type: array
          items:
            type: string
        autoTagEmails:
          type: boolean
          default: false
          description: When true, additively tag every email of this thread (emails
            are never removed from; empty tags leaves emails untouched).
      required:
      - tags
    DeleteEmailThreadsDto:
      type: object
      properties:
        threadIds:
          minItems: 1
          maxItems: 1000
          description: Array of email thread mongo ids to delete.
          type: array
          items:
            type: string
      required:
      - threadIds
    DiscordConversationEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        channelId:
          type: string
          example: '1197400000000000001'
        type:
          type: string
          example: '1'
          nullable: true
        recipients:
          example:
          - '1197412345678901234'
          - '1197498765432109876'
          type: array
          items:
            type: string
        name:
          type: string
          example: c1197400000000000001
          nullable: true
        displayName:
          type: string
          description: Not filled by any importer today, so always null.
          nullable: true
          example:
        messagesCount:
          type: number
          example: 42
        firstMessageDate:
          format: date-time
          type: string
          example: '2026-01-12T09:00:00.000Z'
          nullable: true
        lastMessageDate:
          format: date-time
          type: string
          example: '2026-01-15T18:02:00.000Z'
          nullable: true
        lastMessageText:
          type: string
          example: See you at 6.
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - channelId
      - type
      - recipients
      - name
      - displayName
      - messagesCount
      - firstMessageDate
      - lastMessageDate
      - lastMessageText
    DiscordMessageEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        discordConversation:
          type: string
          example: 507f1f77bcf86cd799439011
        discordUserProvider:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        externalId:
          type: string
          example: '1197412345678901234'
        text:
          type: string
          example: See you at 6.
          nullable: true
        date:
          format: date-time
          type: string
          example: '2026-01-15T18:02:00.000Z'
          nullable: true
        attachments:
          type: string
          example: https://cdn.discordapp.com/attachments/1197400000000000000/1197412345678901235/photo.png
          nullable: true
        metadata:
          type: object
          description: Not filled by any importer today, so always null.
          example:
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - discordConversation
      - discordUserProvider
      - externalId
      - text
      - date
      - attachments
      - metadata
    DiscordImportJobEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        extractionCode:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        file:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        createdBy:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        status:
          type: string
          enum:
          - not_started
          - in_progress
          - completed
          - failed
        startedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:25:00.000Z'
          nullable: true
        completedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        failedAt:
          format: date-time
          type: string
          example:
          nullable: true
        message:
          type: string
          example: Discord export is missing the messages folder
          nullable: true
        discordUserProvider:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        stats:
          type: object
          example:
            conversations: 12
            messages: 3480
            activities: 250
            tickets: 1
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - case
      - extractionCode
      - file
      - createdBy
      - status
      - startedAt
      - completedAt
      - failedAt
      - message
      - discordUserProvider
      - stats
    DiscordActivityEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        eventType:
          type: string
          example: app_opened
          nullable: true
        eventId:
          type: string
          example: AQEAAKzM7VxNfC0eEHn1example
        eventSource:
          type: string
          example: analytics
          nullable: true
        timestamp:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        rawData:
          type: object
          example:
            event_type: app_opened
            event_id: AQEAAKzM7VxNfC0eEHn1example
            timestamp: '"2026-01-15T14:30:00.000Z"'
            os: Windows
            client_version: 1.0.9032
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - eventType
      - eventId
      - eventSource
      - timestamp
      - rawData
    DiscordSupportTicketEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        discordUserProvider:
          type: string
          example: 507f1f77bcf86cd799439011
          description: Id of the Discord profile the ticket belongs to
        ticketId:
          type: number
          example: 41827364
        subject:
          type: string
          example: Unable to log in to my account
          nullable: true
        status:
          type: string
          example: solved
          nullable: true
        ticketCreatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        comments:
          type: array
          items:
            type: object
          example:
          - author: Discord Support
            comment: Thanks for reaching out, we have reset your two-factor settings.
            createdAt: '2026-01-15T14:30:00.000Z'
      required:
      - createdAt
      - updatedAt
      - id
      - discordUserProvider
      - ticketId
      - subject
      - status
      - ticketCreatedAt
      - comments
    DeleteDiscordProfileDto:
      type: object
      properties:
        discordProviderId:
          type: string
          description: DiscordUserProvider ID
      required:
      - discordProviderId
    ImportDiscordDataDto:
      type: object
      properties:
        fileId:
          type: string
          description: UserFile ID of the uploaded zip
        extractionCode:
          type: string
          description: Extraction code string (optional)
      required:
      - fileId
    RedditPostEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        externalId:
          type: string
          example: 1abc2de
        permalink:
          type: string
          example: https://www.reddit.com/r/personalfinance/comments/1abc2de/how_should_i_split_rent_with_a_roommate/
          nullable: true
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        ip:
          type: string
          example: 203.0.113.7
          nullable: true
        subreddit:
          type: string
          example: personalfinance
          nullable: true
        gildings:
          type: number
          example: 0
        title:
          type: string
          example: How should I split rent with a roommate?
          nullable: true
        url:
          type: string
          example: https://www.reddit.com/r/personalfinance/comments/1abc2de/how_should_i_split_rent_with_a_roommate/
          nullable: true
        body:
          type: string
          example: We moved in together last month and disagree on how to split it.
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - externalId
      - permalink
      - date
      - ip
      - subreddit
      - gildings
      - title
      - url
      - body
    RedditCommentEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        externalId:
          type: string
          example: k9x2abc
        permalink:
          type: string
          example: https://www.reddit.com/r/legaladvice/comments/17abcde/question_about_a_lease/k9x2abc/
          nullable: true
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        ip:
          type: string
          example: 203.0.113.42
          nullable: true
        subreddit:
          type: string
          example: legaladvice
          nullable: true
        gildings:
          type: number
          example: 0
        link:
          type: string
          example: https://www.reddit.com/r/legaladvice/comments/17abcde/question_about_a_lease/
          nullable: true
        parent:
          type: string
          example: k9x1zzz
          nullable: true
        body:
          type: string
          example: Check clause 4 before you sign.
          nullable: true
        media:
          type: string
          nullable: true
          example:
      required:
      - createdAt
      - updatedAt
      - id
      - externalId
      - permalink
      - date
      - ip
      - subreddit
      - gildings
      - link
      - parent
      - body
      - media
    RedditMessageEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        externalId:
          type: string
          example: t4_2a1b3c
        permalink:
          type: string
          example: https://www.reddit.com/message/messages/2a1b3c
          nullable: true
        threadId:
          type: string
          example: t4_2a1b3c
          nullable: true
        date:
          format: date-time
          type: string
          example: '2026-01-15T18:02:00.000Z'
          nullable: true
        ip:
          type: string
          example: 203.0.113.42
          nullable: true
        from:
          type: string
          example: example_user
          nullable: true
        to:
          type: string
          example: another_user
          nullable: true
        subject:
          type: string
          example: 'Re: the listing'
          nullable: true
        body:
          type: string
          example: Thanks for the tip.
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - externalId
      - permalink
      - threadId
      - date
      - ip
      - from
      - to
      - subject
      - body
    RedditChatMessageEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        externalId:
          type: string
          example: '2961408275'
        redditCreatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        redditUpdatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        username:
          type: string
          example: jane_doe_42
          nullable: true
        message:
          type: string
          example: Is the apartment still available?
          nullable: true
        threadParentMessageId:
          type: string
          example: '2961408100'
          nullable: true
        channelUrl:
          type: string
          example: sendbird_group_channel_2073012_5a1b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
          nullable: true
        subreddit:
          type: string
          example: legaladvice
          nullable: true
        channelName:
          type: string
          example: jane_doe_42, john_smith_7
          nullable: true
        conversationType:
          type: string
          example: direct
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - externalId
      - redditCreatedAt
      - redditUpdatedAt
      - username
      - message
      - threadParentMessageId
      - channelUrl
      - subreddit
      - channelName
      - conversationType
    RedditVoteEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        externalId:
          type: string
          example: 17abcde
        permalink:
          type: string
          example: https://www.reddit.com/r/legaladvice/comments/17abcde/question_about_a_lease/
          nullable: true
        direction:
          type: string
          example: up
          nullable: true
        voteType:
          type: string
          example: post
      required:
      - createdAt
      - updatedAt
      - id
      - externalId
      - permalink
      - direction
      - voteType
    RedditImportJobEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        case:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        extractionCode:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        file:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        createdBy:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        redditUserProvider:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        status:
          type: string
          enum:
          - not_started
          - in_progress
          - completed
          - failed
        startedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T14:25:00.000Z'
        completedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T14:30:00.000Z'
        failedAt:
          format: date-time
          type: string
          nullable: true
          example:
        message:
          type: string
          example: Import failed
          nullable: true
        stats:
          type: object
          example:
            posts: 42
            comments: 310
            messages: 18
            chatMessages: 95
            votes: 1200
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - case
      - extractionCode
      - file
      - createdBy
      - redditUserProvider
      - status
      - startedAt
      - completedAt
      - failedAt
      - message
      - stats
    DeleteRedditProfileDto:
      type: object
      properties:
        redditProviderId:
          type: string
          description: RedditUserProvider ID
      required:
      - redditProviderId
    ImportRedditDataDto:
      type: object
      properties:
        fileId:
          type: string
          description: UserFile ID of the uploaded zip
        extractionCode:
          type: string
          description: Extraction code string (optional)
      required:
      - fileId
    LinkedInUserProviderEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        linkedInProfileUrl:
          type: string
          example: linkedin-export-jane-doe
        firstName:
          type: string
          example: Jane
          nullable: true
        lastName:
          type: string
          example: Doe
          nullable: true
        headline:
          type: string
          example: Operations Manager at Acme Corp
          nullable: true
        industry:
          type: string
          example: Logistics and Supply Chain
          nullable: true
        email:
          type: string
          example: jane.doe@example.com
          nullable: true
        summary:
          type: string
          example: Operations leader with ten years in regional logistics.
          nullable: true
        geoLocation:
          type: string
          example: Springfield, Illinois, United States
          nullable: true
        avatar:
          type: object
          nullable: true
          example:
          description: Not loaded by any route, so always null.
        metadata:
          type: object
          example:
            address: 123 Main St, Springfield, IL
            zipCode: '62701'
            websites: "[PERSONAL:https://example.com]"
          nullable: true
        deletedAt:
          format: date-time
          type: string
          nullable: true
          example:
        deletedBy:
          type: string
          nullable: true
          example:
        ocSharedStatus:
          type: string
          nullable: true
          enum:
          - shared
          - pending
          - not_gated
          description: 'Opposing-council only: whether this provider has been shared
            with the OL. `null` for OL/OG callers — same convention as every other
            item listing. Provider-level: every item under it shares this status,
            since approval here is all-or-nothing.'
      required:
      - createdAt
      - updatedAt
      - id
      - linkedInProfileUrl
      - firstName
      - lastName
      - headline
      - industry
      - email
      - summary
      - geoLocation
      - avatar
      - metadata
      - deletedAt
      - deletedBy
    LinkedInImportCoverageResponseEntity:
      type: object
      properties:
        fileId:
          type: string
          description: UserFile ID the coverage is reported for
        importedAllDataTypes:
          type: boolean
          description: True when a prior job imported every data type
        importedDataTypes:
          description: Union of data types imported across completed jobs
          type: array
          items:
            type: string
        importedAllConversations:
          type: boolean
          description: True when a prior job imported every conversation
        importedConversationIds:
          description: Union of conversation IDs imported across completed jobs
          type: array
          items:
            type: string
      required:
      - fileId
      - importedAllDataTypes
      - importedDataTypes
      - importedAllConversations
      - importedConversationIds
    ImportLinkedInDataDto:
      type: object
      properties:
        fileId:
          type: string
          description: UserFile ID of the uploaded zip
        extractionCode:
          type: string
          description: Extraction code string (optional)
        selectedDataTypes:
          description: Selected data types to import (optional)
          type: array
          items:
            type: string
        selectedConversationIds:
          description: Specific conversation IDs to import (omit to import all)
          type: array
          items:
            type: string
      required:
      - fileId
    ExportLinkedInDataDto:
      type: object
      properties:
        linkedInExportType:
          type: string
          description: LinkedIn data type to export.
          enum:
          - conversations
          - connections
          - job-applications
          - rich-media
          - saved-jobs
          - saved-job-alerts
          - saved-items
        sortBy:
          type: string
          description: Sort order for exported records.
          enum:
          - old-to-new
          - new-to-old
        tags:
          description: Filter by tag IDs. Only exports records that have all of the
            provided tags.
          type: array
          items:
            type: string
        exportConfig:
          type: array
          description: Export configuration flags (e.g. include tags in output).
          items:
            type: string
            enum:
            - phone-numbers
            - comments
            - tags
            - app-icons
            - device-name
            - images
            - hide-attachment-links
            - extra-texts
            - line-break
            - device-logs
            - custom-entry
            - recently-deleted
            - deleted-messages-inline
            - deleted-messages-page
            - deleted-messages-inline-page
            - ignore-conversations
            - search-history-page
            - hide-page-numbers
            - print-footer-work-timestamp
        custodianName:
          type: string
          description: Custodian name to display on the cover page.
        conversationIds:
          description: Specific conversation IDs to export. Only used when linkedInExportType
            is "conversations". When omitted, all conversations are exported.
          type: array
          items:
            type: string
      required:
      - linkedInExportType
    LinkedInMessageSearchResultEntity:
      type: object
      properties:
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        from:
          type: string
          example: Jane Doe
        to:
          type: string
          example: John Smith
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        content:
          type: string
          example: Thanks for connecting. Are you free for a call next week?
        folder:
          type: string
          nullable: true
          example: INBOX
        isDraft:
          type: boolean
          example: false
        attachments:
          type: string
          example: ''
        conversationId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        conversationTitle:
          type: string
          nullable: true
          example: Project update
        participants:
          example:
          - Jane Doe
          - John Smith
          type: array
          items:
            type: string
        profileId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e3
      required:
      - id
      - from
      - to
      - date
      - content
      - folder
      - isDraft
      - attachments
      - conversationId
      - conversationTitle
      - participants
      - profileId
    LinkedInConversationEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        conversationId:
          type: string
          example: 2-ZGVmYXVsdA==
        title:
          type: string
          example: Introduction
          nullable: true
        participants:
          type: array
          items:
            type: object
          example:
          - name: Jane Doe
            profileUrl: https://www.linkedin.com/in/jane-doe
        messagesCount:
          type: number
          example: 4
        firstMessageDate:
          format: date-time
          type: string
          example: '2026-01-12T09:00:00.000Z'
          nullable: true
        lastMessageDate:
          format: date-time
          type: string
          example: '2026-01-15T09:12:00.000Z'
          nullable: true
        lastMessageText:
          type: string
          example: Happy to connect.
          nullable: true
        tags:
          example:
          - 507f1f77bcf86cd799439011
          type: array
          items:
            type: string
        commentsCount:
          type: number
          example: 0
        comments:
          type: array
          description: Comment references on the conversation
          items:
            type: object
          example:
          - _id: 65a1b2c3d4e5f6a7b8c9d0e3
            case: 507f1f77bcf86cd799439011
            comment: 65a1b2c3d4e5f6a7b8c9d0e2
        starredAt:
          format: date-time
          type: string
          nullable: true
          example:
        starredBy:
          type: string
          nullable: true
          example:
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
      required:
      - createdAt
      - updatedAt
      - id
      - conversationId
      - title
      - participants
      - messagesCount
      - firstMessageDate
      - lastMessageDate
      - lastMessageText
      - tags
      - commentsCount
      - comments
      - starredAt
      - starredBy
    LinkedInMessageEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        from:
          type: string
          example: Jane Doe
          nullable: true
        senderProfileUrl:
          type: string
          example: https://www.linkedin.com/in/jane-doe-example
          nullable: true
        to:
          type: string
          example: John Roe
          nullable: true
        recipientProfileUrls:
          example:
          - https://www.linkedin.com/in/john-roe-example
          type: array
          items:
            type: string
        date:
          format: date-time
          type: string
          example: '2026-01-15T09:12:00.000Z'
        subject:
          type: string
          example: Introduction
          nullable: true
        content:
          type: string
          example: Happy to connect.
          nullable: true
        folder:
          type: string
          example: INBOX
          nullable: true
        attachments:
          type: string
          example: https://www.linkedin.com/dms/prv/attachment/v2/D4E06AQExample/messaging-attachmentFile/0/1700000000000
          nullable: true
        isDraft:
          type: boolean
          example: false
      required:
      - createdAt
      - updatedAt
      - id
      - from
      - senderProfileUrl
      - to
      - recipientProfileUrls
      - date
      - subject
      - content
      - folder
      - attachments
      - isDraft
    LinkedInConnectionEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        firstName:
          type: string
          example: Jane
          nullable: true
        lastName:
          type: string
          example: Doe
          nullable: true
        profileUrl:
          type: string
          example: https://www.linkedin.com/in/jane-doe-example
          nullable: true
        email:
          type: string
          example:
          nullable: true
        company:
          type: string
          example: Example Corp
          nullable: true
        position:
          type: string
          example: Paralegal
          nullable: true
        connectedOn:
          format: date-time
          type: string
          example: '2025-11-03T00:00:00.000Z'
          nullable: true
        tags:
          example:
          - 65a1b2c3d4e5f6a7b8c9d0e2
          type: array
          items:
            type: string
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
      required:
      - createdAt
      - updatedAt
      - id
      - firstName
      - lastName
      - profileUrl
      - email
      - company
      - position
      - connectedOn
      - tags
    LinkedInCommentEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        postLink:
          type: string
          example: https://www.linkedin.com/feed/update/urn:li:activity:7012345678901234567
        message:
          type: string
          example: Congratulations on the new role!
          nullable: true
        tags:
          example:
          - 65a1b2c3d4e5f6a7b8c9d0e2
          type: array
          items:
            type: string
        attachedTags:
          type: array
          description: 'Tag records exactly as stored: they include removed tags (removedAt
            set) and tags from both sides of the case, and carry no tag name.'
          items:
            type: object
            properties:
              tagId:
                type: string
                example: 65a1b2c3d4e5f6a7b8c9d0e2
              addedBy:
                type: string
                enum:
                - user
                - ai
                - auto
                - auto-import
                - system
                - default
              addedById:
                type: string
                nullable: true
                example: 507f1f77bcf86cd799439011
              addedAt:
                type: string
                format: date-time
                example: '2026-01-15T14:30:00.000Z'
              removedAt:
                type: string
                format: date-time
                nullable: true
                example:
              removedBy:
                type: string
                enum:
                - user
                - ai
                - auto
                - auto-import
                - system
                - default
                nullable: true
                example:
              removedById:
                type: string
                nullable: true
                example:
          example:
          - tagId: 65a1b2c3d4e5f6a7b8c9d0e2
            addedBy: user
            addedById: 507f1f77bcf86cd799439011
            addedAt: '2026-01-15T14:30:00.000Z'
            removedAt:
            removedBy:
            removedById:
      required:
      - createdAt
      - updatedAt
      - id
      - date
      - postLink
      - message
      - tags
      - attachedTags
    LinkedInRichMediaEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        dateTime:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        description:
          type: string
          example: Profile photo
          nullable: true
        mediaLink:
          type: string
          example: https://media.licdn.com/dms/image/v2/D4E03AQExample/profile-displayphoto-shrink_800_800/0/1700000000000
          nullable: true
        mediaType:
          type: string
          example: photo
          nullable: true
        tags:
          example:
          - 65a1b2c3d4e5f6a7b8c9d0e2
          type: array
          items:
            type: string
        mediaFile:
          type: string
          nullable: true
          example: 65a1b2c3d4e5f6a7b8c9d0e2
      required:
      - createdAt
      - updatedAt
      - id
      - dateTime
      - description
      - mediaLink
      - mediaType
      - tags
    LinkedInSavedJobEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        savedDate:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        jobUrl:
          type: string
          example: https://www.linkedin.com/jobs/view/3812345678
        jobTitle:
          type: string
          example: Paralegal
          nullable: true
        companyName:
          type: string
          example: Acme Law
          nullable: true
        tags:
          example:
          - 507f1f77bcf86cd799439011
          type: array
          items:
            type: string
      required:
      - createdAt
      - updatedAt
      - id
      - savedDate
      - jobUrl
      - jobTitle
      - companyName
      - tags
    LinkedInSavedItemEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        url:
          type: string
          example: https://www.linkedin.com/feed/update/urn:li:activity:7151234567890123456
        createdTime:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - url
      - createdTime
    LinkedInJobApplicationEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        applicationDate:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        contactEmail:
          type: string
          example: jane.doe@example.com
          nullable: true
        contactPhone:
          type: string
          example: "+15555550100"
          nullable: true
        companyName:
          type: string
          example: Acme Corp
          nullable: true
        jobTitle:
          type: string
          example: Paralegal
          nullable: true
        jobUrl:
          type: string
          example: https://www.linkedin.com/jobs/view/3812345678
          nullable: true
        resumeName:
          type: string
          example: Jane_Doe_Resume.pdf
          nullable: true
        questionsAndAnswers:
          type: array
          items:
            type: object
          example:
          - answer: '5'
            question: How many years of work experience do you have?
        tags:
          example:
          - 65a1b2c3d4e5f6a7b8c9d0e2
          type: array
          items:
            type: string
        commentsCount:
          type: number
          example: 1
        comments:
          type: array
          items:
            type: object
          example:
          - _id: 65a1b2c3d4e5f6a7b8c9d0e3
            case: 507f1f77bcf86cd799439011
            comment: 65a1b2c3d4e5f6a7b8c9d0e2
      required:
      - createdAt
      - updatedAt
      - id
      - applicationDate
      - contactEmail
      - contactPhone
      - companyName
      - jobTitle
      - jobUrl
      - resumeName
      - questionsAndAnswers
      - tags
      - commentsCount
      - comments
    LinkedInSavedJobAlertEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        savedSearchId:
          type: string
          example: '1234567890'
          nullable: true
        alertParameters:
          type: string
          example: keywords:paralegal,location:Chicago, Illinois
          nullable: true
        queryContext:
          type: string
          example: JOB_SEARCH
          nullable: true
      required:
      - createdAt
      - updatedAt
      - id
      - savedSearchId
      - alertParameters
      - queryContext
    UpdateLinkedInTagsDto:
      type: object
      properties:
        tagId:
          type: string
          description: Tag ID to toggle on the resource
      required:
      - tagId
    DeleteLinkedInProfileDto:
      type: object
      properties:
        action:
          type: string
          description: 'Action to perform: "archive" or "delete"'
      required:
      - action
    AiChatMessageSearchResultEntity:
      type: object
      properties:
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        role:
          type: string
          enum:
          - user
          - assistant
          - system
          - tool
          example: user
        text:
          type: string
          example: Summarise clause 4 of the lease in plain English.
        highlightedText:
          type: string
          example: Summarise [hl]clause 4[/hl] of the lease in plain English.
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        senderName:
          type: string
          nullable: true
          example: Jane Doe
        model:
          type: string
          nullable: true
          example: gpt-4o
        conversationId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        conversationTitle:
          type: string
          nullable: true
          example: Lease review
        providerId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e3
      required:
      - id
      - role
      - text
      - date
      - senderName
      - model
      - conversationId
      - conversationTitle
      - providerId
    AiChatConversationEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        aiChatProvider:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        externalId:
          type: string
          example: conv_01HZX3K9P7
        title:
          type: string
          example: Lease review
          nullable: true
        createdDate:
          format: date-time
          type: string
          example: '2026-01-15T14:28:00.000Z'
          nullable: true
        updatedDate:
          format: date-time
          type: string
          example: '2026-01-15T14:41:00.000Z'
          nullable: true
        model:
          type: string
          example: gpt-4o
          nullable: true
        projectId:
          type: string
          example: 0f8e2c1a-6b7d-4e3f-9a1b-2c3d4e5f6a7b
          nullable: true
        projectName:
          type: string
          example: Contract reviews
          nullable: true
        systemPrompt:
          type: string
          example: You are a helpful assistant.
          nullable: true
        isShared:
          type: boolean
          description: Not filled by any importer today, so always false.
          example: false
        messagesCount:
          type: number
          example: 12
        firstMessageDate:
          format: date-time
          type: string
          example: '2026-01-15T14:28:00.000Z'
          nullable: true
        lastMessageDate:
          format: date-time
          type: string
          example: '2026-01-15T14:41:00.000Z'
          nullable: true
        lastMessageText:
          type: string
          example: Here is a plain-English summary of clause 4.
          nullable: true
        totalInputTokens:
          type: number
          description: Not filled by any importer today, so always 0.
          example: 0
        totalOutputTokens:
          type: number
          description: Not filled by any importer today, so always 0.
          example: 0
        tags:
          type: array
          description: Tags on the conversation, each with its name (legacy; attachedTags
            is the side-aware list).
          items:
            type: object
            properties:
              _id:
                type: string
                example: 65a1b2c3d4e5f6a7b8c9d0a1
              name:
                type: string
                example: Responsive
          example:
          - _id: 65a1b2c3d4e5f6a7b8c9d0a1
            name: Responsive
        commentsCount:
          type: number
          example: 2
        comments:
          type: array
          description: Comment references on the conversation
          items:
            type: object
          example:
          - _id: 65a1b2c3d4e5f6a7b8c9d0e3
            case: 507f1f77bcf86cd799439011
            comment: 65a1b2c3d4e5f6a7b8c9d0e2
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
      required:
      - createdAt
      - updatedAt
      - id
      - aiChatProvider
      - externalId
      - title
      - createdDate
      - updatedDate
      - model
      - projectId
      - projectName
      - systemPrompt
      - isShared
      - messagesCount
      - firstMessageDate
      - lastMessageDate
      - lastMessageText
      - totalInputTokens
      - totalOutputTokens
    AiChatMessageEntity:
      type: object
      properties:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        aiChatConversation:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        aiChatProvider:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        externalId:
          type: string
          example: msg_01HZX3K9Q2
        role:
          type: string
          enum:
          - user
          - assistant
          - system
          - tool
          example: user
        text:
          type: string
          example: Summarise clause 4 of the lease in plain English.
          nullable: true
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
          nullable: true
        senderName:
          type: string
          example: Jane Doe
          nullable: true
        senderId:
          type: string
          nullable: true
          example:
          description: Not filled by any importer today, so always null.
        model:
          type: string
          example: gpt-4o
          nullable: true
        inputTokens:
          type: number
          example: 38
        outputTokens:
          type: number
          example: 0
        toolUses:
          type: array
          items:
            type: object
          example:
          - toolName: web_search
            toolInput:
              query: statute of limitations Illinois
            toolOutput:
            toolCallId: toolu_01A2B3C4D5
        attachments:
          type: array
          items:
            type: object
          example:
          - fileName: contract-draft.pdf
            fileType: application/pdf
            fileSize: 48213
            externalUrl:
            userFile: 507f1f77bcf86cd799439011
        artifacts:
          example:
          - 65a1b2c3d4e5f6a7b8c9d0e2
          type: array
          items:
            type: string
        highlightedText:
          type: string
          example: Summarise [hl]clause 4[/hl] of the lease in plain English.
      required:
      - createdAt
      - updatedAt
      - id
      - aiChatConversation
      - aiChatProvider
      - externalId
      - role
      - text
      - date
      - senderName
      - senderId
      - model
      - inputTokens
      - outputTokens
      - toolUses
      - attachments
      - artifacts
    DeleteAiChatProfileDto:
      type: object
      properties:
        aiChatProviderId:
          type: string
          description: AiChatProvider ID to archive
      required:
      - aiChatProviderId
    UpdateAiChatConversationTagsDto:
      type: object
      properties:
        tags:
          description: Array of tag names or tag IDs to apply
          type: array
          items:
            type: string
      required:
      - tags
    AiBackfillRequestDto:
      type: object
      properties:
        caseIds:
          type: array
          items:
            type: string
        organizationIds:
          type: array
          items:
            type: string
        dryRun:
          type: boolean
          default: false
        confirmUnrestricted:
          type: boolean
          default: false
        force:
          type: boolean
          default: false
    AiCaseResetDto:
      type: object
      properties:
        dryRun:
          type: boolean
          default: false
          description: When true, return the counts that would have been deleted without
            actually deleting anything.
    AiSearchExpandedNeighborEntity:
      type: object
      properties:
        messageId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f2
        text:
          type: string
          example: See you at the exchange on Friday.
        timestamp:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
      required:
      - messageId
      - text
      - timestamp
    AiSearchExpandedContextEntity:
      type: object
      properties:
        before:
          type: array
          items:
            "$ref": "#/components/schemas/AiSearchExpandedNeighborEntity"
        after:
          type: array
          items:
            "$ref": "#/components/schemas/AiSearchExpandedNeighborEntity"
      required:
      - before
      - after
    AiSearchItemEntity:
      type: object
      properties:
        kind:
          type: string
          enum:
          - message
          - chunk
          example: message
        id:
          type: string
          description: Primary id of the item — DeviceMessage._id when kind=message,
            AiConversationChunk._id when kind=chunk
          example: 65a1b2c3d4e5f6a7b8c9d0f1
        messageId:
          type: string
          description: Back-compat alias for `id` — Phase 1 clients used this
          example: 65a1b2c3d4e5f6a7b8c9d0f1
        source:
          type: string
          enum:
          - device_message
          - linkedin
          - reddit
          - discord
          - email
          - ai_chat
          example: device_message
        conversationId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f0
        sender:
          type: string
          example: "+15555550123"
        timestamp:
          format: date-time
          type: string
          example: '2026-01-15T10:15:00.000Z'
        score:
          type: number
          example: 0.83
        snippet:
          type: string
          example: I will not be at the pickup tomorrow.
        topics:
          description: Topic tags — only present when kind=chunk
          example:
          - custody
          - scheduling
          type: array
          items:
            type: string
        expandedContext:
          "$ref": "#/components/schemas/AiSearchExpandedContextEntity"
      required:
      - kind
      - id
      - messageId
      - source
      - conversationId
      - score
      - snippet
    AiSearchResponseEntity:
      type: object
      properties:
        items:
          type: array
          items:
            "$ref": "#/components/schemas/AiSearchItemEntity"
        candidateCount:
          type: number
          example: 50
        vectorSearchTopN:
          type: number
          example: 50
        rerankApplied:
          type: boolean
          example: true
        queryEmbeddingModel:
          type: string
          nullable: true
          example: cohere.embed-english-v3
      required:
      - items
      - candidateCount
      - vectorSearchTopN
      - rerankApplied
    AiSearchDateRangeDto:
      type: object
      properties:
        start:
          format: date-time
          type: string
        end:
          format: date-time
          type: string
    AiSearchFiltersDto:
      type: object
      properties:
        sources:
          type: array
          items:
            type: string
            enum:
            - device_message
            - linkedin
            - reddit
            - discord
            - email
            - ai_chat
        dateRange:
          "$ref": "#/components/schemas/AiSearchDateRangeDto"
        participants:
          type: array
          items:
            type: string
        conversationIds:
          type: array
          items:
            type: string
        topics:
          type: array
          items:
            type: string
        legalRelevance:
          type: array
          items:
            type: string
    AiSearchExpandContextDto:
      type: object
      properties:
        before:
          type: number
          default: 0
        after:
          type: number
          default: 0
    AiSearchDto:
      type: object
      properties:
        query:
          type: string
        filters:
          "$ref": "#/components/schemas/AiSearchFiltersDto"
        granularity:
          type: string
          enum:
          - messages
          - chunks
          - mixed
          default: messages
        topK:
          type: number
          default: 50
          minimum: 1
          maximum: 100
        rerank:
          type: boolean
          default: true
        expandContext:
          "$ref": "#/components/schemas/AiSearchExpandContextDto"
      required:
      - query
    AiChunkingPhaseEntity:
      type: object
      properties:
        conversationsTotal:
          type: number
          example: 40
        conversationsDone:
          type: number
          example: 40
        percent:
          type: number
          description: 0–100
          example: 100
        startedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:15:00.000Z'
        finishedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:20:00.000Z'
      required:
      - conversationsTotal
      - conversationsDone
      - percent
    AiEmbeddingPhaseEntity:
      type: object
      properties:
        messagesTotal:
          type: number
          example: 12000
        messagesDone:
          type: number
          example: 9000
        percent:
          type: number
          description: 0–100
          example: 75
        startedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:15:00.000Z'
        finishedAt:
          format: date-time
          type: string
          nullable: true
          example:
        ratePerSec:
          type: number
          nullable: true
          example: 25
        etaSeconds:
          type: number
          nullable: true
          description: Estimated seconds remaining for the embedding phase
          example: 120
      required:
      - messagesTotal
      - messagesDone
      - percent
    AiSummarizationPhaseEntity:
      type: object
      properties:
        chunksTotal:
          type: number
          example: 300
        chunksDone:
          type: number
          example: 0
        percent:
          type: number
          description: 0–100
          example: 0
        startedAt:
          format: date-time
          type: string
          nullable: true
          example:
        finishedAt:
          format: date-time
          type: string
          nullable: true
          example:
      required:
      - chunksTotal
      - chunksDone
      - percent
    AiCaseProgressEntity:
      type: object
      properties:
        case:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        organization:
          type: string
          nullable: true
          example: 65a1b2c3d4e5f6a7b8c9d0e3
        source:
          type: string
          enum:
          - device_message
          - linkedin
          - reddit
          - discord
          - email
          - ai_chat
          example: device_message
        status:
          type: string
          enum:
          - not_started
          - in_progress
          - ready
          - failed
          example: in_progress
        chunking:
          "$ref": "#/components/schemas/AiChunkingPhaseEntity"
        embedding:
          "$ref": "#/components/schemas/AiEmbeddingPhaseEntity"
        summarization:
          "$ref": "#/components/schemas/AiSummarizationPhaseEntity"
        overallPercent:
          type: number
          description: Combined progress across the running phases (0–100)
          example: 58
        startedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:15:00.000Z'
        lastProgressAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:20:00.000Z'
        finishedAt:
          format: date-time
          type: string
          nullable: true
          example:
        errorCount:
          type: number
          example: 0
        lastError:
          type: string
          nullable: true
          example:
      required:
      - case
      - source
      - status
      - chunking
      - embedding
      - summarization
      - overallPercent
      - errorCount
    AiAttorneyNotesDto:
      type: object
      properties:
        theory:
          type: string
          nullable: true
        strategy:
          type: string
          nullable: true
        openQuestions:
          type: array
          items:
            type: string
    LegalClaimElementDto:
      type: object
      properties:
        id:
          type: string
        description:
          type: string
      required:
      - id
      - description
    LegalClaimDto:
      type: object
      properties:
        id:
          type: string
        claim:
          type: string
        statuteCitation:
          type: string
          nullable: true
        elements:
          type: array
          items:
            "$ref": "#/components/schemas/LegalClaimElementDto"
        source:
          type: string
          enum:
          - complaint
          - attorney_stated
          - amended_filing
      required:
      - id
      - claim
    KeyPartyDto:
      type: object
      properties:
        name:
          type: string
        aliases:
          type: array
          items:
            type: string
        role:
          type: string
          enum:
          - petitioner
          - respondent
          - plaintiff
          - defendant
          - child
          - witness
          - third_party
          - other
        handles:
          type: array
          items:
            type: string
        significance:
          type: string
          nullable: true
      required:
      - name
    KeyDateDto:
      type: object
      properties:
        date:
          format: date-time
          type: string
        event:
          type: string
        significance:
          type: string
          nullable: true
      required:
      - date
      - event
    RelevantDateRangeDto:
      type: object
      properties:
        start:
          format: date-time
          type: string
        end:
          format: date-time
          type: string
    AiCaseContextPatchDto:
      type: object
      properties:
        caseType:
          type: string
          nullable: true
        jurisdiction:
          type: string
          nullable: true
        theoryOfCase:
          type: string
          nullable: true
        whatToProve:
          type: array
          items:
            type: string
        legalClaims:
          type: array
          items:
            "$ref": "#/components/schemas/LegalClaimDto"
        keyParties:
          type: array
          items:
            "$ref": "#/components/schemas/KeyPartyDto"
        keyDates:
          type: array
          items:
            "$ref": "#/components/schemas/KeyDateDto"
        relevantDateRange:
          "$ref": "#/components/schemas/RelevantDateRangeDto"
        sourceDocuments:
          type: array
          items:
            type: string
    StartFilingExtractionDto:
      type: object
      properties:
        caseFileId:
          type: string
          description: Id of the CaseFile to extract from
      required:
      - caseFileId
    ApplyFilingExtractionDto:
      type: object
      properties:
        fieldsToApply:
          description: Optional whitelist of fields to apply (e.g. ["legalClaims","keyParties"]).
            Defaults to all extractable fields.
          type: array
          items:
            type: string
    RebuildEvidentiaryElementDto:
      type: object
      properties:
        claimId:
          type: string
        elementId:
          type: string
      required:
      - claimId
      - elementId
    AttorneyOverrideDto:
      type: object
      properties:
        override:
          type: string
          nullable: true
          description: Attorney-authored override; pass null to clear it.
    SuggestTagsDto:
      type: object
      properties:
        forceRefresh:
          type: boolean
          default: false
          description: Bypass the cache and force a fresh Claude call.
    AiPromptUpdateDto:
      type: object
      properties:
        systemPrompt:
          type: string
        toolDescription:
          type: string
        toolInputSchema:
          type: object
          description: Claude tool-use input_schema (JSON Schema). Renaming or removing
            top-level properties triggers 400.
        model:
          type: string
          description: "'haiku' / 'sonnet' alias or a literal Bedrock model id"
        maxTokens:
          type: number
          minimum: 1
          maximum: 32000
        temperature:
          type: number
          minimum: 0
          maximum: 2
        notes:
          type: string
    AiPromptCreateDraftDto:
      type: object
      properties:
        systemPrompt:
          type: string
        toolDescription:
          type: string
        toolInputSchema:
          type: object
          description: Claude tool-use input_schema (JSON Schema). Renaming or removing
            top-level properties triggers 400.
        model:
          type: string
          description: "'haiku' / 'sonnet' alias or a literal Bedrock model id"
        maxTokens:
          type: number
          minimum: 1
          maximum: 32000
        temperature:
          type: number
          minimum: 0
          maximum: 2
        notes:
          type: string
        seedFromVersionSlug:
          type: string
          description: Seed the new draft from this version slug. Defaults to the
            currently active version.
    SandboxPublicTokenResponseEntity:
      type: object
      properties:
        publicToken:
          type: string
          description: Temporary public_token to use with the exchange endpoint.
        requestId:
          type: string
          description: Request identifier for debugging and support.
      required:
      - publicToken
      - requestId
    SandboxPublicTokenDto:
      type: object
      properties:
        institutionId:
          type: string
          description: Plaid institution ID to simulate. Defaults to ins_109508 (First
            Platypus Bank).
          default: ins_109508
        initialProducts:
          type: array
          default:
          - transactions
          - liabilities
          - investments
          - statements
          description: Plaid products to enable on the simulated Item. Defaults to
            [transactions, liabilities, investments, statements].
          items:
            type: string
            enum:
            - assets
            - auth
            - beacon
            - employment
            - identity
            - income_verification
            - identity_verification
            - investments
            - liabilities
            - payment_initiation
            - standing_orders
            - signal
            - statements
            - transactions
            - transfer
            - cra_base_report
            - cra_income_insights
            - cra_cashflow_insights
            - cra_lend_score
            - cra_partner_insights
            - cra_network_insights
            - cra_monitoring
            - layer
            - protect_linked_bank
    InvestmentTransactionDataEntity:
      type: object
      properties:
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        date:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        type:
          type: string
          description: Transaction type (buy, sell, dividend, etc.)
          example: buy
        subtype:
          type: string
          example: buy
        name:
          type: string
          description: Transaction description / name.
          example: BUY Example Total Market ETF
        tickerSymbol:
          type: string
          nullable: true
          example: EXTM
        securityName:
          type: string
          nullable: true
          example: Example Total Market ETF
        securityType:
          type: string
          nullable: true
          example: etf
        quantity:
          type: number
          nullable: true
          example: 10
        price:
          type: number
          nullable: true
          description: Price per unit of the security, in currency units (dollars
            for USD), as Plaid sends it.
          example: 250.5
        amount:
          type: number
          description: In currency units (dollars for USD), as Plaid sends it. Positive
            when cash leaves the account (e.g. a buy), negative when cash comes in
            (e.g. a sell).
          example: 2505
        fees:
          type: number
          nullable: true
          description: In currency units (dollars for USD), as Plaid sends it.
          example: 0
        isoCurrencyCode:
          type: string
          nullable: true
          example: USD
        financialAccountId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
      required:
      - id
      - date
      - type
      - subtype
      - name
      - amount
      - financialAccountId
    RemovePlaidItemDto:
      type: object
      properties:
        reasonCode:
          type: string
          enum:
          - FRAUD_FIRST_PARTY
          - FRAUD_FALSE_IDENTITY
          - FRAUD_ABUSE
          - FRAUD_OTHER
          - CONNECTION_IS_NON_FUNCTIONAL
          - OTHER
          description: Reason for removing the Plaid Item
        reasonNote:
          type: string
          description: Additional context about the reason. Must not contain PII.
          maxLength: 512
    AccountDataEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e1
        plaidItemId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0a0
        plaidAccountId:
          type: string
          example: BxBXxLj1m4HMXBm9WZZmCWVbPjX16EHwv99vp
        balanceAvailable:
          type: number
          nullable: true
          example: 1250.4
        balanceCurrent:
          type: number
          nullable: true
          example: 1310.75
        balanceLastUpdated:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T14:30:00.000Z'
        balanceLimit:
          type: number
          nullable: true
          example:
        createdAt:
          format: date-time
          type: string
          example: '2026-01-10T09:00:00.000Z'
        holderCategory:
          type: string
          nullable: true
          example: personal
        currency:
          type: string
          nullable: true
          example: USD
        name:
          type: string
          example: Everyday Checking
        officialName:
          type: string
          nullable: true
          example: Example Bank Everyday Checking
        accountMask:
          type: string
          nullable: true
          description: Last digits of the account number, as the bank shows them
          example: '0042'
        subtype:
          type: string
          nullable: true
          example: checking
        type:
          type: string
          enum:
          - depository
          - credit
          - loan
          - investment
          - brokerage
          - other
          - brokerage
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        financialAccountProviderId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        financialAccountProviderType:
          type: string
          enum:
          - plaid
        financialAccountProviderName:
          type: string
          nullable: true
          example: Example Bank
        extractionCodeId:
          type: string
          nullable: true
          example: 65a1b2c3d4e5f6a7b8c9d0b5
        ocSharedStatus:
          type: string
          nullable: true
          enum:
          - shared
          - pending
          - not_gated
          description: 'Opposing-council only: whether this account has been shared
            with the OL. `null` for OL/OG callers — same convention as every other
            item listing. Transactions inherit their account''s status, since FINANCIAL
            approvals are keyed on account ids.'
      required:
      - id
      - plaidItemId
      - plaidAccountId
      - createdAt
      - name
      - type
      - updatedAt
      - financialAccountProviderId
      - financialAccountProviderType
    TransactionTagData:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0a1
        name:
          type: string
          example: Responsive
      required:
      - id
      - name
    HoldingDataEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f1
        financialAccount:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        assetName:
          type: string
          nullable: true
          example: Example Index Fund
        tickerSymbol:
          type: string
          nullable: true
          example: EXIF
        type:
          type: string
          nullable: true
          example: mutual fund
        subtype:
          type: string
          nullable: true
          example:
        quantity:
          type: number
          nullable: true
          example: 12.5
        price:
          type: number
          nullable: true
          example: 101.2
        totalValue:
          type: number
          nullable: true
          example: 1265
        costBasis:
          type: number
          nullable: true
          example: 1180
        currency:
          type: string
          nullable: true
          example: USD
        tags:
          type: array
          items:
            "$ref": "#/components/schemas/TransactionTagData"
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
      required:
      - id
      - financialAccount
      - assetName
      - tickerSymbol
      - type
      - subtype
      - quantity
      - price
      - totalValue
      - costBasis
      - currency
    AprEntity:
      type: object
      properties:
        aprPercentage:
          type: number
          example: 24.99
        aprType:
          type: string
          example: purchase_apr
        balanceSubjectToApr:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 1562.32
        interestChargeAmount:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 32.48
      required:
      - aprPercentage
      - aprType
    CreditLiabilityEntity:
      type: object
      properties:
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        financialAccount:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        plaidAccountId:
          type: string
          example: BxBXxLj1m4HMXBm9WZZmCWVbPjX16EHwv99vp
        liabilityType:
          type: string
          example: credit
        aprs:
          type: array
          items:
            "$ref": "#/components/schemas/AprEntity"
        isOverdue:
          type: boolean
          nullable: true
          example: false
        lastPaymentAmount:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 168.25
        lastPaymentDate:
          type: string
          nullable: true
          example: '2026-01-05'
        lastStatementIssueDate:
          type: string
          nullable: true
          example: '2026-01-10'
        lastStatementBalance:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 1708.77
        minimumPaymentAmount:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 40.25
        nextPaymentDueDate:
          type: string
          nullable: true
          example: '2026-02-05'
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        tags:
          type: array
          items:
            "$ref": "#/components/schemas/TransactionTagData"
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
      required:
      - id
      - financialAccount
      - plaidAccountId
      - liabilityType
      - aprs
      - createdAt
      - updatedAt
    InterestRateEntity:
      type: object
      properties:
        percentage:
          type: number
          nullable: true
          example: 3.99
        type:
          type: string
          nullable: true
          example: fixed
    PropertyAddressEntity:
      type: object
      properties:
        city:
          type: string
          nullable: true
          example: Springfield
        country:
          type: string
          nullable: true
          example: US
        postalCode:
          type: string
          nullable: true
          example: '62701'
        region:
          type: string
          nullable: true
          example: IL
        street:
          type: string
          nullable: true
          example: 123 Main St
    MortgageLiabilityEntity:
      type: object
      properties:
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        financialAccount:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        plaidAccountId:
          type: string
          example: BxBXxLj1m4HMXBm9WZZmCWVbPjX16EHwv99vp
        liabilityType:
          type: string
          example: mortgage
        accountNumber:
          type: string
          nullable: true
          description: Last 4 characters of the loan account number
          example: '4321'
        currentLateFee:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 25.5
        escrowBalance:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 3141.54
        hasPmi:
          type: boolean
          nullable: true
          example: true
        hasPrepaymentPenalty:
          type: boolean
          nullable: true
          example: false
        interestRate:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/InterestRateEntity"
        lastPaymentAmount:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 1875.32
        lastPaymentDate:
          type: string
          nullable: true
          example: '2026-01-01'
        loanTypeDescription:
          type: string
          nullable: true
          example: conventional
        loanTerm:
          type: string
          nullable: true
          example: 30 year
        maturityDate:
          type: string
          nullable: true
          example: '2051-02-01'
        nextMonthlyPayment:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 1875.32
        nextPaymentDueDate:
          type: string
          nullable: true
          example: '2026-02-01'
        originationDate:
          type: string
          nullable: true
          example: '2021-01-15'
        originationPrincipalAmount:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 425000.5
        pastDueAmount:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 1875.32
        propertyAddress:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/PropertyAddressEntity"
        ytdInterestPaid:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 1150.42
        ytdPrincipalPaid:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 724.9
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        tags:
          type: array
          items:
            "$ref": "#/components/schemas/TransactionTagData"
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
      required:
      - id
      - financialAccount
      - plaidAccountId
      - liabilityType
      - createdAt
      - updatedAt
    LoanStatusEntity:
      type: object
      properties:
        endDate:
          type: string
          nullable: true
          example: '2033-08-20'
        type:
          type: string
          nullable: true
          example: repayment
    RepaymentPlanEntity:
      type: object
      properties:
        description:
          type: string
          nullable: true
          example: Standard Repayment
        type:
          type: string
          nullable: true
          example: standard
    ServicerAddressEntity:
      type: object
      properties:
        city:
          type: string
          nullable: true
          example: Springfield
        region:
          type: string
          nullable: true
          example: IL
        street:
          type: string
          nullable: true
          example: 456 Oak Ave
        postalCode:
          type: string
          nullable: true
          example: '62704'
        country:
          type: string
          nullable: true
          example: US
    StudentLiabilityEntity:
      type: object
      properties:
        id:
          type: string
          example: 507f1f77bcf86cd799439011
        financialAccount:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        plaidAccountId:
          type: string
          example: BxBXxLj1m4HMXBm9WZZmCWVbPjX16EHwv99vp
        liabilityType:
          type: string
          example: student
        accountNumber:
          type: string
          nullable: true
          description: Last 4 characters of the loan account number
          example: '4321'
        disbursementDates:
          example:
          - '2018-08-20'
          type: array
          items:
            type: string
        expectedPayoffDate:
          type: string
          nullable: true
          example: '2033-08-20'
        guarantor:
          type: string
          nullable: true
          example: DEPT OF ED
        interestRatePercentage:
          type: number
          example: 5.25
        isOverdue:
          type: boolean
          nullable: true
          example: false
        lastPaymentAmount:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 138.05
        lastPaymentDate:
          type: string
          nullable: true
          example: '2026-01-12'
        lastStatementBalance:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 18250.75
        lastStatementIssueDate:
          type: string
          nullable: true
          example: '2025-12-28'
        loanName:
          type: string
          nullable: true
          example: Consolidation
        loanStatus:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/LoanStatusEntity"
        minimumPaymentAmount:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 138.05
        nextPaymentDueDate:
          type: string
          nullable: true
          example: '2026-02-12'
        originationDate:
          type: string
          nullable: true
          example: '2018-08-20'
        originationPrincipalAmount:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 25000.5
        outstandingInterestAmount:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 612.36
        paymentReferenceNumber:
          type: string
          nullable: true
          example: '4307799999'
        repaymentPlan:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/RepaymentPlanEntity"
        sequenceNumber:
          type: string
          nullable: true
          example: '1'
        servicerAddress:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/ServicerAddressEntity"
        ytdInterestPaid:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 79.84
        ytdPrincipalPaid:
          type: number
          nullable: true
          description: In currency units (e.g. dollars), not cents, as reported by
            Plaid.
          example: 58.21
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        tags:
          type: array
          items:
            "$ref": "#/components/schemas/TransactionTagData"
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
      required:
      - id
      - financialAccount
      - plaidAccountId
      - liabilityType
      - disbursementDates
      - interestRatePercentage
      - createdAt
      - updatedAt
    GetLiabilitiesResponseEntity:
      type: object
      properties:
        total:
          type: number
          example: 42
        page:
          type: number
          example: 1
        perPage:
          type: number
          example: 20
        skip:
          type: number
          example: 0
        limit:
          type: number
          example: 20
        totalPages:
          type: number
          example: 3
        nextPage:
          type: number
          example: 2
          nullable: true
        previousPage:
          type: number
          example:
          nullable: true
        creditLiabilities:
          type: array
          items:
            "$ref": "#/components/schemas/CreditLiabilityEntity"
        mortgageLiabilities:
          type: array
          items:
            "$ref": "#/components/schemas/MortgageLiabilityEntity"
        studentLiabilities:
          type: array
          items:
            "$ref": "#/components/schemas/StudentLiabilityEntity"
      required:
      - total
      - totalPages
      - nextPage
      - previousPage
      - creditLiabilities
      - mortgageLiabilities
      - studentLiabilities
    RetrySyncDto:
      type: object
      properties:
        products:
          type: array
          description: Products to re-trigger a sync for. Omit to retry every retryable
            product on the item.
          items:
            type: string
            enum:
            - transactions
            - recurringTransactions
            - investments
            - liabilities
    TransactionDataEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0c1
        plaidItemId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0a0
        plaidTransactionId:
          type: string
          example: lPNjeW1nR6CDn5okmGQ6hEpMo4lLNoSrzqDje
        amount:
          type: number
          description: Absolute value of the transaction amount.
          example: 42.18
        transactionType:
          type: string
          enum:
          - debit
          - credit
          description: debit = money out, credit = money in.
        authorizedDate:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-13T00:00:00.000Z'
        detailed:
          type: string
          nullable: true
          example: FOOD_AND_DRINK_COFFEE
        category:
          type: string
          nullable: true
          description: 'Effective category: the user override when set, otherwise
            Plaid’s primary category.'
          example: FOOD_AND_DRINK
        categoryOverridden:
          type: boolean
          description: True when `category` comes from a user override rather than
            Plaid.
          example: false
        createdAt:
          format: date-time
          type: string
          example: '2026-01-14T08:00:00.000Z'
        transactionDate:
          format: date-time
          type: string
          example: '2026-01-14T00:00:00.000Z'
        financialAccount:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e1
        currency:
          type: string
          nullable: true
          example: USD
        merchantName:
          type: string
          nullable: true
          example: Corner Coffee
        name:
          type: string
          example: CORNER COFFEE 1123
        paymentChannel:
          type: string
          example: in store
        pending:
          type: boolean
          example: false
        pendingTransactionId:
          type: string
          nullable: true
          example:
        plaidAccountId:
          type: string
          example: BxBXxLj1m4HMXBm9WZZmCWVbPjX16EHwv99vp
        transactionCode:
          type: string
          nullable: true
          example:
        unofficialCurrencyCode:
          type: string
          nullable: true
          example:
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-14T08:00:00.000Z'
        isRecurring:
          type: boolean
          description: True if this transaction belongs to a recurring stream.
          example: false
        tags:
          type: array
          items:
            "$ref": "#/components/schemas/TransactionTagData"
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
      required:
      - id
      - plaidItemId
      - plaidTransactionId
      - amount
      - transactionType
      - categoryOverridden
      - createdAt
      - transactionDate
      - financialAccount
      - name
      - paymentChannel
      - pending
      - plaidAccountId
      - updatedAt
      - isRecurring
    SubcategorySummaryEntry:
      type: object
      properties:
        subcategory:
          type: string
          description: Normalized subcategory name (uppercase). Transactions with
            no Plaid subcategory or whose primary category was overridden fall under
            a bucket labelled 'UNCATEGORIZED' in the per-account summary (null in
            the monthly summary).
          nullable: true
          example: FOOD_AND_DRINK_COFFEE
        debitTotal:
          type: number
          description: Sum of positive amounts (money out) for this subcategory. In
            the account currency's major unit (dollars for USD), not cents, rounded
            to 2 decimals.
          example: 64.2
        creditTotal:
          type: number
          description: Sum of absolute negative amounts (money in) for this subcategory.
            In the account currency's major unit (dollars for USD), not cents, rounded
            to 2 decimals.
          example: 0
        transactionCount:
          type: number
          description: Total number of transactions in this subcategory.
          example: 9
      required:
      - subcategory
      - debitTotal
      - creditTotal
      - transactionCount
    CategoryBreakdownEntry:
      type: object
      properties:
        category:
          type: string
          description: Normalized category name (uppercase). Null for uncategorized
            transactions.
          nullable: true
          example: FOOD_AND_DRINK
        debitTotal:
          type: number
          description: Sum of positive amounts (money out) for this category. In the
            account currency's major unit (dollars for USD), not cents, rounded to
            2 decimals.
          example: 482.35
        creditTotal:
          type: number
          description: Sum of absolute negative amounts (money in) for this category.
            In the account currency's major unit (dollars for USD), not cents, rounded
            to 2 decimals.
          example: 0
        transactionCount:
          type: number
          description: Total number of transactions in this category.
          example: 17
        subcategories:
          description: Per-subcategory breakdown, present only when groupBy=detailed.
            Sorted by debitTotal desc, uncategorized (null) last.
          type: array
          items:
            "$ref": "#/components/schemas/SubcategorySummaryEntry"
      required:
      - category
      - debitTotal
      - creditTotal
      - transactionCount
    TransactionSummaryTotals:
      type: object
      properties:
        debitTotal:
          type: number
          description: Total sum of positive amounts (money out) across all matched
            transactions. In the account currency's major unit (dollars for USD),
            not cents, rounded to 2 decimals.
          example: 3120.75
        creditTotal:
          type: number
          description: Total sum of absolute negative amounts (money in) across all
            matched transactions. In the account currency's major unit (dollars for
            USD), not cents, rounded to 2 decimals.
          example: 4500
        transactionCount:
          type: number
          description: Total number of matched transactions.
          example: 86
      required:
      - debitTotal
      - creditTotal
      - transactionCount
    GetTransactionsSummaryResponseEntity:
      type: object
      properties:
        categories:
          type: array
          items:
            "$ref": "#/components/schemas/CategoryBreakdownEntry"
        totals:
          "$ref": "#/components/schemas/TransactionSummaryTotals"
      required:
      - categories
      - totals
    MonthlyCategoryBreakdownEntry:
      type: object
      properties:
        category:
          type: string
          description: Normalized category name (uppercase). Null for uncategorized
            transactions.
          nullable: true
          example: FOOD_AND_DRINK
        debitTotal:
          type: number
          description: Sum of positive amounts (money out) for this category. In the
            account currency's major unit (dollars for USD), not cents, rounded to
            2 decimals.
          example: 482.35
        creditTotal:
          type: number
          description: Sum of absolute negative amounts (money in) for this category.
            In the account currency's major unit (dollars for USD), not cents, rounded
            to 2 decimals.
          example: 0
        transactionCount:
          type: number
          description: Total number of transactions in this category.
          example: 17
        subcategories:
          description: Per-subcategory breakdown, present only when groupBy=detailed.
            Sorted by debitTotal desc, uncategorized (null) last.
          type: array
          items:
            "$ref": "#/components/schemas/SubcategorySummaryEntry"
      required:
      - category
      - debitTotal
      - creditTotal
      - transactionCount
    MonthlyCategorySummaryEntry:
      type: object
      properties:
        month:
          type: string
          description: 'Month bucket in UTC. Format: YYYY-MM.'
          example: 2024-01
        categories:
          description: Per-category debit/credit totals for this month, sorted by
            debitTotal desc.
          type: array
          items:
            "$ref": "#/components/schemas/MonthlyCategoryBreakdownEntry"
        totals:
          description: Totals across all categories for this month.
          allOf:
          - "$ref": "#/components/schemas/TransactionSummaryTotals"
      required:
      - month
      - categories
      - totals
    GetMonthlyCategorySummaryResponseEntity:
      type: object
      properties:
        months:
          description: One entry per month that has matching transactions, ascending.
          type: array
          items:
            "$ref": "#/components/schemas/MonthlyCategorySummaryEntry"
        totals:
          description: Grand totals across every month and category.
          allOf:
          - "$ref": "#/components/schemas/TransactionSummaryTotals"
        accountIds:
          description: FinancialAccount IDs included in the aggregation.
          example:
          - 507f1f77bcf86cd799439011
          - 65a1b2c3d4e5f6a7b8c9d0e2
          type: array
          items:
            type: string
      required:
      - months
      - totals
      - accountIds
    CaseSubcategoryEntry:
      type: object
      properties:
        subcategory:
          type: string
          description: Normalized subcategory name (uppercase). Null for transactions
            with no Plaid subcategory or whose primary category was overridden.
          nullable: true
          example: FOOD_AND_DRINK_RESTAURANT
        transactionCount:
          type: number
          description: Number of transactions in this subcategory across the included
            accounts.
          example: 12
      required:
      - subcategory
      - transactionCount
    CaseCategoryEntry:
      type: object
      properties:
        category:
          type: string
          description: Normalized category name (uppercase). Null for uncategorized
            transactions.
          nullable: true
          example: FOOD_AND_DRINK
        transactionCount:
          type: number
          description: Number of transactions in this category across the included
            accounts.
          example: 37
        subcategories:
          description: Subcategories nested under this category, sorted alphabetically
            with uncategorized (null) last. Empty when the category has no subcategories.
          type: array
          items:
            "$ref": "#/components/schemas/CaseSubcategoryEntry"
      required:
      - category
      - transactionCount
      - subcategories
    GetCaseCategoriesResponseEntity:
      type: object
      properties:
        categories:
          description: Distinct categories present across the included accounts, sorted
            alphabetically with uncategorized last.
          type: array
          items:
            "$ref": "#/components/schemas/CaseCategoryEntry"
        accountIds:
          description: FinancialAccount IDs included in the lookup.
          example:
          - 507f1f77bcf86cd799439011
          type: array
          items:
            type: string
      required:
      - categories
      - accountIds
    UpdateTransactionCategoryResponseEntity:
      type: object
      properties:
        id:
          type: string
          description: Transaction ID.
          example: 507f1f77bcf86cd799439011
        category:
          type: string
          description: Effective category after the update (override if set, otherwise
            Plaid’s value). Null when uncategorized.
          nullable: true
          example: MEDICAL
        categoryOverridden:
          type: boolean
          description: True when the effective category comes from a user override
            rather than Plaid.
          example: true
        updatedCount:
          type: number
          description: Total number of transactions updated by this request, including
            the target. Greater than 1 only when applyToSameMerchant matched additional
            transactions.
          example: 1
      required:
      - id
      - category
      - categoryOverridden
      - updatedCount
    UpdateTransactionCategoryDto:
      type: object
      properties:
        category:
          type: string
          description: New category for the transaction. An existing category to reassign,
            or a new label to create. Stored normalized (trimmed, uppercased). Omit
            or send null to reset to the original Plaid category.
          nullable: true
          example: MEDICAL
        applyToSameMerchant:
          type: boolean
          description: When true, apply the same category to every other transaction
            from the same merchant (exact merchant-name match) across the case. No-op
            when the transaction has no merchant name. One-time update of existing
            transactions only.
          default: false
        accountIds:
          description: Restrict the bulk apply to these FinancialAccount IDs (all
            must belong to the case). Omit to apply across every linked account in
            the case. Only used when applyToSameMerchant is true.
          type: array
          items:
            type: string
    RecurringTransactionStreamDataEntity:
      type: object
      properties:
        id:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0d1
        plaidItemId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0a0
        streamId:
          type: string
          example: no86Eox18VHMvaOVL7gPUM9ap3aR1LsAVZ5nc
        averageAmount:
          type: number
          nullable: true
          example: 15.49
        averageAmountCurrency:
          type: string
          nullable: true
          example: USD
        detailed:
          type: string
          nullable: true
          example: ENTERTAINMENT_TV_AND_MOVIES
        category:
          type: string
          nullable: true
          example: ENTERTAINMENT
        createdAt:
          format: date-time
          type: string
          example: '2026-01-10T09:00:00.000Z'
        description:
          type: string
          example: STREAMING SERVICE
        financialAccount:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0e1
        firstDate:
          format: date-time
          type: string
          example: '2025-03-01T00:00:00.000Z'
        frequency:
          type: string
          example: MONTHLY
        isActive:
          type: boolean
          example: true
        lastAmount:
          type: number
          nullable: true
          example: 15.49
        lastDate:
          format: date-time
          type: string
          example: '2026-01-01T00:00:00.000Z'
        merchantName:
          type: string
          nullable: true
          example: Streaming Service
        plaidAccountId:
          type: string
          example: BxBXxLj1m4HMXBm9WZZmCWVbPjX16EHwv99vp
        predictedNextDate:
          format: date-time
          type: string
          nullable: true
          example: '2026-02-01T00:00:00.000Z'
        status:
          type: string
          example: MATURE
        transactionIds:
          example:
          - lPNjeW1nR6CDn5okmGQ6hEpMo4lLNoSrzqDje
          type: array
          items:
            type: string
        type:
          type: string
          enum:
          - inflow
          - outflow
          example: outflow
        updatedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        tags:
          type: array
          items:
            "$ref": "#/components/schemas/TransactionTagData"
        attachedTags:
          type: array
          items:
            "$ref": "#/components/schemas/AttachedTagResponseEntity"
      required:
      - id
      - plaidItemId
      - streamId
      - createdAt
      - description
      - financialAccount
      - firstDate
      - frequency
      - isActive
      - lastDate
      - plaidAccountId
      - status
      - transactionIds
      - type
      - updatedAt
    AddTransactionTagsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID this transaction belongs to.
        tagNames:
          minItems: 1
          maxItems: 100
          description: Array of tag names or tag IDs to add.
          type: array
          items:
            type: string
      required:
      - caseId
      - tagNames
    RemoveTransactionTagsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID this transaction belongs to.
        tagNames:
          minItems: 1
          maxItems: 100
          description: Array of tag names or tag IDs to remove.
          type: array
          items:
            type: string
      required:
      - caseId
      - tagNames
    BulkAddTransactionTagsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID these transactions belong to.
        transactionIds:
          minItems: 1
          maxItems: 500
          description: Array of transaction IDs to tag.
          type: array
          items:
            type: string
        tagNames:
          minItems: 1
          maxItems: 100
          description: Array of tag names or tag IDs to add.
          type: array
          items:
            type: string
      required:
      - caseId
      - transactionIds
      - tagNames
    BulkAddRecurringTransactionTagsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID these recurring transaction streams belong to.
        streamIds:
          minItems: 1
          maxItems: 500
          description: Array of recurring transaction stream IDs to tag.
          type: array
          items:
            type: string
        tagNames:
          minItems: 1
          maxItems: 100
          description: Array of tag names or tag IDs to add.
          type: array
          items:
            type: string
      required:
      - caseId
      - streamIds
      - tagNames
    BulkAddLiabilityTagsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID these liabilities belong to.
        liabilityIds:
          minItems: 1
          maxItems: 500
          description: Array of liability IDs to tag.
          type: array
          items:
            type: string
        tagNames:
          minItems: 1
          maxItems: 100
          description: Array of tag names or tag IDs to add.
          type: array
          items:
            type: string
      required:
      - caseId
      - liabilityIds
      - tagNames
    BulkAddHoldingTagsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID these holdings belong to.
        holdingIds:
          minItems: 1
          maxItems: 500
          description: Array of holding IDs to tag.
          type: array
          items:
            type: string
        tagNames:
          minItems: 1
          maxItems: 100
          description: Array of tag names or tag IDs to add.
          type: array
          items:
            type: string
      required:
      - caseId
      - holdingIds
      - tagNames
    BulkAddInvestmentTransactionTagsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID these investment transactions belong to.
        investmentTransactionIds:
          minItems: 1
          maxItems: 500
          description: Array of investment transaction IDs to tag.
          type: array
          items:
            type: string
        tagNames:
          minItems: 1
          maxItems: 100
          description: Array of tag names or tag IDs to add.
          type: array
          items:
            type: string
      required:
      - caseId
      - investmentTransactionIds
      - tagNames
    ExportTransactionFiltersDto:
      type: object
      properties:
        startDate:
          type: string
          description: Optional start date filter (YYYY-MM-DD, inclusive).
          example: '2024-01-01'
        endDate:
          type: string
          description: Optional end date filter (YYYY-MM-DD, inclusive).
          example: '2024-12-31'
        category:
          type: string
          description: Filter by transaction category (categoryPrimary). Case-insensitive
            partial match.
        transactionType:
          type: string
          description: Filter by transaction type. debit = money out (positive in
            Plaid), credit = money in (negative in Plaid).
          enum:
          - debit
          - credit
        minAmount:
          type: number
          description: Filter transactions with amount >= minAmount.
        maxAmount:
          type: number
          description: Filter transactions with amount <= maxAmount (absolute value).
        tagIds:
          description: Filter by tag IDs. Only exports transactions that have at least
            one of the provided tags.
          type: array
          items:
            type: string
    ExportTransactionsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID this financial data belongs to.
        format:
          type: string
          description: Export format.
          enum:
          - csv
          - pdf
        filters:
          description: Optional filters to narrow down exported records.
          allOf:
          - "$ref": "#/components/schemas/ExportTransactionFiltersDto"
      required:
      - caseId
      - format
    ExportPlaidItemFiltersDto:
      type: object
      properties:
        startDate:
          type: string
          description: Optional start date filter (YYYY-MM-DD, inclusive).
          example: '2024-01-01'
        endDate:
          type: string
          description: Optional end date filter (YYYY-MM-DD, inclusive).
          example: '2024-12-31'
        includeAccountTypes:
          type: array
          description: Only include accounts of these types. If omitted, all types
            are included.
          default: []
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
        excludeAccountTypes:
          type: array
          description: Exclude accounts of these types.
          default: []
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
        category:
          type: string
          description: Transaction category (partial, case-insensitive).
        transactionType:
          type: string
          enum:
          - debit
          - credit
        minAmount:
          type: number
        maxAmount:
          type: number
        tagIds:
          type: array
          items:
            type: array
    ExportPlaidItemTransactionsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID that the PlaidItem belongs to.
        format:
          type: string
          description: Export format.
          enum:
          - csv
          - pdf
        filters:
          description: Optional filters to narrow down exported records.
          allOf:
          - "$ref": "#/components/schemas/ExportPlaidItemFiltersDto"
      required:
      - caseId
      - format
    ExportRecurringTransactionFiltersDto:
      type: object
      properties:
        type:
          type: string
          description: Filter by stream type (inflow / outflow).
          enum:
          - inflow
          - outflow
        isActive:
          type: boolean
          description: Filter by active status.
        frequency:
          type: string
          description: Filter by frequency (e.g. WEEKLY, MONTHLY).
        tagIds:
          description: Filter by tag IDs. Only exports streams that have at least
            one of the provided tags.
          type: array
          items:
            type: string
    ExportRecurringTransactionsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID this financial data belongs to.
        format:
          type: string
          description: Export format.
          enum:
          - csv
          - pdf
        filters:
          description: Optional filters to narrow down exported records.
          allOf:
          - "$ref": "#/components/schemas/ExportRecurringTransactionFiltersDto"
      required:
      - caseId
      - format
    ExportPlaidItemRecurringFiltersDto:
      type: object
      properties:
        includeAccountTypes:
          type: array
          description: Only include accounts of these types. If omitted, all types
            are included.
          default: []
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
        excludeAccountTypes:
          type: array
          description: Exclude accounts of these types.
          default: []
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
    ExportPlaidItemRecurringTransactionsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID that the PlaidItem belongs to.
        format:
          type: string
          description: Export format.
          enum:
          - csv
          - pdf
        filters:
          description: Optional filters to narrow down exported accounts.
          allOf:
          - "$ref": "#/components/schemas/ExportPlaidItemRecurringFiltersDto"
      required:
      - caseId
      - format
    ExportHoldingFiltersDto:
      type: object
      properties:
        type:
          type: string
          description: Filter by security type (e.g. equity, etf, derivative, mutual
            fund).
        tagIds:
          description: Filter by tag IDs. Only exports holdings that have at least
            one of the provided tags.
          type: array
          items:
            type: string
    ExportHoldingsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID this financial data belongs to.
        format:
          type: string
          description: Export format.
          enum:
          - csv
          - pdf
        filters:
          description: Optional filters to narrow down exported holdings.
          allOf:
          - "$ref": "#/components/schemas/ExportHoldingFiltersDto"
      required:
      - caseId
      - format
    ExportPlaidItemHoldingFiltersDto:
      type: object
      properties:
        includeAccountTypes:
          type: array
          description: Only include accounts of these types. If omitted, all types
            are included.
          default: []
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
        excludeAccountTypes:
          type: array
          description: Exclude accounts of these types.
          default: []
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
    ExportPlaidItemHoldingsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID that the PlaidItem belongs to.
        format:
          type: string
          description: Export format.
          enum:
          - csv
          - pdf
        filters:
          description: Optional filters to narrow down exported accounts.
          allOf:
          - "$ref": "#/components/schemas/ExportPlaidItemHoldingFiltersDto"
      required:
      - caseId
      - format
    ExportLiabilityFiltersDto:
      type: object
      properties:
        liabilityType:
          type: string
          description: Filter by liability type.
          enum:
          - credit
          - mortgage
          - student
        isOverdue:
          type: boolean
          description: Filter by overdue status. Applies to credit and student liabilities
            only.
        tagIds:
          description: Filter by tag IDs. Only exports liabilities that have at least
            one of the provided tags.
          type: array
          items:
            type: string
    ExportLiabilitiesDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID this financial data belongs to.
        format:
          type: string
          description: Export format.
          enum:
          - csv
          - pdf
        filters:
          description: Optional filters to narrow down exported liabilities.
          allOf:
          - "$ref": "#/components/schemas/ExportLiabilityFiltersDto"
      required:
      - caseId
      - format
    ExportPlaidItemLiabilityFiltersDto:
      type: object
      properties:
        includeAccountTypes:
          type: array
          description: Only include accounts of these types. If omitted, all types
            are included.
          default: []
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
        excludeAccountTypes:
          type: array
          description: Exclude accounts of these types.
          default: []
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
    ExportPlaidItemLiabilitiesDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID that the PlaidItem belongs to.
        format:
          type: string
          description: Export format.
          enum:
          - csv
          - pdf
        filters:
          description: Optional filters to narrow down exported accounts.
          allOf:
          - "$ref": "#/components/schemas/ExportPlaidItemLiabilityFiltersDto"
      required:
      - caseId
      - format
    ExportAccountInvestmentTransactionFiltersDto:
      type: object
      properties:
        startDate:
          type: string
          description: Start date filter (YYYY-MM-DD, inclusive).
          example: '2024-01-01'
        endDate:
          type: string
          description: End date filter (YYYY-MM-DD, inclusive).
          example: '2024-12-31'
        transactionType:
          type: string
          description: Filter by transaction type (e.g. buy, sell, dividend, transfer).
          example: sell
        tagIds:
          description: Filter by tag IDs (reserved for future use).
          type: array
          items:
            type: string
    ExportAccountInvestmentTransactionsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID this financial data belongs to.
        format:
          type: string
          description: Export format.
          enum:
          - csv
          - pdf
        filters:
          description: Optional filters.
          allOf:
          - "$ref": "#/components/schemas/ExportAccountInvestmentTransactionFiltersDto"
      required:
      - caseId
      - format
    ExportInvestmentTransactionFiltersDto:
      type: object
      properties:
        includeAccountTypes:
          type: array
          description: Only include accounts of these types. If omitted, all types
            are included.
          default: []
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
        excludeAccountTypes:
          type: array
          description: Exclude accounts of these types.
          default: []
          items:
            type: string
            enum:
            - depository
            - credit
            - loan
            - investment
            - brokerage
            - other
    ExportInvestmentTransactionsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID that the PlaidItem belongs to.
        format:
          type: string
          description: Export format.
          enum:
          - csv
          - pdf
        filters:
          description: Optional filters to narrow down exported accounts.
          allOf:
          - "$ref": "#/components/schemas/ExportInvestmentTransactionFiltersDto"
      required:
      - caseId
      - format
    ListFinancialExportJobsItemEntity:
      type: object
      properties:
        jobId:
          type: string
          description: Export job ID
          example: 65a1b2c3d4e5f6a7b8c9d0e9
        exportType:
          type: string
          enum:
          - transactions
          - recurring-transactions
          - holdings
          - liabilities
          - investment-transactions
          - statements-bundle
        format:
          type: string
          enum:
          - csv
          - pdf
          - zip
        status:
          type: string
          enum:
          - pending
          - in-progress
          - completed
          - failed
          - cancelled
        fileName:
          type: string
          example: transactions-2026-01-15.csv
        downloadUrl:
          type: string
          example: https://files.example.com/exports/transactions-2026-01-15.csv?signature=abc123
        errorMessage:
          type: string
          example:
          nullable: true
        createdAt:
          format: date-time
          type: string
          example: '2026-01-15T14:30:00.000Z'
        completedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:31:10.000Z'
      required:
      - jobId
      - exportType
      - format
      - status
      - createdAt
    FinancialExportJobResponseEntity:
      type: object
      properties:
        jobId:
          type: string
          description: Export job ID
          example: 65a1b2c3d4e5f6a7b8c9d0e9
        status:
          type: string
          enum:
          - pending
          - in-progress
          - completed
          - failed
          - cancelled
        downloadUrl:
          type: string
          description: Pre-signed S3 download URL
          example: https://files.example.com/exports/transactions-2026-01-15.csv?signature=abc123
        fileName:
          type: string
          description: Generated file name
          example: transactions-2026-01-15.csv
          nullable: true
        completedAt:
          format: date-time
          type: string
          example: '2026-01-15T14:31:10.000Z'
          nullable: true
        errorMessage:
          type: string
          example:
          nullable: true
      required:
      - jobId
      - status
    BulkDownloadStatementsResponseEntity:
      type: object
      properties:
        jobId:
          type: string
          description: Export job ID. Poll GET /financial-data/export/:jobId for status
            and the signed download URL when complete.
          example: 507f1f77bcf86cd799439011
      required:
      - jobId
    BulkDownloadStatementsDto:
      type: object
      properties:
        caseId:
          type: string
          description: Case ID that the PlaidItem belongs to.
      required:
      - caseId
    PartnerContactDto:
      type: object
      properties:
        givenName:
          type: string
        familyName:
          type: string
        email:
          type: string
      required:
      - givenName
      - familyName
      - email
    PartnerAddressDto:
      type: object
      properties:
        street:
          type: string
        city:
          type: string
        region:
          type: string
        postalCode:
          type: string
        countryCode:
          type: string
          description: ISO-3166-1 alpha-2 country code. Defaults to "US" on the backend
            if omitted.
          example: US
      required:
      - street
      - city
      - region
      - postalCode
    CreatePartnerCustomerDto:
      type: object
      properties:
        companyName:
          type: string
          description: Legal company name
        legalEntityName:
          type: string
          description: Legal entity name (may differ from company name)
        website:
          type: string
          description: Company website
        applicationName:
          type: string
          description: Name of the application using Plaid. Defaults to companyName
            if omitted.
        logo:
          type: string
          description: Company logo (base64-encoded PNG)
        technicalContact:
          "$ref": "#/components/schemas/PartnerContactDto"
        billingContact:
          "$ref": "#/components/schemas/PartnerContactDto"
        address:
          "$ref": "#/components/schemas/PartnerAddressDto"
        isDiligenceAttested:
          type: boolean
          description: Attests the information provided is accurate and the end customer
            agrees to Plaid's terms. Must be true.
      required:
      - companyName
      - legalEntityName
      - website
      - isDiligenceAttested
    LiveExportQueueStateEntity:
      type: object
      properties:
        bullJobId:
          type: string
          example: '1842'
        state:
          type: string
          enum:
          - waiting
          - active
          - delayed
          - paused
          example: active
        attemptsMade:
          type: number
          example: 0
      required:
      - bullJobId
      - state
      - attemptsMade
    LiveExportRowEntity:
      type: object
      properties:
        exportId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f1
        caseId:
          type: string
          nullable: true
          example: 65a1b2c3d4e5f6a7b8c9d0e2
        caseName:
          type: string
          nullable: true
          example: Doe v. Roe
        organizationId:
          type: string
          nullable: true
          example: 65a1b2c3d4e5f6a7b8c9d0e3
        requestedBy:
          type: string
          nullable: true
          example: 65a1b2c3d4e5f6a7b8c9d0e4
        status:
          type: string
          example: inprogress
        type:
          type: string
          example: pdf
        source:
          type: string
          example: conversation
        exportStartedAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:15:00.000Z'
        lastProgressAt:
          format: date-time
          type: string
          nullable: true
          example: '2026-01-15T10:20:00.000Z'
        heartbeatAgeMs:
          type: number
          nullable: true
          description: Milliseconds since lastProgressAt, or null if never set.
          example: 45000
        conversationsProcessed:
          type: number
          example: 12
        totalConversations:
          type: number
          example: 40
        messagesProcessed:
          type: number
          example: 3100
        totalMessages:
          type: number
          example: 9800
        progressUnreliable:
          type: boolean
          description: True when a stored progress counter exceeds its own total —
            a known corruption in rows pre-dating the Phase 1 counter reset. The raw
            counters are still reported as-is; this only flags them.
          example: false
        errorMessage:
          type: string
          nullable: true
          example:
        jobId:
          type: string
          nullable: true
          description: The chain id shared by every chunk of a multi-job export (uuidV4).
            NOT a Bull job id — see queue.bullJobId for that. null on an export that
            was never chunked.
          example:
        jobIndex:
          type: number
          nullable: true
          description: 0-based position of this row within its jobId chain.
          example:
        totalJobs:
          type: number
          description: Total chunks in this row's jobId chain; 1 when unchunked.
          example: 1
        queue:
          nullable: true
          type: object
          allOf:
          - "$ref": "#/components/schemas/LiveExportQueueStateEntity"
        dequeuedAt:
          format: date-time
          type: string
          nullable: true
          description: When an admin's queue operation MAY have left this export with
            no queue job — DELETE /admin/exports/:id/job, or a POST /admin/exports/:id/priority
            that did not end with the job back in the queue. Written before the queue
            is touched, so an operation interrupted midway still leaves a row this
            view can explain, and dropped again once the job provably survived. A
            record of what was done, not of what is true now — read awaitingRequeue
            for that.
          example:
        awaitingRequeue:
          type: boolean
          description: 'True when an admin''s queue operation removed this export''s
            queue job and nothing has put one back: it will not run until it is re-run
            or resumed. Requires ALL THREE of the dequeue stamp, no live queue job,
            and a not-started status — so an export caught mid-reprioritise (a remove
            + re-add, briefly jobless) is not reported as waiting for an operator,
            nor is one that has since been cancelled or picked up, while one whose
            re-add failed outright is.'
          example: false
      required:
      - exportId
      - caseId
      - caseName
      - organizationId
      - requestedBy
      - status
      - type
      - source
      - exportStartedAt
      - lastProgressAt
      - heartbeatAgeMs
      - conversationsProcessed
      - totalConversations
      - messagesProcessed
      - totalMessages
      - progressUnreliable
      - errorMessage
      - jobId
      - jobIndex
      - totalJobs
      - queue
      - dequeuedAt
      - awaitingRequeue
    LiveExportListResponseEntity:
      type: object
      properties:
        rows:
          type: array
          items:
            "$ref": "#/components/schemas/LiveExportRowEntity"
        total:
          type: number
          description: Total rows matching the filter, pre-page.
          example: 1
      required:
      - rows
      - total
    CancelExportResponseEntity:
      type: object
      properties:
        exportId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f1
        cancelRequested:
          type: boolean
          description: 'Always true on a successful response: the cancel request is
            recorded (or was already recorded by an earlier call).'
          example: true
        jobRemoved:
          type: boolean
          description: 'True only when a live queued job was found for this export
            and removed outright. Read "effectiveAt", not this, to tell whether the
            cancellation took effect immediately: a not-yet-started export whose job
            was already absent is cancelled immediately with nothing to remove, and
            reports false here.'
          example: false
        effectiveAt:
          type: string
          enum:
          - immediately
          - next-conversation-boundary
          - already-cancelled
          description: When the cancellation actually takes effect. "immediately"
            when the export had not started yet, so it is marked cancelled on the
            spot and will never run. A RUNNING export always stops at its next conversation
            boundary — cancellation is cooperative, never an instant stop.
          example: next-conversation-boundary
        message:
          type: string
          description: Plain-English summary of what happened, safe to show as-is.
          example: Cancellation requested; the export stops at its next conversation
            boundary.
      required:
      - exportId
      - cancelRequested
      - jobRemoved
      - effectiveAt
      - message
    ResumeExportResponseEntity:
      type: object
      properties:
        exportId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f1
        resumed:
          type: boolean
          description: 'Always true on a successful response: the export has been
            re-queued and will restore what a previous attempt finished.'
          example: true
        resumedFrom:
          type: string
          enum:
          - not-started
          - inprogress
          - completed
          - failed
          - cancelled
          description: The status the export was resumed from. Only failed, cancelled
            and inprogress (the stuck case) exports can be resumed.
          example: failed
        attemptCount:
          type: number
          description: How many times this export has been re-started. Counts resumes
            only — a first run and a re-run do not bump it.
          example: 1
        restorableConversations:
          type: number
          description: 'Conversations a previous attempt already finished and checkpointed
            — an upper bound on what this run restores, not a promise: a checkpoint
            is only reused when it was rendered with the same render settings this
            run picks up, and anything else is re-rendered. Zero is a legitimate resume:
            an export that died on its first conversation simply starts over.'
          example: 12
        message:
          type: string
          description: Plain-English summary of what happened, safe to show as-is.
          example: Export re-queued; it restores up to 12 finished conversations.
      required:
      - exportId
      - resumed
      - resumedFrom
      - attemptCount
      - restorableConversations
      - message
    ReprioritizeExportResponseEntity:
      type: object
      properties:
        exportId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f1
        position:
          type: string
          enum:
          - top
          - bottom
          description: Which end of the wait list the job was moved to.
          example: top
        reordered:
          type: boolean
          description: Always true on a successful (non-throwing) response.
          example: true
        bullJobId:
          type: string
          description: The job's NEW queue id. Reordering is physically a remove and
            re-add, so the id you saw a moment ago no longer exists — it is reported
            here rather than silently swapped.
          example: '1907'
        previousBullJobId:
          type: string
          description: The id the job had before the move, so a stale view can be
            matched up against the new one.
          example: '1842'
        message:
          type: string
          description: Plain-English summary of what happened, safe to show as-is.
          example: Export moved to the top of the queue with a new queue job id.
      required:
      - exportId
      - position
      - reordered
      - bullJobId
      - previousBullJobId
      - message
    ReprioritizeExportDto:
      type: object
      properties:
        position:
          type: string
          enum:
          - top
          - bottom
          description: Which end of the export wait list to move this export to. "top"
            makes it the next export a worker picks up; "bottom" puts it behind everything
            currently waiting.
      required:
      - position
    DequeueExportResponseEntity:
      type: object
      properties:
        exportId:
          type: string
          example: 65a1b2c3d4e5f6a7b8c9d0f1
        jobRemoved:
          type: boolean
          description: False when nothing was removed — no live queue job was FOUND
            for the export, or the job was gone by the time the removal ran. A legitimate
            outcome rather than a failure, because the end state you asked for appears
            to already hold; read the message, which is careful to say "appears",
            because nothing here can prove a negative about the queue.
          example: true
        removedFromState:
          type: string
          nullable: true
          description: The queue state the removed job was in; null when nothing was
            removed.
          example: waiting
        status:
          type: string
          enum:
          - not-started
          - inprogress
          - completed
          - failed
          - cancelled
          description: What the export row reads now. Always notstarted — a dequeue
            is queue hygiene, not a cancellation, and leaves the export re-queueable.
          example: not-started
        message:
          type: string
          description: Plain-English summary of what happened, safe to show as-is.
          example: Queue job removed; the export is not started and stays re-queueable.
      required:
      - exportId
      - jobRemoved
      - removedFromState
      - status
      - message
