---
openapi: 3.0.1
info:
  title: Decile Hub API v1
  version: v1
  description:
    The Decile Hub API and Decile Hub Browser Extension use **API tokens**,
    which you can view/generate [here](/settings/api) if you are logged into Hub, or see above. Once you have an API
    token, click the Authorize button below, and enter your token. Then click the
    "Try it out" button to test end points. If you have questions, ask in [Decile
    Base](/base/8) and somebody from the dev team will get back to you shortly.
paths:
  "/api/v1/whoami":
    get:
      summary: Identify the authenticated token
      tags:
        - Authentication
      description:
        Returns metadata about the authenticated API token, the user it
        belongs to, the account it is scoped to, the token-level permissions,
        and (for regular user tokens) the user's account_user role flags and
        accessible pipeline IDs. Useful for agents and integrations to
        introspect their own capabilities before making other calls. Legacy
        API tokens are rejected with a 403.
      security:
        - api_key: []
      responses:
        "200":
          description: token identified
          content:
            application/json:
              examples:
                user_token:
                  value:
                    token:
                      id: 42
                      name: Default
                      kind: user
                      transient: false
                      expires_at:
                      last_used_at: "2026-04-28T10:00:00Z"
                      permissions: []
                    user:
                      id: 7
                      email: john@example.com
                      first_name: John
                      last_name: Doe
                    account:
                      id: 1
                      name: Example Account
                      subdomain: example
                      currency: USD
                    account_user:
                      is_admin: false
                      roles:
                        admin: false
                        recruiting: false
                        investor: false
                        investment: true
                        connector: false
                        inbox: true
                        data_room: false
                        closing: false
                        clean: false
                        custom: false
                        capital_call: false
                        portfolio: false
                        account_organization_fund: false
                        event_attendance: false
                        directory: true
                        website: false
                      accessible_pipeline_ids:
                        - 42
                        - 99
                admin_token:
                  value:
                    token:
                      id: 99
                      name: Administrate
                      kind: admin
                      transient: false
                      expires_at:
                      last_used_at: "2026-04-28T10:00:00Z"
                      permissions: []
                    user:
                      id: 3
                      email: admin@decile.com
                      first_name: Decile
                      last_name: Admin
                    account:
                      id: customer-account
                      name: Customer Account
                      subdomain: customer-account
                      currency: USD
                    account_user:
        "401":
          description: Unauthorized — token missing, invalid, or expired
        "403":
          description: Forbidden — legacy API tokens are not supported
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                legacy_rejected:
                  value:
                    error:
                      code: forbidden
                      message:
                        "This endpoint does not support legacy API tokens. Generate a new token at /api/settings and retire the old token."
  "/api/v1/admin/accounts":
    get:
      summary: List accounts the calling admin can access
      tags:
        - Admin
      description:
        Admin-only. Returns the Decile Hub accounts the calling admin user can
        reach — accounts that have opted in to decile-admin access plus any
        account the admin is directly a member of. Results are sorted by name
        and capped at 100 rows; pass `search` to filter by name or subdomain
        when the admin has access to many accounts. Non-admin tokens receive
        403. Requires `X-Account-Id` header set to any account this admin can
        access; the response then enumerates the full set including that
        seed account.
      security:
        - api_key: []
      parameters:
        - name: search
          in: query
          required: false
          description: Optional case-insensitive substring filter on account name or subdomain.
          schema:
            type: string
      responses:
        "200":
          description: accounts returned
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  required:
                    - id
                    - slug
                    - name
                  properties:
                    id:
                      type: integer
                      description: Numeric account id; pass as the `X-Account-Id` header on subsequent admin API calls.
                    slug:
                      type: string
                      description: Account subdomain.
                    name:
                      type: string
                      description: Human-readable account name.
              examples:
                two_accounts:
                  value:
                    - id: 12
                      slug: acme
                      name: Acme Capital
                    - id: 47
                      slug: zenith
                      name: Zenith Ventures
        "401":
          description: Unauthorized — token missing, invalid, expired, or `X-Account-Id` header missing/unauthorized
        "403":
          description: Forbidden — endpoint requires an admin API token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                non_admin_token:
                  value:
                    error:
                      code: forbidden
                      message: Forbidden
  "/api/v1/accounts":
    get:
      operationId: get_account
      x-agent-tool: true
      summary: Retrieves the current account
      tags:
        - Accounts
      description: Returns the current account's basic information
      security:
        - api_key: []
      responses:
        "200":
          description: account found
        "401":
          description: Unauthorized
  "/api/v1/account_users":
    get:
      operationId: list_account_users
      x-agent-tool: true
      summary: List the AccountUsers (team members) on the current account
      tags:
        - AccountUsers
      description: |
        Returns the AccountUsers belonging to the authenticated token's account.
        Use to discover the `assigned_id` value for `PATCH /api/v1/pipeline_prospects` and
        `PATCH /api/v1/pipeline_prospects/{id}` (the prospect-owner field).

        Pagination is zero-indexed; page size is 100.
      security:
        - api_key: []
      parameters:
        - name: page
          in: query
          required: false
          description: Zero-indexed page number (default 0)
          schema:
            type: integer
      responses:
        "200":
          description: Account users
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        name:
                          type: string
                        email:
                          type: string
                        title:
                          type: string
                          nullable: true
                        is_admin:
                          type: boolean
                  pagination:
                    type: object
                    properties:
                      total_count:
                        type: integer
                      current_page:
                        type: integer
                      total_pages:
                        type: integer
        "401":
          description: Unauthorized
  "/api/v1/activity_entries":
    get:
      operationId: list_activity_entries
      x-agent-tool: true
      summary: List activity feed entries for the current account
      tags:
        - ActivityEntries
      description: |
        Returns the activity feed for the current account, newest first. Filter by
        subject (entity), event type, actor, or date range.

        Pagination is keyset — pass `pagination.next_page_token` from a response back
        as `page_token=` to fetch the next page.
      security:
        - api_key: []
      parameters:
        - name: subject_type
          in: query
          required: false
          description: Filter to entries whose subject is this top-level entity class. Pair with `subject_id`.
          schema:
            type: string
            enum:
              - Person
              - Organization
              - PipelineProspect
              - AccountOrganizationFund
              - AccountOrganizationManagementCompany
              - AccountOrganizationGeneralPartnership
              - FirmAdmin::PortfolioCompany
              - FirmAdmin::CapitalAccount
              - Chat
              - User
        - name: subject_id
          in: query
          required: false
          description: Subject record id; pair with `subject_type`.
          schema:
            type: integer
        - name: include_associated
          in: query
          required: false
          description: When `subject_type=Organization`, also include entries for its People. Defaults `false`.
          schema:
            type: boolean
        - name: event
          in: query
          required: false
          description: |
            Curated event filter. Mutually exclusive with `entryable_type` — sending both returns 400.
          schema:
            type: string
            enum:
              - Item Created
              - Item Edited
              - Prospect Moved
              - All Emails
              - Email Sent
              - Email Received
              - Email Link Clicked
              - PACT Signed
              - Cornerstone Signed
              - Signatory Signed
              - Signing Link Email Sent
              - Onboarding Questionnaire Completed
              - Onboarding Questionnaire Reset
              - Note Added
              - Task Completed
              - File Uploaded
              - AML/KYC
              - Chat Summary
              - Deal Share Copied to Pipeline
        - name: entryable_type
          in: query
          required: false
          description: Lower-level entryable class filter (e.g. `Log::ActivityEntries::Note`). Mutually exclusive with `event`.
          schema:
            type: string
        - name: user_id
          in: query
          required: false
          description: Actor user id. Pass the literal `automation` to match entries from system/automated events.
          schema:
            type: string
        - name: created_after
          in: query
          required: false
          description: ISO 8601 date or datetime; only entries created on or after this point.
          schema:
            type: string
        - name: created_before
          in: query
          required: false
          description: ISO 8601 date or datetime; only entries created on or before this point.
          schema:
            type: string
        - name: order_direction
          in: query
          required: false
          description: Sort direction on `(created_at, id)`. Defaults `desc`.
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
        - name: page_token
          in: query
          required: false
          description: Opaque keyset cursor returned by a previous call as `pagination.next_page_token`.
          schema:
            type: string
        - name: per_page
          in: query
          required: false
          description: Page size. Capped at 100; defaults to 25.
          schema:
            type: integer
            default: 25
            maximum: 100
        - name: include
          in: query
          required: false
          description: |
            Comma-separated nested embeds. Currently supports `entryable` (returns
            minimal `{type, action}` only).
          schema:
            type: string
        - name: fields
          in: query
          required: false
          description: Comma-separated top-level fields to return. Always returns at least `id`. Unknown fields are ignored.
          schema:
            type: string
      responses:
        "200":
          description: paginated list of activity entries
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      - id: 12345
                        name: Acme Corp
                        subject_type: Organization
                        subject_id: 678
                        entryable_type: Log::ActivityEntries::Note
                        user_id: 42
                        account_id: 1
                        origin: api
                        created_at: "2026-05-13T18:31:00Z"
                    pagination:
                      next_page_token: eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0xM1QxODozMTowMFoiLCJpZCI6MTIzNDV9
                      has_more: true
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ActivityEntry"
                  pagination:
                    type: object
                    properties:
                      next_page_token:
                        type: string
                        nullable: true
                      has_more:
                        type: boolean
        "400":
          description: invalid parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
  "/api/v1/activity_entries/{id}":
    get:
      operationId: get_activity_entry
      x-agent-tool: true
      summary: Get a single activity entry with its full entryable body
      tags:
        - ActivityEntries
      description: |
        Returns one activity entry plus the full per-type `entryable` body —
        audit change diffs, full email bodies and recipients, note bodies,
        AML/KYC status, chat summaries, and so on. The list endpoint omits this detail.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Activity entry id.
          schema:
            type: integer
      responses:
        "200":
          description: the activity entry with its entryable body
          content:
            application/json:
              examples:
                default:
                  value:
                    id: 12345
                    name: Acme Corp
                    subject_type: Organization
                    subject_id: 678
                    entryable_type: Log::ActivityEntries::Note
                    entryable_id: 9999
                    user_id: 42
                    account_id: 1
                    origin: api
                    created_at: "2026-05-13T18:31:00Z"
                    entryable:
                      type: Note
                      body: Followed up via email
              schema:
                $ref: "#/components/schemas/ActivityEntryDetail"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/entities":
    get:
      operationId: list_entities
      x-agent-tool: true
      summary: List firm-admin entities
      tags:
        - Entities
      description: |
        Returns a paginated list of the firm-admin entities (funds, SPVs,
        holdings, management companies, general partnerships) under the
        authenticated token's account. Identity-only payload — fund-specific
        configuration (cutover dates, GP carry %, logo URL) is returned by
        `GET /api/v1/entities/{id}` instead.

        Pagination is 1-indexed (page 1 is the first page). `per_page` is
        capped at 100; values <= 0 fall back to the default of 50.
      security:
        - api_key: []
      parameters:
        - name: type
          in: query
          description: |
            Filter by entity kind. Repeatable — send the param multiple times
            for multiple kinds (`?type=fund&type=spv`). Unknown values return a 400
            `invalid_parameter` error rather than being silently ignored.

            `fund` maps to `AccountOrganizationFund` rows whose
            `fund_details.fund_type` is `fund` or `start_fund`. `spv` maps to
            `fund_details.fund_type=spv`. `holding` maps to
            `fund_details.fund_type=fund_of_funds`.
          required: false
          explode: true
          schema:
            type: array
            items:
              type: string
              enum:
                - fund
                - spv
                - management_company
                - general_partnership
                - holding
        - name: active
          in: query
          description: |
            Active vs hidden entity filter. `true` (default) returns
            active (non-hidden) entities (`hidden_from_left_nav=false`);
            `false` returns only hidden entities; `all` returns both.
          required: false
          schema:
            type: string
            enum:
              - "true"
              - "false"
              - all
            default: "true"
        - name: page
          in: query
          description: 1-indexed page number (default 1).
          required: false
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          description: Page size. Capped at 100; defaults to 50 when omitted or <= 0.
          required: false
          schema:
            type: integer
            default: 50
            maximum: 100
      responses:
        "200":
          description: paginated list of entities
          content:
            application/json:
              schema:
                type: object
                required:
                  - entities
                  - page
                  - per_page
                  - total
                properties:
                  entities:
                    type: array
                    items:
                      $ref: "#/components/schemas/Entity"
                  page:
                    type: integer
                  per_page:
                    type: integer
                  total:
                    type: integer
        "400":
          description: invalid parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
    post:
      operationId: create_entity
      x-agent-tool: true
      summary: Create a firm-admin entity
      tags:
        - Entities
      description: |
        Creates a new `AccountOrganization` plus a fresh `Organization` and (for
        fund kinds) a `FundDetail` under the authenticated account.

        Onboarding side effects are **suppressed** for API-created entities —
        no LP closing pipeline is generated, no `AccountOrganizationUser` is
        attached for the account owner, and no Accelerator client_product
        touch fires. Callers that want the full onboarding workflow should
        continue to use the firm-admin UI.

        POST is not idempotent. To retry safely, GET /api/v1/entities and
        check whether the desired entity already exists before retrying.

        Requires a caller who is a `full_access_account_admin?` of the target account.
      security:
        - api_key: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EntityCreateRequest"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EntityShowResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden (caller is not full_access_account_admin)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Validation failure
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/entities/{id}":
    get:
      operationId: get_entity
      x-agent-tool: true
      summary: Show one firm-admin entity
      tags:
        - Entities
      description: |
        Returns identity + a `details` block for one entity. The `details`
        block always includes currency, currency_symbol, fiscal_year_end,
        entity_name, logo_url, is_active, and kind. When the entity is a fund
        (kind=`fund`, `spv`, or `holding`) the details block additionally
        includes `gp_carry`, `new_management_fee_system_cutover_date`, and
        `new_carry_system_cutover_date`. For non-fund entities those keys are
        omitted entirely.

        Pass `?include=calculations` to embed a `calculations` block of
        fund-level dashboard values. `?window=now` (default) returns
        as-of-today values and includes `capital_activity.invested` plus
        `fees.reserved_for_fees`; `?window=lifetime` returns forecast values
        and omits those two fields entirely. `calculations` is only supported
        on fund-kind entities (kind=`fund`, `spv`, or `holding`); requesting
        it on a non-fund entity returns 404. Numeric values serialize as
        2-decimal-place decimal strings (currency) or 4-decimal-place
        decimal strings (ratios such as `performance.net_irr`); investor
        counts are integers.

        Returns 404 (not 403) when the id refers to an entity in a different
        account.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Entity id (plain integer).
          schema:
            type: integer
        - name: include
          in: query
          required: false
          description: |
            Comma-separated list of optional nested resources to embed. Only
            `calculations` is currently supported. Unknown values are
            silently ignored. When omitted, the `calculations` block is not
            present in the response.
          schema:
            type: string
            enum:
              - calculations
        - name: window
          in: query
          required: false
          description: |
            Calculation window for the `calculations` block. Only meaningful
            when `include=calculations` is supplied. `now` (default) returns
            current as-of values; `lifetime` returns forecast values and
            omits `capital_activity.invested` and `fees.reserved_for_fees`.
            Any other value returns 400 `invalid_parameter`.
          schema:
            type: string
            enum:
              - now
              - lifetime
            default: now
      responses:
        "200":
          description: entity found
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Entity"
                  - type: object
                    required:
                      - details
                    properties:
                      details:
                        $ref: "#/components/schemas/EntityDetails"
                      calculations:
                        $ref: "#/components/schemas/EntityCalculations"
        "400":
          description: Invalid `window` value
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Entity not found (includes `include=calculations` requested on a non-fund entity)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    patch:
      operationId: update_entity
      x-agent-tool: true
      summary: Update a firm-admin entity
      tags:
        - Entities
      description: |
        Updates writable fields on an existing entity within the authenticated
        account. All fields optional — only sent fields are applied.

        `kind` is immutable. Sending a matching `kind` is a no-op; sending a
        differing `kind` returns 422.

        Renaming guard: if the entity's `Organization` is shared with sibling
        AccountOrganization rows (an edge case for entities created before this
        endpoint existed, or via paths that don't use the API's two-step create),
        PATCH refuses with 409 `organization_shared_with_siblings` and includes
        the sibling entity ids in `error.details.sibling_entity_ids` so callers
        can fall back to the firm-admin UI.

        Additional rename guard at the model level:
        `Organization#prevent_name_change_if_accounting_transactions_exist`
        returns 422 if the Organization has associated accounting transactions.

        PATCH `is_active=false` is the v1 stopgap for hiding an entity, since
        DELETE is not yet supported.

        Requires a caller who is a `full_access_account_admin?` of the target account.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EntityUpdateRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EntityShowResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Entity not found in the authenticated account
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Rename would affect sibling entities sharing this Organization
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Validation failure
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/entities/{entity_id}/capital_accounts":
    get:
      operationId: list_capital_accounts
      x-agent-tool: true
      summary: List capital accounts under an entity
      tags:
        - Capital Accounts
      description: |
        Identity-only list. Per-account calculation values (committed
        capital, contributions, balances) are intentionally NOT included
        here — they come from the per-record `/calculations` endpoint and
        the bulk `/entities/{entity_id}/capital_accounts/calculations`
        endpoint.

        Returns 404 when `entity_id` resolves to an entity in a different
        account (cross-account requests are not distinguished from missing).
      security:
        - api_key: []
      parameters:
        - name: entity_id
          in: path
          required: true
          description: Entity id (plain integer).
          schema:
            type: integer
        - name: active
          in: query
          description: |
            `true` (default) returns capital accounts where
            `is_capital_account_enabled=true`. `false` returns disabled
            accounts; `all` returns both.
          required: false
          schema:
            type: string
            enum:
              - "true"
              - "false"
              - all
            default: "true"
        - name: partner_type
          in: query
          description: |
            Optional partner-type filter. Unknown values return a 400
            `invalid_parameter` error.
          required: false
          schema:
            type: string
            enum:
              - limited_partner
              - general_partner
        - name: page
          in: query
          description: 1-indexed page number (default 1).
          required: false
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          description: Page size. Capped at 100; defaults to 50.
          required: false
          schema:
            type: integer
            default: 50
            maximum: 100
      responses:
        "200":
          description: paginated list of capital accounts
          content:
            application/json:
              schema:
                type: object
                required:
                  - capital_accounts
                  - page
                  - per_page
                  - total
                properties:
                  capital_accounts:
                    type: array
                    items:
                      $ref: "#/components/schemas/CapitalAccountIdentity"
                  page:
                    type: integer
                  per_page:
                    type: integer
                  total:
                    type: integer
        "400":
          description: invalid parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Entity not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/entities/{entity_id}/journal_entries":
    get:
      operationId: list_journal_entries
      x-agent-tool: true
      summary: List journal entries (GL transactions) under an entity
      tags:
        - Journal Entries
      description: |
        Returns the journal entries (general-ledger / accounting transactions —
        double-entry GL postings) recorded under the given entity. Each row
        carries the transaction date/amount, reconciled/pending flags, comment,
        the debit and credit ledger accounts (code + name), and the polymorphic
        counterparty (type, id, name; null when absent). Discarded entries are
        excluded.

        Filters (all optional): a `transaction_at` window via
        `transaction_at_start`/`transaction_at_end`; `debit_code`/`credit_code`
        (GL account codes — an unknown code returns no rows); `reconciled` and
        `pending` (true/false); `min_amount`/`max_amount`; and
        `counterparty_type` (+ optional `counterparty_id`).

        Returns 404 when `entity_id` resolves to an entity in a different
        account (cross-account requests are not distinguished from missing).
      security:
        - api_key: []
      parameters:
        - name: entity_id
          in: path
          required: true
          description: Entity id (plain integer).
          schema:
            type: integer
        - name: transaction_at_start
          in: query
          required: false
          description: ISO date (YYYY-MM-DD) lower bound on `transaction_at`.
          schema:
            type: string
            format: date
        - name: transaction_at_end
          in: query
          required: false
          description: ISO date (YYYY-MM-DD) upper bound on `transaction_at`.
          schema:
            type: string
            format: date
        - name: debit_code
          in: query
          required: false
          description: GL account code on the debit side (e.g. "1100"). Unknown codes return no rows.
          schema:
            type: string
        - name: credit_code
          in: query
          required: false
          description: GL account code on the credit side (e.g. "6000"). Unknown codes return no rows.
          schema:
            type: string
        - name: reconciled
          in: query
          required: false
          description: Filter by reconciled flag. Unknown values return a 400 `invalid_parameter` error.
          schema:
            type: string
            enum:
              - "true"
              - "false"
        - name: pending
          in: query
          required: false
          description: Filter by pending flag. Unknown values return a 400 `invalid_parameter` error.
          schema:
            type: string
            enum:
              - "true"
              - "false"
        - name: min_amount
          in: query
          required: false
          description: Lower bound on `transaction_amount`.
          schema:
            type: number
        - name: max_amount
          in: query
          required: false
          description: Upper bound on `transaction_amount`.
          schema:
            type: number
        - name: counterparty_type
          in: query
          required: false
          description: Polymorphic counterparty type (e.g. FirmAdmin::CapitalAccount, FirmAdmin::PortfolioCompany, Organization).
          schema:
            type: string
        - name: counterparty_id
          in: query
          required: false
          description: Counterparty id; only meaningful alongside counterparty_type.
          schema:
            type: integer
        - name: page
          in: query
          required: false
          description: 1-indexed page number (default 1).
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          required: false
          description: Page size. Capped at 100; defaults to 50.
          schema:
            type: integer
            default: 50
            maximum: 100
      responses:
        "200":
          description: paginated list of journal entries
          content:
            application/json:
              schema:
                type: object
                required:
                  - journal_entries
                  - page
                  - per_page
                  - total
                properties:
                  journal_entries:
                    type: array
                    items:
                      $ref: "#/components/schemas/JournalEntry"
                  page:
                    type: integer
                  per_page:
                    type: integer
                  total:
                    type: integer
        "400":
          description: invalid parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Entity not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      operationId: create_journal_entry
      x-agent-tool: true
      summary: Create one journal entry (GL transaction) under an entity
      tags:
        - Journal Entries
      description: |
        Creates a single journal entry (general-ledger / accounting transaction —
        a double-entry GL posting) under the given entity. Posts one debit/credit
        pair for the supplied amount and date. The entry is created unreconciled
        (`reconciled` false, `pending` false). `debit_code` and `credit_code` are
        GL account codes (see the chart-of-accounts endpoint) — they must differ
        and both must resolve to a known accounting account. `amount` must be
        numeric and greater than 0. `transaction_at` is an ISO date (YYYY-MM-DD).
        An optional counterparty can be attached via `counterparty_type` +
        `counterparty_id`; the counterparty must belong to the same account.

        Returns 422 on invalid input (unknown code, equal debit/credit codes,
        non-positive or non-numeric amount, malformed date, or a cross-account
        counterparty), 403 for a read-only token, and 404 when `entity_id`
        resolves to an entity in a different account.
      security:
        - api_key: []
      parameters:
        - name: entity_id
          in: path
          required: true
          description: Entity id (plain integer).
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - journal_entry
              properties:
                journal_entry:
                  $ref: "#/components/schemas/JournalEntryCreateRequest"
      responses:
        "201":
          description: the created journal entry
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JournalEntry"
        "400":
          description: invalid parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden (read-only token or insufficient permission)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Entity not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: validation failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/entities/{entity_id}/journal_entries/bulk":
    post:
      operationId: bulk_create_journal_entries
      x-agent-tool: true
      summary: Create many journal entries (GL transactions) under an entity (atomic)
      tags:
        - Journal Entries
      description: |
        Creates many journal entries (general-ledger / accounting transactions —
        double-entry GL postings) under the given entity in a single atomic,
        all-or-nothing batch. Each row in `journal_entries` is a full independent
        entry validated by the same rules as the single-create endpoint:
        `debit_code` and `credit_code` are GL account codes (see the
        chart-of-accounts endpoint) that must differ and both resolve to a known
        account, `amount` is numeric and greater than 0, `transaction_at` is an
        ISO date (YYYY-MM-DD), and an optional counterparty may be attached via
        `counterparty_type` + `counterparty_id` (same account).

        Provide between 1 and 100 rows. EVERY row is validated first; if ANY row
        is invalid the entire batch is rejected with 422 and a per-row `errors`
        array (`{index, field, message}`) and NOTHING is created. When all rows
        are valid they are created together inside one database transaction; any
        unexpected failure rolls the whole batch back (never a partial commit).
        All entries are created unreconciled (`reconciled` false, `pending`
        false). Idempotency is the caller's responsibility — re-sending a batch
        posts it again.

        Returns 422 on validation failure or an out-of-range batch (empty or more
        than 100 rows), 403 for a read-only token, and 404 when `entity_id`
        resolves to an entity in a different account.
      security:
        - api_key: []
      parameters:
        - name: entity_id
          in: path
          required: true
          description: Entity id (plain integer).
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/JournalEntryBulkCreateRequest"
      responses:
        "201":
          description: the created journal entries
          content:
            application/json:
              schema:
                type: object
                required:
                  - journal_entries
                  - created_count
                properties:
                  journal_entries:
                    type: array
                    items:
                      $ref: "#/components/schemas/JournalEntry"
                  created_count:
                    type: integer
                    description: Number of journal entries created in the batch.
        "401":
          description: Unauthorized
        "403":
          description: Forbidden (read-only token or insufficient permission)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Entity not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: validation failed (per-row errors, or an out-of-range batch)
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/JournalEntryBulkErrors"
                  - $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/journal_entries/{id}":
    get:
      operationId: get_journal_entry
      x-agent-tool: true
      summary: Show one journal entry (GL transaction)
      tags:
        - Journal Entries
      description: |
        Returns a single journal entry (general-ledger / accounting transaction —
        a double-entry GL posting): transaction date/amount, reconciled/pending
        flags, comment, the debit and credit ledger accounts (code + name), and
        the polymorphic counterparty (type, id, name; null when absent). Returns
        404 when the entry is unknown, discarded, or belongs to a different
        account (cross-account requests are not distinguished from missing).
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Journal entry id (plain integer).
          schema:
            type: integer
      responses:
        "200":
          description: journal entry found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JournalEntry"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Journal entry not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/capital_accounts/{id}":
    get:
      operationId: get_capital_account
      x-agent-tool: true
      summary: Show one capital account
      tags:
        - Capital Accounts
      description: |
        Identity block + commitments + primary_contact + contacts + entity
        ref. `contacts` is the full list of contacts on the capital account
        (joint signatories, spouses, trustees, entity owners/signatories,
        trust beneficiaries); each entry carries role flags plus
        `contact_status_type` (`none`/`primary`/`additional`). Returns 404
        when the capital account belongs to a different account. Use
        `?include=transfers` to embed an ordered list of capital-account
        transfers oriented at the requested CA (`kind=outbound` when the
        requested CA is the `from_capital_account`, `inbound` otherwise);
        `effective_at` is aliased from the underlying `transferred_at`
        column.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Capital account id (plain integer).
          schema:
            type: integer
        - name: include
          in: query
          description: |
            Comma-separated list of nested resources to embed. Only
            `transfers` is currently supported. Unknown values are silently
            ignored. When omitted, the `transfers` array is not present in
            the response.
          required: false
          schema:
            type: string
      responses:
        "200":
          description: capital account found
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/CapitalAccountIdentity"
                  - type: object
                    required:
                      - commitments
                      - primary_contact
                      - contacts
                      - entity
                    properties:
                      commitments:
                        type: array
                        items:
                          $ref: "#/components/schemas/Commitment"
                      primary_contact:
                        $ref: "#/components/schemas/PrimaryContact"
                      contacts:
                        type: array
                        description: |
                          Full list of contacts on the capital account, ordered by
                          created_at. Includes the primary contact plus joint
                          signatories, spouses, trustees, entity owners/signatories,
                          and trust beneficiaries.
                        items:
                          $ref: "#/components/schemas/CapitalAccountContact"
                      entity:
                        $ref: "#/components/schemas/EntityRef"
                      transfers:
                        type: array
                        description: Present only when `?include=transfers` is supplied.
                        items:
                          $ref: "#/components/schemas/CapitalAccountTransfer"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Capital account not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/capital_accounts/{id}/calculations":
    get:
      operationId: get_capital_account_calculations
      x-agent-tool: true
      summary: Single-capital-account period calculations
      tags:
        - Capital Accounts
      description: |
        Computes a single capital account's period figures. Response groups:

        - `commitments` — 12 commitment + paid-in fields.
        - `period_activity` — 15 ledger movements within the window.
        - `performance` — `dpi`, `rvpi`, `tvpi`.
        - `meta` — `period_start`, `period_end`, `currency`,
          `management_fee_system`, `carry_system`, `cutover_dates`. The
          `*_system` keys are `legacy` (window ends before the cutover),
          `new` (window starts on/after the cutover), or `hybrid` (window
          straddles the cutover).

        All numeric values are returned as decimal strings (currency 2
        decimal places, ratios 4 decimal places). Nil values are returned
        as `null`. Returning decimal strings is intentional so callers
        don't lose precision via JS number parsing.

        Period semantics: pass both `period_start` and `period_end` (ISO
        `YYYY-MM-DD`). Both are required together — supplying one without
        the other returns a 400. `period_end` must be on or after
        `period_start`. When neither is supplied, the window defaults to
        the current fiscal-year-to-date for the parent entity.

        In a hybrid window the API uses the new-system carry calculation
        directly even for GP-carry capital accounts, which can differ from
        calculating each side of the cutover separately. Request explicit
        pre/post cutover windows if you need each side calculated on its own.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Capital account id (plain integer).
          schema:
            type: integer
        - name: period_start
          in: query
          description: |
            ISO 8601 date (YYYY-MM-DD). Required when `period_end` is
            supplied. Omitting both defaults to the fund's current
            fiscal-year-to-date.
          required: false
          schema:
            type: string
            format: date
        - name: period_end
          in: query
          description: |
            ISO 8601 date (YYYY-MM-DD). Required when `period_start` is
            supplied. Must be on or after `period_start`.
          required: false
          schema:
            type: string
            format: date
      responses:
        "200":
          description: calculations computed
          content:
            application/json:
              schema:
                type: object
                required:
                  - commitments
                  - period_activity
                  - performance
                  - meta
                properties:
                  commitments:
                    $ref: "#/components/schemas/PeriodCommitments"
                  period_activity:
                    $ref: "#/components/schemas/PeriodActivity"
                  performance:
                    $ref: "#/components/schemas/PeriodPerformance"
                  meta:
                    $ref: "#/components/schemas/PeriodMeta"
        "400":
          description: invalid parameter (e.g. malformed date, end before start)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Capital account not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/entities/{entity_id}/capital_accounts/calculations":
    get:
      operationId: bulk_capital_account_calculations
      x-agent-tool: true
      summary: Bulk capital-account period calculations + totals
      tags:
        - Capital Accounts
      description: |
        Paginated bulk variant of the single-CA calculations endpoint.
        Each row in `capital_accounts` is the same shape as the single-CA
        response (commitments/period_activity/performance/meta groups) but
        with id+name pulled out of identity for easier table rendering.
        Adds a fund-level `totals` block aggregated across the filtered
        scope (NOT only the current page).

        Totals semantics:
        - Currency fields are summed across the filtered scope.
        - Per-CA ratio fields (`percentage_of_total_commitments`,
          `percentage_of_lp_commitments`, `percentage_of_funds_called`,
          `amount_paid_in_for_capital_accounts_percentage`) are `null` in
          totals — per-account ratios do not sum into a fund-level figure.
        - Performance ratios (`dpi`, `rvpi`, `tvpi`) are `null` in totals
          for the same reason.
        - `total_committed_capital`, `lp_committed_capital`,
          `gp_committed_capital` are fund-wide regardless of filters — the
          per-row `total_committed_capital` denominator is a fund-level
          value, not "total of the filtered list."

        Period and decimal-string semantics are identical to the single-CA
        endpoint.

        `fields=` projection: comma-separated whitelist of calculation
        field names. When supplied, rows AND totals are narrowed to just
        the requested fields. If zero requested fields fall within a
        group, that entire group key is dropped from both rows and totals.
        `id` and `name` are always present on each row. Unknown field
        names are silently dropped.
      security:
        - api_key: []
      parameters:
        - name: entity_id
          in: path
          required: true
          description: Entity id (plain integer).
          schema:
            type: integer
        - name: period_start
          in: query
          description: ISO 8601 date. Required when `period_end` is supplied. Defaults to fiscal-year-to-date.
          required: false
          schema:
            type: string
            format: date
        - name: period_end
          in: query
          description: ISO 8601 date. Required when `period_start` is supplied. Must be on or after `period_start`.
          required: false
          schema:
            type: string
            format: date
        - name: active
          in: query
          description: |
            `true` (default) restricts to enabled capital accounts; `false`
            for disabled-only; `all` for both. The filter narrows the rows
            returned AND the totals aggregation, EXCEPT for the fund-wide
            commitment denominators noted above.
          required: false
          schema:
            type: string
            enum:
              - "true"
              - "false"
              - all
            default: "true"
        - name: partner_type
          in: query
          required: false
          schema:
            type: string
            enum:
              - limited_partner
              - general_partner
        - name: fields
          in: query
          description: |
            Comma-separated whitelist of calculation fields to return.
            When supplied, rows and totals are narrowed to just these
            fields; groups with zero matching fields are dropped from both
            rows and totals. `id` and `name` are always returned on each
            row. Allowed fields (group in parentheses):

            commitments — `committed_capital`, `total_committed_capital`,
            `lp_committed_capital`, `gp_committed_capital`,
            `percentage_of_total_commitments`,
            `percentage_of_lp_commitments`,
            `percentage_of_funds_called`,
            `amount_paid_in_for_capital_accounts`,
            `amount_paid_in_for_capital_accounts_percentage`,
            `outstanding_capital_contribution`,
            `capital_call_receivable`, `capital_call_prepaid`.

            period_activity — `beginning_balance`, `capital_contribution`,
            `syndication_cost`, `management_fees`, `incentive_fees`,
            `net_operating_gain`, `late_interest`, `unrealized_gain`,
            `carried_interest_accrued`, `net_realized_gain`,
            `deemed_gain`, `distributions`, `in_kind_distributions`,
            `transfers`, `ending_balance`.

            performance — `dpi`, `rvpi`, `tvpi`.

            meta — `period_start`, `period_end`, `currency`,
            `management_fee_system`, `carry_system`, `cutover_dates`.
          required: false
          schema:
            type: string
        - name: page
          in: query
          description: 1-indexed page number (default 1).
          required: false
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          description: Page size. Capped at 100; defaults to 50.
          required: false
          schema:
            type: integer
            default: 50
            maximum: 100
      responses:
        "200":
          description: bulk calculations + totals
          content:
            application/json:
              schema:
                type: object
                required:
                  - capital_accounts
                  - totals
                  - page
                  - per_page
                  - total
                properties:
                  capital_accounts:
                    type: array
                    items:
                      $ref: "#/components/schemas/BulkCalculationsRow"
                  totals:
                    $ref: "#/components/schemas/BulkCalculationsTotals"
                  page:
                    type: integer
                  per_page:
                    type: integer
                  total:
                    type: integer
        "400":
          description: invalid parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Entity not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/accounting_accounts":
    get:
      operationId: list_accounting_accounts
      x-agent-tool: true
      summary: List the chart of accounts / GL account codes
      tags:
        - Accounting Accounts
      description: |
        Returns the chart of accounts (GL account codes) shared across the
        account. Use this to discover valid account codes before posting
        journal entries. Codes follow standard GL ranges: 1xxx=assets,
        2xxx=liabilities, 3xxx=equity, 4xxx=revenue, 6xxx/7xxx=expenses. All
        filters are optional and combine with AND. Results are paginated and
        ordered by code.
      security:
        - api_key: []
      parameters:
        - name: code
          in: query
          required: false
          description: Exact account code to match, e.g. "1000".
          schema:
            type: string
        - name: code_prefix
          in: query
          required: false
          description: Match account codes beginning with these characters, e.g. "1" for the asset range.
          schema:
            type: string
        - name: is_1099_eligible
          in: query
          required: false
          description: Filter to accounts that are (true) or are not (false) 1099-eligible.
          schema:
            type: boolean
        - name: search
          in: query
          required: false
          description: Case-insensitive substring match against account name or description.
          schema:
            type: string
        - name: page
          in: query
          required: false
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            default: 50
            maximum: 100
      responses:
        "200":
          description: paginated list of accounting accounts
          content:
            application/json:
              schema:
                type: object
                required:
                  - accounting_accounts
                  - page
                  - per_page
                  - total
                properties:
                  accounting_accounts:
                    type: array
                    items:
                      $ref: "#/components/schemas/AccountingAccount"
                  page:
                    type: integer
                  per_page:
                    type: integer
                  total:
                    type: integer
        "400":
          description: Invalid query parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
  "/api/v1/accounting_accounts/{id}":
    get:
      operationId: get_accounting_account
      x-agent-tool: true
      summary: Show one chart-of-accounts / GL account code
      tags:
        - Accounting Accounts
      description: |
        Returns a single chart-of-accounts entry (GL account code) by its
        integer id. Use after list_accounting_accounts to confirm a code
        before posting a journal entry.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Accounting account id (plain integer).
          schema:
            type: integer
      responses:
        "200":
          description: accounting account found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountingAccount"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Accounting account not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/capital_calls":
    get:
      operationId: list_capital_calls
      x-agent-tool: true
      summary: List capital calls under an entity
      tags:
        - Capital Calls
      description: |
        Returns the capital calls (draft/open/closed) issued from the given
        entity's fund. Each row carries header fields only: id, name, status,
        percentage_to_call, notes, amount_being_called (sum of per-LP details),
        created_at, closed_at, due_date, and the parent entity ref.

        Filter by `status` and a created_at window via `period_start` /
        `period_end` (both ISO dates; supply together). Returns 404 when
        `entity_id` belongs to a different account.
      security:
        - api_key: []
      parameters:
        - name: entity_id
          in: query
          required: true
          description: Entity id (plain integer).
          schema:
            type: integer
        - name: status
          in: query
          required: false
          description: Filter by lifecycle status.
          schema:
            type: string
            enum:
              - draft
              - open
              - closed
        - name: period_start
          in: query
          required: false
          description: ISO date (YYYY-MM-DD) lower bound on `created_at`. Required together with period_end.
          schema:
            type: string
            format: date
        - name: period_end
          in: query
          required: false
          description: ISO date (YYYY-MM-DD) upper bound on `created_at`. Required together with period_start.
          schema:
            type: string
            format: date
        - name: page
          in: query
          required: false
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            default: 50
            maximum: 100
      responses:
        "200":
          description: paginated list of capital calls
          content:
            application/json:
              schema:
                type: object
                required:
                  - capital_calls
                  - page
                  - per_page
                  - total
                properties:
                  capital_calls:
                    type: array
                    items:
                      $ref: "#/components/schemas/CapitalCallHeader"
                  page:
                    type: integer
                  per_page:
                    type: integer
                  total:
                    type: integer
        "400":
          description: invalid parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Entity not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/capital_calls/{id}":
    get:
      operationId: get_capital_call
      x-agent-tool: true
      summary: Show one capital call
      tags:
        - Capital Calls
      description: |
        Returns the header block for a single capital call. Use
        `/api/v1/capital_calls/{id}/details` for per-LP rows.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: capital call found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CapitalCallHeader"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Capital call not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/capital_calls/{id}/details":
    get:
      operationId: list_capital_call_details
      x-agent-tool: true
      summary: List per-LP detail rows under a capital call
      tags:
        - Capital Calls
      description: |
        Returns the per-capital-account line items (LPs/GPs) attached to a
        capital call: amount called, the LP's committed capital, wired amount
        and wire date (from reconciled wire transactions on the period),
        variance (wired − called) and a derived payment_status
        (fully_paid/underpaid/overpaid/unpaid/prepaid/over_commitment/
        any_variance/unknown).
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Capital call id (plain integer).
          schema:
            type: integer
        - name: page
          in: query
          required: false
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            default: 50
            maximum: 100
      responses:
        "200":
          description: paginated list of capital call details
          content:
            application/json:
              schema:
                type: object
                required:
                  - details
                  - page
                  - per_page
                  - total
                properties:
                  details:
                    type: array
                    items:
                      $ref: "#/components/schemas/CapitalCallDetail"
                  page:
                    type: integer
                  per_page:
                    type: integer
                  total:
                    type: integer
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Capital call not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/financial_reports":
    get:
      operationId: list_financial_reports
      x-agent-tool: true
      summary: List financial report generation jobs
      tags:
        - Financial Reports
      description:
        Returns a paginated list of financial report generation jobs that
        belong to the authenticated token's account, regardless of which
        token within that account created them. Sorted by `created_at`
        descending. Successful jobs include download URLs for the
        generated files.
      security:
        - api_key: []
      parameters:
        - name: page
          in: query
          description: 0-indexed page number (50 jobs per page).
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: paginated list of jobs
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/financial_report_job"
                  pagination:
                    type: object
                    properties:
                      total_count:
                        type: integer
                      current_page:
                        type: integer
                      total_pages:
                        type: integer
        "401":
          description: Unauthorized
    post:
      operationId: generate_financial_report
      x-agent-tool: true
      summary: Generate a financial report
      tags:
        - Financial Reports
      description:
        Queues an asynchronous financial report generation job. The job
        includes every entity of the requested
        `entity_type` within the token's account. Use the returned
        `status_url` to poll job status and download generated files after
        the job succeeds.
      security:
        - api_key: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                entity_type:
                  type: string
                  default: fund
                  description:
                    Selects which class of account organization to include in the
                    report. The job covers every firm-admin-enabled entity of
                    this type within the authenticated token's account.
                  enum:
                    - fund
                    - management_company
                    - general_partnership
                report_types:
                  type: array
                  description:
                    Which report types to include in the generated file. Each
                    type becomes one worksheet (xlsx) or section (pdf). Required
                    unless `all_reports=true` is sent instead.
                  items:
                    type: string
                    enum:
                      - balance_sheet
                      - income_statement
                      - schedule_of_investments
                      - statement_of_changes
                all_reports:
                  type: boolean
                  default: false
                  description:
                    Shortcut to include every supported report type. When `true`,
                    `report_types` may be omitted; when `false` or absent,
                    `report_types` is required.
                period:
                  type: string
                  default: last_financial_year
                  description:
                    Reporting period for the generated report. Defaults to
                    `last_financial_year`. Pass `custom` together with
                    `custom_start_date` and `custom_end_date` to specify an
                    explicit date range; for every other value the dates are
                    derived from the period name.
                  enum:
                    - this_month
                    - this_quarter
                    - this_financial_year
                    - last_month
                    - last_quarter
                    - last_financial_year
                    - month_to_date
                    - quarter_to_date
                    - year_to_date
                    - custom
                custom_start_date:
                  type: string
                  format: date
                  description:
                    ISO 8601 date (YYYY-MM-DD). Required when `period=custom`;
                    ignored otherwise.
                custom_end_date:
                  type: string
                  format: date
                  description:
                    ISO 8601 date (YYYY-MM-DD). Required when `period=custom`;
                    ignored otherwise. Must be on or after `custom_start_date`.
                format:
                  type: string
                  default: xlsx
                  description:
                    Output format for the generated file. `xlsx` produces a
                    single workbook with one worksheet per report type plus
                    supplemental worksheets (PAA, General Ledger, Journal
                    Entries). `pdf` produces a single document with the same
                    sections.
                  enum:
                    - xlsx
                    - pdf
              required:
                - report_types
            examples:
              balance_sheet:
                value:
                  report_types:
                    - balance_sheet
                  period: year_to_date
                  format: xlsx
      responses:
        "202":
          description: report generation job queued
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/financial_report_job"
        "400":
          description: Invalid parameter
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Account organization not found
        "422":
          description: Validation failed
  "/api/v1/financial_reports/{id}":
    get:
      operationId: get_financial_report
      x-agent-tool: true
      summary: Get financial report generation status
      tags:
        - Financial Reports
      description:
        Returns the current status for a financial report generation job
        created by the authenticated token. Successful jobs include download
        URLs for the generated files.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: job status found
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/financial_report_job"
        "401":
          description: Unauthorized
        "404":
          description: Job not found
  "/api/v1/events":
    get:
      operationId: list_events
      x-agent-tool: true
      summary: List Events
      tags:
        - Events
      description:
        Gets a paginated list of Events in your account. The response will
        have a data array with the events and a pagination object with the total count,
        current page, and total pages. Pages are 0-indexed, so the first page is page
        0. The Events returned can be filtered and ordered. Order direction is ascending
        by default.
      security:
        - api_key: []
      parameters:
        - name: title
          in: query
          description: Event title includes this string (case-insensitive)
          schema:
            type: string
        - name: start_date
          in: query
          description: Only events starting on or after this date (ISO 8601 format)
          schema:
            type: string
        - name: end_date
          in: query
          description: Only events ending on or before this date (ISO 8601 format)
          schema:
            type: string
        - name: published
          in: query
          description: Filter by published status
          schema:
            type: boolean
        - name: access_type
          in: query
          description: Filter by access type
          schema:
            $ref: "#/components/schemas/AccessType"
        - name: page
          in: query
          description: Page number (0-indexed)
          default: 0
          schema:
            type: integer
        - name: order_by
          in: query
          enum:
            - title
            - event_date
            - created_at
          description: Field to order by
          default: event_date
          schema:
            type: string
        - name: order_direction
          in: query
          enum:
            - asc
            - desc
          description: Order direction
          default: asc
          schema:
            type: string
      responses:
        "200":
          description: no errors
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      - id: 123
                        unique_event_id: startup-pitch-night-2024
                        landing_page_header_text: Startup Pitch Night 2024
                        landing_page_super_text:
                          Join us for an exciting evening of
                          innovation
                        event_when: "2024-03-15T18:00:00"
                        event_when_timezone: America/New_York
                        event_duration_minutes: 120
                        event_where_name: Innovation Hub
                        event_where_address: 123 Tech Street, San Francisco, CA
                        virtual_link:
                        attendance_limit: 100
                        access_type: public_access
                        published: true
                        shareable: true
                        waiting_list_enabled: false
                        rsvp_yes_count: 45
                        rsvp_no_count: 12
                        rsvp_waiting_list_count: 0
                        registration_url: https://your-domain.com/events/startup-pitch-night-2024
                        event_is_upcoming: true
                        event_is_past: false
                        seats_left: 55
                        created_at: "2024-02-01T10:00:00Z"
                        updated_at: "2024-02-15T14:30:00Z"
                    pagination:
                      total_count: 25
                      current_page: 0
                      total_pages: 1
        "401":
          description: Unauthorized
    post:
      operationId: create_event
      x-agent-tool: true
      summary: Create Event
      tags:
        - Events
      description:
        Creates a new Event in your account. A successful request will
        return the created event data with success status. The event will automatically
        have an associated pipeline and questionnaire created for guest management.
      security:
        - api_key: []
      parameters: []
      responses:
        "201":
          description: event created
          content:
            application/json:
              examples:
                created:
                  value:
                    success: true
                    data:
                      id: 124
                      unique_event_id: new-test-event
                      landing_page_header_text: New Test Event
                      registration_url: https://your-domain.com/events/new-test-event
                      created_at: "2024-02-20T10:00:00Z"
                    message: Event created successfully
        "400":
          description: bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                invalid_timezone:
                  value:
                    error:
                      code: invalid_timezone
                      message: Invalid timezone 'Invalid/Timezone'
                      field: event_when_timezone
                      valid_values:
                        - America/New_York
                        - Etc/UTC
                validation_errors:
                  value:
                    error:
                      code: validation_failed
                      message: Event title can't be blank and Event date can't be blank
                      details:
                        - Event title can't be blank
                        - Event date can't be blank
        "401":
          description: Unauthorized
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: object
                  properties:
                    landing_page_header_text:
                      type: string
                      description: Event title
                    landing_page_super_text:
                      type: string
                      description: Event subtitle
                    landing_page_body_text:
                      type: string
                      description: Event description
                    event_when:
                      type: string
                      format: date-time
                      description: Event start time (ISO 8601 format)
                    event_when_timezone:
                      $ref: "#/components/schemas/TimeZone"
                      description: Event timezone (e.g., America/New_York)
                    event_duration_minutes:
                      type: integer
                      description: Event duration in minutes
                      default: 60
                    event_where_name:
                      type: string
                      description: Venue or location name
                    event_where_address:
                      type: string
                      description: Physical address for in-person events
                    virtual_link:
                      type: string
                      description: Virtual meeting link for online events
                    attendance_limit:
                      type: integer
                      description: Maximum number of attendees (0 for unlimited)
                      default: 0
                    access_type:
                      $ref: "#/components/schemas/AccessType"
                      description: Event access type
                      default: public_access
                    published:
                      type: boolean
                      description: Whether the event is published
                      default: false
                    shareable:
                      type: boolean
                      description: Whether the event can be shared
                      default: true
                    waiting_list_enabled:
                      type: boolean
                      description: Whether to enable waiting list
                      default: false
                    phone_number_setting:
                      type: string
                      description: Phone number requirement setting
                      default: show_not_required
                    bottom_banner_header_text:
                      type: string
                      description: Bottom banner header text
                    bottom_banner_super_text:
                      type: string
                      description: Bottom banner subtitle text
                    hero_image_url:
                      type: string
                      description: Event image URL
                  required:
                    - landing_page_header_text
                    - event_when
                    - event_when_timezone
              required:
                - event
  "/api/v1/events/{id}":
    get:
      operationId: get_event
      x-agent-tool: true
      summary: Show Event
      tags:
        - Events
      description: >
        Get a single Event by ID or unique_event_id. The default response shape
        is unchanged from prior versions. The optional include= and fields=
        parameters are purely opt-in - include= replaces the default include set
        (currently empty for events) with a caller-specified set, and fields=
        narrows top-level columns. include_guests=true is preserved as a
        backwards-compatible alias for include=guests.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Event ID or unique_event_id
          schema:
            type: string
        - name: include
          in: query
          description: |
            Comma-separated list of nested resources to embed in the response.
            Unknown values are ignored silently. Allowed values: guests.
          required: false
          schema:
            type: string
        - name: fields
          in: query
          description: |
            Comma-separated list of top-level fields to return. Unknown values
            are ignored silently. When `fields=` is supplied, the response is always narrowed; `id` and `name` are always present, and if no supplied keys match the whitelist the response collapses to just `{id, name}`.
          required: false
          schema:
            type: string
        - name: include_guests
          in: query
          description: Deprecated alias for include=guests; kept for backwards compatibility.
          default: false
          schema:
            type: boolean
      responses:
        "200":
          description: event found with guests
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      id: 123
                      unique_event_id: show-test-event
                      landing_page_header_text: Show Test Event
                      landing_page_super_text:
                        Join us for an exciting evening of
                        innovation
                      event_when: "2024-03-15T18:00:00"
                      event_when_timezone: America/New_York
                      event_duration_minutes: 120
                      event_where_name: Innovation Hub
                      event_where_address: 123 Tech Street, San Francisco, CA
                      virtual_link:
                      attendance_limit: 100
                      access_type: public_access
                      published: true
                      shareable: true
                      waiting_list_enabled: false
                      rsvp_yes_count: 45
                      rsvp_no_count: 12
                      rsvp_waiting_list_count: 0
                      registration_url: https://your-domain.com/events/show-test-event
                      event_is_upcoming: true
                      event_is_past: false
                      seats_left: 55
                      created_at: "2024-02-01T10:00:00Z"
                      updated_at: "2024-02-15T14:30:00Z"
                success_with_guests:
                  value:
                    data:
                      id: 123
                      unique_event_id: show-test-event
                      landing_page_header_text: Show Test Event
                      registration_url: https://your-domain.com/events/show-test-event
                      fund_event_rsvps:
                        - status: "yes"
                          created_at: "2024-02-10T09:00:00Z"
                          updated_at: "2024-02-10T09:00:00Z"
                          person:
                            id: 456
                            email: john@example.com
                            first_name: John
                            last_name: Doe
                            phone: "+1234567890"
        "404":
          description: not found
        "401":
          description: Unauthorized
    put:
      operationId: update_event
      x-agent-tool: true
      summary: Update Event
      tags:
        - Events
      description: Update an existing Event. Only the provided fields will be updated.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Event ID or unique_event_id
          schema:
            type: string
      responses:
        "200":
          description: event updated
          content:
            application/json:
              examples:
                updated:
                  value:
                    success: true
                    data:
                      id: 124
                      landing_page_header_text: Updated Event Title
                      updated_at: "2024-02-20T11:00:00Z"
                    message: Event updated successfully
        "404":
          description: not found
        "401":
          description: Unauthorized
        "400":
          description: invalid timezone
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                invalid_timezone:
                  value:
                    error:
                      code: invalid_timezone
                      message: Invalid timezone 'Invalid/Timezone'
                      field: event_when_timezone
                      valid_values:
                        - America/New_York
                        - Etc/UTC
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: object
                  properties:
                    landing_page_header_text:
                      type: string
                      description: Event title
                    landing_page_super_text:
                      type: string
                      description: Event subtitle
                    landing_page_body_text:
                      type: string
                      description: Event description
                    event_when:
                      type: string
                      format: date-time
                      description: Event start time (ISO 8601 format)
                    event_when_timezone:
                      $ref: "#/components/schemas/TimeZone"
                      description: Event timezone
                    event_duration_minutes:
                      type: integer
                      description: Event duration in minutes
                    event_where_name:
                      type: string
                      description: Venue or location name
                    event_where_address:
                      type: string
                      description: Physical address
                    virtual_link:
                      type: string
                      description: Virtual meeting link
                    attendance_limit:
                      type: integer
                      description: Maximum number of attendees
                    access_type:
                      $ref: "#/components/schemas/AccessType"
                      description: Event access type
                    published:
                      type: boolean
                      description: Whether the event is published
                    shareable:
                      type: boolean
                      description: Whether the event can be shared
                    waiting_list_enabled:
                      type: boolean
                      description: Whether to enable waiting list
                    phone_number_setting:
                      type: string
                      description: Phone number requirement setting
                    bottom_banner_header_text:
                      type: string
                      description: Bottom banner header text
                    bottom_banner_super_text:
                      type: string
                      description: Bottom banner subtitle text
                    hero_image_url:
                      type: string
                      description: Event image URL
              required:
                - event
    delete:
      operationId: delete_event
      x-agent-tool: true
      summary: Delete Event
      tags:
        - Events
      description:
        Delete an Event and all associated data including pipeline and
        questionnaire.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Event ID or unique_event_id
          schema:
            type: string
      responses:
        "200":
          description: event deleted
          content:
            application/json:
              examples:
                deleted:
                  value:
                    success: true
                    message: Event deleted successfully
        "404":
          description: not found
        "401":
          description: Unauthorized
  "/api/v1/events/{id}/guests":
    post:
      operationId: add_event_guests
      x-agent-tool: true
      summary: Add Guests to Event
      tags:
        - Events
      description:
        Add multiple guests to an event. Guests will be created as People
        if they don't exist and added to the event pipeline. The response includes
        arrays of added, duplicate, and error results for processing feedback.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Event ID or unique_event_id
          schema:
            type: string
      responses:
        "200":
          description: guests added
          content:
            application/json:
              examples:
                guests_added:
                  value:
                    success: true
                    results:
                      added:
                        - john.doe@example.com
                        - jane.smith@example.com
                      duplicates: []
                      errors: []
                    message: Processed 2 guests
        "400":
          description: bad request
        "404":
          description: not found
        "401":
          description: Unauthorized
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: object
                  properties:
                    guests:
                      type: array
                      items:
                        type: object
                        properties:
                          first_name:
                            type: string
                            description: Guest first name
                          middle_name:
                            type: string
                            description: Guest middle name
                          last_name:
                            type: string
                            description: Guest last name
                          email:
                            type: string
                            description: Guest email address
                          phone:
                            type: string
                            description: Guest phone number
                          linkedin:
                            type: string
                            description: Guest LinkedIn URL
                          tag_list:
                            type: string
                            description: Comma-separated tags
                          address:
                            type: object
                            properties:
                              street:
                                type: string
                              city:
                                type: string
                              state:
                                type: string
                              country:
                                type: string
                              zipcode:
                                type: string
                          organizations:
                            type: array
                            items:
                              type: object
                              properties:
                                name:
                                  type: string
                                  description: Organization name
                                title:
                                  type: string
                                  description: Job title at organization
                        required:
                          - first_name
                          - last_name
                          - email
                  required:
                    - guests
              required:
                - event
    get:
      operationId: list_event_guests
      x-agent-tool: true
      summary: Get Event Guests
      tags:
        - Events
      description:
        Get a paginated list of guests for an event with their RSVP status
        and pipeline stage information. Results can be filtered by email or pipeline
        stage.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Event ID or unique_event_id
          schema:
            type: string
        - name: page
          in: query
          description: Page number (0-indexed)
          default: 0
          schema:
            type: integer
        - name: email
          in: query
          description: Filter by email (partial match)
          schema:
            type: string
        - name: stage
          in: query
          description: Filter by pipeline stage name
          schema:
            type: string
      responses:
        "200":
          description: guests retrieved
          content:
            application/json:
              examples:
                guests_list:
                  value:
                    data:
                      - id: 456
                        email: guest1@example.com
                        first_name: John
                        last_name: Doe
                        phone: "+1234567890"
                        stage: RSVP - Yes
                        rsvp_status: "yes"
                        created_at: "2024-02-10T09:00:00Z"
                        updated_at: "2024-02-15T10:30:00Z"
                      - id: 457
                        email: guest2@example.com
                        first_name: Jane
                        last_name: Smith
                        phone: "+1987654321"
                        stage: RSVP - No
                        rsvp_status: "no"
                        created_at: "2024-02-10T09:00:00Z"
                        updated_at: "2024-02-15T10:30:00Z"
                    pagination:
                      total_count: 2
                      current_page: 0
                      total_pages: 1
        "404":
          description: event not found
        "401":
          description: Unauthorized
  "/api/v1/events/{id}/rsvp":
    patch:
      operationId: update_event_rsvp
      x-agent-tool: true
      summary: Update Guest RSVP
      tags:
        - Events
      description:
        Update a guest's RSVP status for an event. This will also update
        the guest's pipeline stage accordingly.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Event ID or unique_event_id
          schema:
            type: string
      responses:
        "200":
          description: rsvp updated
          content:
            application/json:
              examples:
                rsvp_updated:
                  value:
                    success: true
                    data:
                      person_email: test@example.com
                      status: "yes"
                      event_id: 123
                    message: RSVP updated successfully
        "400":
          description: bad request
        "404":
          description: event not found
        "401":
          description: Unauthorized
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                rsvp:
                  type: object
                  properties:
                    email:
                      type: string
                      description: Guest email address
                    status:
                      $ref: "#/components/schemas/RsvpStatus"
                      description: RSVP status
                  required:
                    - email
                    - status
              required:
                - rsvp
  "/api/v1/files":
    get:
      operationId: list_files
      x-agent-tool: true
      summary: List Files
      tags:
        - Files
      description:
        Gets a paginated list of Files in your Files. The response will
        have a data array with the files and a pagination object with the total count,
        current page, and total pages. Pages are 0-indexed, so the first page is page
        0. The Files returned can be filtered and ordered. Order direction is ascending
        by default.
      security:
        - api_key: []
      parameters:
        - name: name
          in: query
          description: Name includes this string (case-insensitive)
          schema:
            type: string
        - name: folder_id
          in: query
          description: Filter by folder ID
          schema:
            type: integer
        - name: extension
          in: query
          description: Filter by file extension (e.g., .pdf, .doc)
          schema:
            type: string
        - name: created_before
          in: query
          description: Only files created on or before this date
          schema:
            type: string
        - name: created_after
          in: query
          description: Only files created on or after this date
          schema:
            type: string
        - name: page
          in: query
          description: Page number (0-indexed)
          default: 0
          schema:
            type: integer
        - name: order_by
          in: query
          enum:
            - name
            - created_at
            - updated_at
          description:
            "Field to order by:\n * `name` \n * `created_at` \n * `updated_at`
            \n "
          default: created_at
          schema:
            type: string
        - name: order_direction
          in: query
          enum:
            - asc
            - desc
          description: Order direction
          default: asc
          schema:
            type: string
      responses:
        "200":
          description: no errors
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      - id: 1
                        name: Annual Report
                        description: Company annual report for 2024
                        created_at: "2024-08-06T18:32:52.335Z"
                        updated_at: "2024-08-06T18:32:52.335Z"
                        extension: ".pdf"
                        file_name: annual-report-2024.pdf
                        data_room_folders:
                          - id: 1
                            name: Financial Documents
                      - id: 2
                        name: Company Logo
                        description: Official company logo
                        created_at: "2024-08-06T18:32:52.335Z"
                        updated_at: "2024-08-06T18:32:52.335Z"
                        extension: ".png"
                        file_name: company-logo.png
                        data_room_folders:
                          - id: 2
                            name: Brand Assets
                    pagination:
                      total_count: 2
                      current_page: 0
                      total_pages: 1
        "401":
          description: Unauthorized
    post:
      operationId: upload_file
      x-agent-tool: true
      summary: Upload File
      tags:
        - Files
      description: |
        Uploads a new file.

        **Request format**

        Must be sent as `multipart/form-data` with file params **nested under
        the `file` key**:

        - `file[file]` — the upload (required)
        - `file[name]` — optional custom name
        - `file[description]` — optional description
        - `folder_id` — optional, **top-level** (not under `file[]`). If omitted,
          the file is placed in the account's default folder.
        - `file[attachable_type]` and `file[attachable_id]` — optional pair. When
          both are provided, the upload is *also* attached to the given record so
          it appears in that record's attachment list (in addition to landing in
          the data room). Allowed `attachable_type` values: `Person`,
          `Organization`, `PipelineProspect`. For `PipelineProspect`, the
          attachment is routed to the prospect's underlying `prospectable`
          (Person or Organization) — this matches UI behavior.

        **Supported extensions**

        `.ppt`, `.pptx`, `.key`, `.doc`, `.docx`, `.mov`, `.mp4`, `.ogg`, `.pdf`,
        `.png`, `.jpg`, `.jpeg`

        **Example**

        ```
        curl -X POST https://decilehub.com/api/v1/files \
          -H "Authorization: Bearer $TOKEN" \
          -F "file[file]=@./doc.pdf" \
          -F "file[name]=My Doc" \
          -F "folder_id=123"
        ```
      security:
        - api_key: []
      parameters: []
      responses:
        "201":
          description: file uploaded successfully
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      id: 1
                      name: Test Document
                      description: A test document
                      created_at: "2024-08-06T18:32:52.335Z"
                      updated_at: "2024-08-06T18:32:52.335Z"
                      extension: ".pdf"
                      file_name: test.pdf
                      data_room_folders: []
                      attachment:
                    message: File uploaded successfully
                success_with_attachment:
                  value:
                    data:
                      id: 1
                      name: Meeting Transcript
                      description:
                      created_at: "2024-08-06T18:32:52.335Z"
                      updated_at: "2024-08-06T18:32:52.335Z"
                      extension: ".pdf"
                      file_name: transcript.pdf
                      data_room_folders: []
                      attachment:
                        id: 42
                        subject_type: Person
                        subject_id: 17
                        name: Meeting Transcript
                    message: File uploaded successfully
        "400":
          description: bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                invalid_request_format:
                  value:
                    error:
                      code: bad_request
                      message:
                        "Invalid request format. Expected multipart/form-data with
                        nested params: file[file]=<upload>, optional file[name],
                        file[description], and top-level folder_id."
                file_required:
                  value:
                    error:
                      code: bad_request
                      message: File is required
                      field: file
                invalid_file_type:
                  value:
                    error:
                      code: bad_request
                      message:
                        "Invalid file type. Allowed: .ppt, .pptx, .key, .doc, .docx,
                        .mov, .mp4, .ogg, .pdf, .png, .jpg, .jpeg"
                missing_parameter:
                  value:
                    error:
                      code: bad_request
                      message:
                        "Missing required parameter: file. Expected multipart/form-data
                        with file[file]=<upload>."
                      field: file
        "401":
          description: Unauthorized
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                "file[file]":
                  type: string
                  format: binary
                  description: The file to upload (required)
                "file[name]":
                  type: string
                  description: Custom name for the file (defaults to original filename)
                "file[description]":
                  type: string
                  description: Optional description for the file
                "file[attachable_type]":
                  type: string
                  enum:
                    - Person
                    - Organization
                    - PipelineProspect
                  description:
                    Optional record type to attach this file to. Must be supplied
                    together with `file[attachable_id]`. When `PipelineProspect`,
                    the attachment is routed to the prospect's underlying
                    Person/Organization (matches UI behavior).
                "file[attachable_id]":
                  type: integer
                  description:
                    Optional id (in current account) of the record to attach this
                    file to. Must be supplied together with `file[attachable_type]`.
                folder_id:
                  type: integer
                  description:
                    Optional folder to place the file in. Top-level field, NOT
                    nested under `file[]`. If omitted, uses the account's default folder.
              required:
                - "file[file]"
  "/api/v1/files/{id}":
    get:
      operationId: get_file
      x-agent-tool: true
      summary: Show File
      tags:
        - Files
      description:
        Gets details for a specific file, including metadata and associated
        folders.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          description: File ID
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: file found
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      id: 1
                      name: Test Document
                      description: A test document for API documentation
                      created_at: "2024-08-06T18:32:52.335Z"
                      updated_at: "2024-08-06T18:32:52.335Z"
                      extension: ".pdf"
                      file_name: test-document.pdf
                      data_room_folders:
                        - id: 1
                          name: Test Folder
        "400":
          description: file not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                not_found:
                  value:
                    error:
                      code: bad_request
                      message: File not found
        "401":
          description: Unauthorized
    put:
      operationId: update_file
      x-agent-tool: true
      summary: Update File
      tags:
        - Files
      description: |
        Updates a file. You can update just the metadata (name/description),
        or upload a new version of the file — uploading a new file creates a new
        version while maintaining history.

        **Request format**

        Must be sent as `multipart/form-data` with params **nested under the
        `file` key**. All fields are optional:

        - `file[name]` — updated name
        - `file[description]` — updated description
        - `file[file]` — new file upload (creates a new version)
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          description: File ID
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: new version created successfully
          content:
            application/json:
              examples:
                metadata_update:
                  value:
                    data:
                      id: 1
                      name: Updated Document
                      description: Updated description
                      created_at: "2024-08-06T18:32:52.335Z"
                      updated_at: "2024-08-06T18:32:52.335Z"
                      extension: ".pdf"
                      file_name: test-document.pdf
                      data_room_folders:
                        - id: 1
                          name: Test Folder
                    message: File updated successfully
                new_version:
                  value:
                    data:
                      id: 2
                      name: New Version
                      description: New version description
                      created_at: "2024-08-06T18:32:52.335Z"
                      updated_at: "2024-08-06T18:32:52.335Z"
                      extension: ".pdf"
                      file_name: test.pdf
                      data_room_folders:
                        - id: 1
                          name: Test Folder
                    message: New file version created successfully
        "404":
          description: file not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                not_found:
                  value:
                    error:
                      code: not_found
                      message: File not found
        "401":
          description: Unauthorized
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                "file[file]":
                  type: string
                  format: binary
                  description: New file to upload (creates new version)
                "file[name]":
                  type: string
                  description: Updated name for the file
                "file[description]":
                  type: string
                  description: Updated description for the file
  "/api/v1/files/{id}/download":
    get:
      operationId: download_file
      x-agent-tool: true
      summary: Download File
      tags:
        - Files
      description:
        Downloads a file from your Files. Returns the file content with
        appropriate headers for download.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          description: File ID
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: file downloaded successfully
        "400":
          description: file not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                not_found:
                  value:
                    error:
                      code: bad_request
                      message: File not found
        "401":
          description: Unauthorized
  "/api/v1/files/{id}/save_to_folder":
    post:
      operationId: save_file_to_folder
      # No x-agent-tool: this agent tool is hand-seeded (see
      # db/seeds/agent_platform_save_to_folder_tools.rb), like file_search.
      # Marking it x-agent-tool would make SyncAgentPlatformHttpToolsFromSwaggerTask
      # claim ownership and raise OwnershipConflictError against the seeded row.
      summary: Save File To Folder
      tags:
        - Files
      description: |
        Saves an in-message file (an Attachment) into a data room folder so it is
        durably stored and visible to the folder's audience.

        The `{id}` path parameter is the Attachment id. The target folder is given
        by the required top-level `folder_id` in the JSON body.

        The operation is idempotent on the pair of (file content, folder): if the
        same file content already exists in the folder, no new placement is created
        and the existing one is returned with `status: unchanged`. Otherwise a new
        placement is created and returned with `status: created`.

        The response includes `folder_path` (a breadcrumb such as "Parent > Child")
        and `audience` (a human readable phrase describing who can see the file),
        which the agent uses to compose its success message.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          description: Attachment ID
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                folder_id:
                  type: integer
                  description: ID of the data room folder to save the file into.
              required:
                - folder_id
      responses:
        "201":
          description: file saved to folder
          content:
            application/json:
              examples:
                created:
                  value:
                    folder_file_id: 101
                    status: created
                    folder_path: Financials > 2024
                    audience: This file will be visible to everyone in your account.
        "200":
          description: file already present in folder (idempotent)
          content:
            application/json:
              examples:
                unchanged:
                  value:
                    folder_file_id: 101
                    status: unchanged
                    folder_path: Financials > 2024
                    audience: This file will be visible to everyone in your account.
        "401":
          description: Unauthorized
        "403":
          description: forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: file or folder not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                not_found:
                  value:
                    error:
                      code: not_found
                      message: File or folder not found
        "400":
          description: missing folder_id or attachment has no file content
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                bad_request:
                  value:
                    error:
                      code: bad_request
                      message: "Missing required parameter: folder_id."
  "/api/v1/folders/search":
    get:
      operationId: search_data_room_folders
      # No x-agent-tool: hand-seeded agent tool (see
      # db/seeds/agent_platform_save_to_folder_tools.rb), like file_search. The
      # swagger-sync task must not claim ownership of the seeded row.
      summary: Search Data Room Folders
      tags:
        - Folders
      description: |
        Searches data room folders by name to help disambiguate a user's folder
        reference (for example "save it to Acme") into folder ids.

        Only folders in your account that you can read are returned. The match is
        case insensitive on the folder name and is capped at 50 results. Each match
        includes the folder id, name, a breadcrumb path (for example "Parent > Child"),
        and an audience phrase describing who can see files saved to that folder. The
        agent uses these to compose its confirmation request.

        The q parameter is required (a missing or blank q returns 400). A query
        that matches no folders returns an empty matches array.
      security:
        - api_key: []
      parameters:
        - name: q
          in: query
          description: Substring to match against folder names (case insensitive).
          required: true
          schema:
            type: string
      responses:
        "200":
          description: no errors
          content:
            application/json:
              examples:
                success:
                  value:
                    matches:
                      - folder_id: 1
                        name: Acme Financials
                        path: Portfolio > Acme Financials
                        audience: This file will be visible to everyone in your account.
                empty:
                  value:
                    matches: []
        "400":
          description: missing required parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                bad_request:
                  value:
                    error:
                      code: bad_request
                      message: "Missing required parameter: q."
        "401":
          description: Unauthorized
  "/api/v1/folders":
    get:
      operationId: list_folders
      x-agent-tool: true
      summary: List Folders
      tags:
        - Files
      description:
        Gets a paginated list of Folders in your Files. The response will
        have a data array with the folders and a pagination object with the total
        count, current page, and total pages. Pages are 0-indexed, so the first page
        is page 0. The Folders returned can be filtered and ordered. Order direction
        is ascending by default.
      security:
        - api_key: []
      parameters:
        - name: name
          in: query
          description: Name includes this string (case-insensitive)
          schema:
            type: string
        - name: folder_type
          in: query
          description: Filter by folder type
          schema:
            type: string
        - name: parent_id
          in: query
          description: Filter by parent folder ID
          schema:
            type: integer
        - name: created_before
          in: query
          description: Only folders created on or before this date
          schema:
            type: string
        - name: created_after
          in: query
          description: Only folders created on or after this date
          schema:
            type: string
        - name: page
          in: query
          description: Page number (0-indexed)
          default: 0
          schema:
            type: integer
        - name: order_by
          in: query
          enum:
            - name
            - created_at
            - updated_at
          description:
            "Field to order by:\n * `name` \n * `created_at` \n * `updated_at`
            \n "
          default: created_at
          schema:
            type: string
        - name: order_direction
          in: query
          enum:
            - asc
            - desc
          description: Order direction
          default: asc
          schema:
            type: string
      responses:
        "200":
          description: no errors
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      - id: 1
                        name: Root Folder
                        description: A root folder for organizing files
                        folder_type: firm
                        created_at: "2024-08-06T18:32:52.335Z"
                        updated_at: "2024-08-06T18:32:52.335Z"
                        shareable?: true
                        parent:
                        root_parent:
                      - id: 2
                        name: Child Folder
                        description: A child folder
                        folder_type: firm
                        created_at: "2024-08-06T18:32:52.335Z"
                        updated_at: "2024-08-06T18:32:52.335Z"
                        shareable?: false
                        parent:
                          id: 1
                          name: Root Folder
                        root_parent:
                          id: 1
                          name: Root Folder
                    pagination:
                      total_count: 2
                      current_page: 0
                      total_pages: 1
        "401":
          description: Unauthorized
    post:
      operationId: create_folder
      x-agent-tool: true
      summary: Create Folder
      tags:
        - Files
      description:
        Creates a new folder in your Files. You can optionally specify
        a parent folder by providing a parent_id. Folder names must be unique within
        the same parent folder.
      security:
        - api_key: []
      parameters: []
      responses:
        "201":
          description: child folder created successfully
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      id: 1
                      name: New Test Folder
                      description:
                      folder_type: standard
                      created_at: "2024-08-06T18:32:52.335Z"
                      updated_at: "2024-08-06T18:32:52.335Z"
                      shareable?: true
                      parent:
                      root_parent:
                    message: Folder created successfully
                child_folder:
                  value:
                    data:
                      id: 2
                      name: Child Folder
                      description:
                      folder_type: standard
                      created_at: "2024-08-06T18:32:52.335Z"
                      updated_at: "2024-08-06T18:32:52.335Z"
                      shareable?: true
                      parent:
                        id: 1
                        name: Parent Folder
                      root_parent:
                        id: 1
                        name: Parent Folder
                    message: Folder created successfully
        "400":
          description: malformed request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missing_name:
                  value:
                    error:
                      code: bad_request
                      message: Folder name is required
                      field: name
                duplicate_name:
                  value:
                    error:
                      code: validation_failed
                      message: Name has already been taken
                      details:
                        - Name has already been taken
                malformed_request:
                  value:
                    error:
                      code: bad_request
                      message: "param is missing or the value is empty or invalid: folder"
                      field: folder
        "404":
          description: parent folder not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                parent_not_found:
                  value:
                    error:
                      code: not_found
                      message: Parent folder not found
        "401":
          description: Unauthorized
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                folder:
                  type: object
                  properties:
                    name:
                      type: string
                      description: The name of the folder
                    parent_id:
                      type: integer
                      description: Optional parent folder ID
                  required:
                    - name
              required:
                - folder
  "/api/v1/folders/{id}":
    get:
      operationId: get_folder
      x-agent-tool: true
      summary: Show Folder
      tags:
        - Files
      description:
        'Gets details for a specific folder, including optional child folders
        and files. Use the contents parameter to specify what to include: "folders",
        "files", or "folders,files". Default includes both folders and files.'
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          description: Folder ID
          required: true
          schema:
            type: integer
        - name: contents
          in: query
          description: 'What to include: "folders", "files", "folders,files", or "none"'
          default: folders,files
          schema:
            type: string
        - name: name
          in: query
          description: Filter child items by name (case-insensitive)
          schema:
            type: string
        - name: order_by
          in: query
          enum:
            - name
            - created_at
            - updated_at
          description:
            "Field to order child items by:\n * `name` \n * `created_at`
            \n * `updated_at` \n "
          default: created_at
          schema:
            type: string
        - name: order_direction
          in: query
          enum:
            - asc
            - desc
          description: Order direction for child items
          default: asc
          schema:
            type: string
      responses:
        "200":
          description: folder found
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      id: 1
                      name: Test Folder
                      description: A test folder
                      folder_type: firm
                      created_at: "2024-08-06T18:32:52.335Z"
                      updated_at: "2024-08-06T18:32:52.335Z"
                      shareable?: true
                      parent:
                      root_parent:
                    children:
                      folders:
                        - id: 2
                          name: Child Folder
                          description: A child folder
                          folder_type: firm
                          created_at: "2024-08-06T18:32:52.335Z"
                          updated_at: "2024-08-06T18:32:52.335Z"
                          shareable?: false
                          parent:
                            id: 1
                            name: Test Folder
                          root_parent:
                            id: 1
                            name: Test Folder
                      folders_count: 1
                      folders_displayed: 1
                      files:
                        - id: 1
                          created_at: "2024-08-06T18:32:52.335Z"
                          updated_at: "2024-08-06T18:32:52.335Z"
                          data_room_file:
                            id: 1
                            name: Test Document
                            description: A test document
                            created_at: "2024-08-06T18:32:52.335Z"
                            updated_at: "2024-08-06T18:32:52.335Z"
                            extension: ".pdf"
                            file_name: test-document.pdf
                      files_count: 1
                      files_displayed: 1
        "404":
          description: folder not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                not_found:
                  value:
                    error:
                      code: not_found
                      message: Folder not found
        "401":
          description: Unauthorized
  "/api/v1/organizations":
    get:
      operationId: list_organizations
      x-agent-tool: true
      summary: List Organizations
      tags:
        - Directory
      description:
        Gets a paginated list of Organizations in your Organizations Directory.
        The response will have a data array with the organizations and a pagination
        object with the total count, current page, and total pages.Pages are 0-indexed,
        so the first page is page 0.The Organizations returned can be filtered and
        ordered. Order direction is ascending by default. The default response
        shape is unchanged from prior versions and embeds notes, people, and
        submit_your_company_response_data. The optional include= parameter is
        purely opt-in - when supplied it replaces the default include set with
        a caller-specified subset; absent the parameter, the legacy default
        is returned verbatim. fields= narrows top-level columns when supplied.
      security:
        - api_key: []
      parameters:
        - name: include
          in: query
          description: |
            Comma-separated list of nested resources to embed. Unknown values
            are ignored silently. Allowed values: notes, people, referred_by,
            submit_your_company_response_data.
          required: false
          schema:
            type: string
        - name: fields
          in: query
          description: |
            Comma-separated list of top-level fields to return. Unknown values
            are ignored silently. When `fields=` is supplied, the response is always narrowed; `id` and `name` are always present, and if no supplied keys match the whitelist the response collapses to just `{id, name}`.
          required: false
          schema:
            type: string
        - name: custom_data_points
          in: query
          description: |
            Optional. Controls embedding of a custom_data_points field on
            each organization.

            - Omit the param entirely to leave custom_data_points out of the
              response (default).
            - Pass `*` to embed every account-declared organization_data_points
              key (subject to the allow-list).
            - Pass a comma-separated list (e.g. `key1,key2`) to narrow the
              embed to that subset. Unknown keys are silently dropped.
            - Pass an empty value to embed an explicit empty
              custom_data_points object.
            - The wildcard always wins, so `*,foo` behaves identically to
              `*`.

            Select-type values are resolved to their human-readable option
            labels rather than the underlying stored value. Internal jsonb
            keys are never returned.
          required: false
          schema:
            type: string
        - name: name
          in: query
          description: Name includes this string (case-insensitive)
          schema:
            type: string
        - name: created_before
          in: query
          description: Only organizations created on or before this date
          schema:
            type: string
        - name: created_after
          in: query
          description: Only organizations created on or after this date
          schema:
            type: string
        - name: page
          in: query
          description: Page number (0-indexed)
          default: 0
          schema:
            type: integer
        - name: order_by
          in: query
          enum:
            - first_name
            - last_name
            - email
            - created_at
          description:
            "Field to order by:\n * `first_name` \n * `last_name` \n * `email`
            \n * `created_at` \n "
          default: created_at
          schema:
            type: string
        - name: order_direction
          in: query
          enum:
            - asc
            - desc
          description: Order direction
          default: asc
          schema:
            type: string
      responses:
        "200":
          description: no errors
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      - id: 13
                        address:
                          city: ""
                          state: ""
                          street: ""
                          country: ""
                          zipcode: ""
                        name: Some Org
                        company_url: https://www.some-organization-site.com
                        short_description: Some short description
                        data:
                          custom_field: custom value
                          another field: another value
                        referred_by:
                          id: 54
                          email: florene.harvey.18@kirlinincllc.com
                          phone: "+918727727698"
                          first_name: Florene
                          middle_name:
                          last_name: Harvey
                        notes:
                          - body: This is a note
                            created_at: "2024-08-22T14:13:06.672Z"
                            author:
                              first_name: Apple
                              last_name: Owner
                          - body: This is another note
                            created_at: "2024-08-22T14:13:11.320Z"
                            author:
                              first_name: Apple
                              last_name: Owner
                        people:
                          - id: 105
                            email: jack@mcglonch.io
                            phone: "1112223344"
                            first_name: Jack
                            middle_name:
                            last_name: McGloncho
                        submit_your_company_response_data:
                          submitted_at: "2024-08-22T14:13:06.672Z"
                          responses:
                            what_problem_are_you_solving:
                              Climate change solutions through
                              AI technology
                            describe_your_product: AI-powered carbon tracking platform
                    pagination:
                      total_count: 1
                      current_page: 0
                      total_pages: 1
    post:
      operationId: create_organizations
      x-agent-tool: true
      summary: Create Organizations
      tags:
        - Directory
      description:
        Adds Organizations to your Organizations Directory. Only the first
        100 Organizations per request will be processed.The response will have a created,
        duplicates, and errors array. The created array will contain the emails of
        the organizations that were created. The duplicates array will contain the
        emails of the organizations that were not created because they already exist
        in the database. The errors array will contain the emails of the organizations
        that were not created because of an error.
      security:
        - api_key: []
      parameters: []
      responses:
        "200":
          description: no errors or creations
          content:
            application/json:
              examples:
                no_creates:
                  value:
                    created: []
                    duplicates:
                      - Test Org
                    errors: []
        "201":
          description: organizations created
          content:
            application/json:
              examples:
                at_least_one_created:
                  value:
                    created:
                      - Test Org
                    duplicates: []
                    errors: []
        "400":
          description: bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                invalid_order_by:
                  value:
                    error:
                      code: invalid_parameter
                      message: "order_by must be one of: title, event_date, created_at"
                      field: order_by
                      valid_values:
                        - title
                        - event_date
                        - created_at
        "401":
          description: Unauthorized
        "422":
          description: unprocessable entity
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                no_creates_and_has_errors:
                  value:
                    error:
                      code: validation_failed
                      message: Some organizations could not be created
                      details:
                        - ": Validation failed: Name can't be blank"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                organizations:
                  type: array
                  items:
                    type: object
                    properties:
                      name:
                        type: string
                      website:
                        type: string
                      description:
                        type: string
                      logo:
                        type: string
                        description: "Logo, as a base64 data URI or a public http(s) image URL. JPG, PNG, WebP, or GIF; max 5 MB."
                      custom_data_points:
                        type: object
                      note:
                        type: string
                      address:
                        "$ref": "#/components/schemas/address"
                      referred_by:
                        "$ref": "#/components/schemas/associated_person"
                      people:
                        type: array
                        items:
                          "$ref": "#/components/schemas/associated_person"
                    required:
                      - name
              required:
                - organizations
  "/api/v1/tasks":
    get:
      operationId: list_tasks
      x-agent-tool: true
      summary: List Tasks
      tags:
        - Tasks
      description:
        Returns a paginated, account-scoped list of Tasks. Optional filters
        narrow by status, assignee, and origin. Pages are 0-indexed. Requires
        the :hub_tasks feature flag to be enabled for the account; otherwise a
        404 is returned.
      security:
        - api_key: []
      parameters:
        - name: status
          in: query
          description: Filter by lifecycle status
          required: false
          schema:
            type: string
            enum:
              - open
              - in_progress
              - completed
              - cancelled
        - name: assignee_id
          in: query
          description: Filter by assignee user id
          required: false
          schema:
            type: integer
        - name: origin
          in: query
          description: Filter by task origin
          required: false
          schema:
            type: string
            enum:
              - agent_suspension
              - user
              - system
        - name: page
          in: query
          description: Page number (0-indexed)
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: tasks listed
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Task"
                  pagination:
                    "$ref": "#/components/schemas/pagination"
        "401":
          description: Unauthorized
        "404":
          description: feature not enabled for this account
    post:
      operationId: create_task
      x-agent-tool: true
      summary: Create Task
      tags:
        - Tasks
      description:
        Creates a user-origin Task assigned to a member of the account. The
        authenticated token's user is recorded as the creator. Requires the
        :hub_tasks feature flag for the account.
      security:
        - api_key: []
      parameters: []
      responses:
        "201":
          description: task created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Task"
        "401":
          description: Unauthorized
        "404":
          description: feature not enabled, or assignee is not an account member
        "422":
          description: validation failed
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                task:
                  type: object
                  properties:
                    title:
                      type: string
                    description:
                      type: string
                    assignee_id:
                      type: integer
                    due_date:
                      type: string
                      format: date
                  required:
                    - title
                    - assignee_id
              required:
                - task
  "/api/v1/tasks/{id}":
    get:
      operationId: get_task
      x-agent-tool: true
      summary: Get Task
      tags:
        - Tasks
      description: Returns a single account-scoped Task by id.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: task found
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Task"
        "401":
          description: Unauthorized
        "404":
          description: task not found, or feature not enabled
    patch:
      operationId: update_task
      x-agent-tool: true
      summary: Update Task
      tags:
        - Tasks
      description:
        Updates a Task's title, description, assignee, due date, or status.
        Permitted for the task creator, current assignee, account owners/admins,
        and Decile admins.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: task updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Task"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: task not found, feature not enabled, or assignee not a member
        "422":
          description: validation failed
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                task:
                  type: object
                  properties:
                    title:
                      type: string
                    description:
                      type: string
                    assignee_id:
                      type: integer
                    due_date:
                      type: string
                      format: date
                    status:
                      type: string
                      enum:
                        - open
                        - in_progress
                        - completed
                        - cancelled
              required:
                - task
  "/api/v1/tasks/{id}/complete":
    post:
      operationId: complete_task
      x-agent-tool: true
      summary: Complete Task
      tags:
        - Tasks
      description:
        Marks a Task as completed. Permitted for the task creator, current
        assignee, account owners/admins, and Decile admins.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: task completed
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Task"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: task not found, or feature not enabled
  "/api/v1/organization":
    post:
      operationId: upsert_organization
      x-agent-tool: true
      summary: Create/Update an Organization
      tags:
        - Directory
      description:
        "Create or Update an existing Organization. The request can only
        contain one Organization. A successful request will return a json object containing
        the success status, the organization_id, and a list of changes in the form
        of field_name: [old_value, new_value]."
      security:
        - api_key: []
      parameters: []
      responses:
        "201":
          description: organization created/updated
          content:
            application/json:
              examples:
                created:
                  value:
                    status: success
                    organization_id: 1
                    changes:
                      name:
                        -
                        - Jane Co.
                      data:
                        -
                        - company_url: https://www.example.com
                          short_description: An example org
        "400":
          description: bad request
        "401":
          description: Unauthorized
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                organization:
                  type: object
                  properties:
                    name:
                      type: string
                    website:
                      type: string
                    description:
                      type: string
                    logo:
                      type: string
                      description: "Logo, as a base64 data URI (data:image/png;base64,...) or a public http(s) image URL. JPG, PNG, WebP, or GIF; max 5 MB."
                    tag_list:
                      type: string
                      description: Comma-separated tags to add.
                    remove_tag_list:
                      type: string
                      description: Comma-separated tags to remove. Tags not present are ignored. A record is not created solely to remove tags.
                    custom_data_points:
                      type: object
                    note:
                      type: string
                    address:
                      "$ref": "#/components/schemas/address"
                    referred_by:
                      "$ref": "#/components/schemas/associated_person"
                    people:
                      type: array
                      items:
                        "$ref": "#/components/schemas/associated_person"
                  required:
                    - name
              required:
                - organization
  "/api/v1/organizations/{id}":
    get:
      operationId: get_organization
      x-agent-tool: true
      summary: Show Organization
      tags:
        - Directory
      description: >
        Get a single Organization by id. The default response shape is unchanged
        from prior versions (embeds notes, people, submit_your_company_response_data).
        The optional include= and fields= parameters are purely opt-in - include=
        replaces the default include set with a caller-specified subset, and
        fields= narrows top-level columns. The response also carries a `logo`
        object (see schemas/attached_image) with a download `url`, or null when
        no logo is attached.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Organization ID
          schema:
            type: integer
        - name: include
          in: query
          description: |
            Comma-separated list of nested resources to embed. Unknown values
            are ignored silently. Allowed values: notes, people, referred_by,
            submit_your_company_response_data.
          required: false
          schema:
            type: string
        - name: fields
          in: query
          description: |
            Comma-separated list of top-level fields to return. Unknown values
            are ignored silently. When `fields=` is supplied, the response is always narrowed; `id` and `name` are always present, and if no supplied keys match the whitelist the response collapses to just `{id, name}`.
          required: false
          schema:
            type: string
        - name: custom_data_points
          in: query
          description: |
            Optional. Controls embedding of a custom_data_points field on
            the organization.

            - Omit the param entirely to leave custom_data_points out of the
              response (default).
            - Pass `*` to embed every account-declared organization_data_points
              key (subject to the allow-list).
            - Pass a comma-separated list (e.g. `key1,key2`) to narrow the
              embed to that subset. Unknown keys are silently dropped.
            - Pass an empty value to embed an explicit empty
              custom_data_points object.
            - The wildcard always wins, so `*,foo` behaves identically to
              `*`.

            Select-type values are resolved to their human-readable option
            labels rather than the underlying stored value. Internal jsonb
            keys are never returned.
          required: false
          schema:
            type: string
      responses:
        "200":
          description: organization found
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      id: 1
                      address:
                        city: ""
                        state: ""
                        street: ""
                        country: ""
                        zipcode: ""
                      name: ShowOrg
                      company_url: ""
                      short_description: ""
                      data: {}
                      referred_by:
                      notes: []
                      people: []
                      submit_your_company_response_data:
        "404":
          description: not found
        "401":
          description: Unauthorized
  "/api/v1/organizations/{id}/notes":
    post:
      operationId: add_organization_note
      x-agent-tool: true
      summary: Add a note to an organization
      tags:
        - Directory
      description: |
        Append a plain-text note to an organization. The note is stored on the organization
        and surfaced wherever organization notes appear (including the prospect view if the
        organization is a pipeline prospect's prospectable).
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Organization ID
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                note:
                  type: object
                  properties:
                    body:
                      type: string
                    context:
                      type: string
                  required:
                    - body
              required:
                - note
      responses:
        "201":
          description: note created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/note_envelope"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Organization not found
        "422":
          description: Validation error
  "/api/v1/people":
    get:
      operationId: list_people
      x-agent-tool: true
      summary: List People
      tags:
        - Directory
      description:
        Gets a paginated list of People in your People Directory. The response
        will have a data array with the people and a pagination object with the total
        count, current page, and total pages.Pages are 0-indexed, so the first page
        is page 0.The People returned can be filtered and ordered. Order direction
        is ascending by default. The default response shape is unchanged from
        prior versions and embeds notes, organizations_with_titles, and referred_by.
        The optional include= parameter is purely opt-in - when supplied it
        replaces the default include set with a caller-specified subset; absent
        the parameter, the legacy default is returned verbatim. fields= narrows
        top-level columns when supplied.
      security:
        - api_key: []
      parameters:
        - name: include
          in: query
          description: |
            Comma-separated list of nested resources to embed. Unknown values
            are ignored silently. Allowed values: notes, referred_by, organizations.
          required: false
          schema:
            type: string
        - name: fields
          in: query
          description: |
            Comma-separated list of top-level fields to return. Unknown values
            are ignored silently. When `fields=` is supplied, the response is always narrowed; `id` and `name` are always present, and if no supplied keys match the whitelist the response collapses to just `{id, name}`.
          required: false
          schema:
            type: string
        - name: custom_data_points
          in: query
          description: |
            Optional. Controls embedding of a custom_data_points field on
            each person.

            - Omit the param entirely to leave custom_data_points out of the
              response (default).
            - Pass `*` to embed every account-declared person_data_points key
              (subject to the allow-list).
            - Pass a comma-separated list (e.g. `key1,key2`) to narrow the
              embed to that subset. Unknown keys are silently dropped.
            - Pass an empty value to embed an explicit empty
              custom_data_points object.
            - The wildcard always wins, so `*,foo` behaves identically to
              `*`.

            Select-type values are resolved to their human-readable option
            labels rather than the underlying stored value. Internal jsonb
            keys are never returned.
          required: false
          schema:
            type: string
        - name: first_name
          in: query
          description: First name includes this string (case-insensitive)
          schema:
            type: string
        - name: last_name
          in: query
          description: Last name includes this string (case-insensitive)
          schema:
            type: string
        - name: email
          in: query
          description: Email address includes this string (case-insensitive)
          schema:
            type: string
        - name: created_before
          in: query
          description: Only people created on or before this date
          schema:
            type: string
        - name: created_after
          in: query
          description: Only people created on or after this date
          schema:
            type: string
        - name: page
          in: query
          description: Page number (0-indexed)
          default: 0
          schema:
            type: integer
        - name: order_by
          in: query
          enum:
            - first_name
            - last_name
            - email
            - created_at
          description:
            "Field to order by:\n * `first_name` \n * `last_name` \n * `email`
            \n * `created_at` \n "
          default: created_at
          schema:
            type: string
        - name: order_direction
          in: query
          enum:
            - asc
            - desc
          description: Order direction
          default: asc
          schema:
            type: string
      responses:
        "200":
          description: no errors
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      - id: 13
                        address:
                          city: ""
                          state: ""
                          street: ""
                          country: ""
                          zipcode: ""
                        email: buck.brown.27@vclab.fund
                        phone: "+11112223344"
                        created_at: "2024-08-06T18:32:52.335Z"
                        first_name: Buck
                        middle_name: ""
                        last_name: Brown
                        tag_list:
                          - tag1
                          - Another Tag
                        data:
                          custom_field: custom value
                          another field: another value
                        referred_by:
                          id: 54
                          email: florene.harvey.18@kirlinincllc.com
                          phone: "+918727727698"
                          first_name: Florene
                          middle_name:
                          last_name: Harvey
                        notes:
                          - body: This is a note
                            created_at: "2024-08-22T14:13:06.672Z"
                            author:
                              first_name: Apple
                              last_name: Owner
                          - body: This is another note
                            created_at: "2024-08-22T14:13:11.320Z"
                            author:
                              first_name: Apple
                              last_name: Owner
                        organizations_with_titles:
                          - id: 11
                            name: Green, Hand and Konopelski and Sons
                            title: CEO
                    pagination:
                      total_count: 1
                      current_page: 0
                      total_pages: 1
    post:
      operationId: create_people
      x-agent-tool: true
      summary: Create People
      tags:
        - Directory
      description:
        Adds People to your People Directory. Only the first 100 People
        per request will be processed.The response will have a created, duplicates,
        and errors array. The created array will contain the emails of the people
        that were created. The duplicates array will contain the emails of the people
        that were not created because they already exist in the database. The errors
        array will contain the emails of the people that were not created because
        of an error.
      security:
        - api_key: []
      parameters: []
      responses:
        "200":
          description: no errors or creations
          content:
            application/json:
              examples:
                no_creates:
                  value:
                    created: []
                    duplicates:
                      - test@test.com
                    errors: []
        "201":
          description: people created
          content:
            application/json:
              examples:
                at_least_one_created:
                  value:
                    created:
                      - test@test.com
                    duplicates: []
                    errors: []
        "400":
          description: bad request
        "401":
          description: Unauthorized
        "422":
          description: unprocessable entity
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                no_creates_and_has_errors:
                  value:
                    error:
                      code: validation_failed
                      message: Some people could not be created
                      details:
                        - "test@test.com: Validation failed: Name can't be blank"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                people:
                  type: array
                  items:
                    type: object
                    properties:
                      first_name:
                        type: string
                      middle_name:
                        type: string
                      last_name:
                        type: string
                      email:
                        type: string
                      phone:
                        type: string
                      linkedin:
                        type: string
                      tag_list:
                        type: string
                      custom_data_points:
                        type: object
                      note:
                        type: string
                      picture:
                        type: string
                        description: "Profile photo, as a base64 data URI or a public http(s) image URL. JPG, PNG, WebP, or GIF; max 5 MB."
                      address:
                        "$ref": "#/components/schemas/address"
                      referred_by:
                        "$ref": "#/components/schemas/associated_person"
                      organizations:
                        type: array
                        items:
                          type: object
                          properties:
                            name:
                              type: string
                            title:
                              type: string
                    required:
                      - first_name
                      - last_name
                      - email
              required:
                - people
  "/api/v1/person":
    post:
      operationId: upsert_person
      x-agent-tool: true
      summary: Create/Update a person
      tags:
        - Directory
      description:
        "Create or Update an existing Person. The request can only contain
        one Person. A successful request will return a json object containing the
        success status, the person_id, and a list of changes in the form of field_name:
        [old_value, new_value]."
      security:
        - api_key: []
      parameters: []
      responses:
        "201":
          description: person updated
          content:
            application/json:
              examples:
                created:
                  value:
                    status: success
                    person_id: 1
                    changes:
                      first_name:
                        - Test
                        - Updated
                      last_name:
                        -
                        - Person
                      email:
                        -
                        - test@test.com
                      data:
                        - dog_name: Barkley
                        - dog_name: Charlie
        "400":
          description: bad request
        "401":
          description: Unauthorized
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                person:
                  type: object
                  properties:
                    first_name:
                      type: string
                    middle_name:
                      type: string
                    last_name:
                      type: string
                    email:
                      type: string
                    phone:
                      type: string
                    linkedin:
                      type: string
                    tag_list:
                      type: string
                    remove_tag_list:
                      type: string
                      description: Comma-separated tags to remove. Tags not present are ignored. A record is not created solely to remove tags.
                    custom_data_points:
                      type: object
                    note:
                      type: string
                    picture:
                      type: string
                      description: "Profile photo, as a base64 data URI (data:image/png;base64,...) or a public http(s) image URL. JPG, PNG, WebP, or GIF; max 5 MB."
                    address:
                      "$ref": "#/components/schemas/address"
                    referred_by:
                      "$ref": "#/components/schemas/associated_person"
                    organizations:
                      type: array
                      items:
                        type: object
                        properties:
                          name:
                            type: string
                          title:
                            type: string
                  required:
                    - first_name
                    - last_name
                    - email
              required:
                - person
  "/api/v1/people/{id}":
    get:
      operationId: get_person
      x-agent-tool: true
      summary: Show Person
      tags:
        - Directory
      description: >
        Get a single Person by id. The default response shape is unchanged
        from prior versions (embeds notes, organizations_with_titles, referred_by).
        The optional include= and fields= parameters are purely opt-in - include=
        replaces the default include set with a caller-specified subset, and
        fields= narrows top-level columns. The response also carries a `picture`
        object (see schemas/attached_image) with a download `url`, or null when
        no photo is attached.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Person ID
          schema:
            type: integer
        - name: include
          in: query
          description: |
            Comma-separated list of nested resources to embed. Unknown values
            are ignored silently. Allowed values: notes, referred_by, organizations.
          required: false
          schema:
            type: string
        - name: fields
          in: query
          description: |
            Comma-separated list of top-level fields to return. Unknown values
            are ignored silently. When `fields=` is supplied, the response is always narrowed; `id` and `name` are always present, and if no supplied keys match the whitelist the response collapses to just `{id, name}`.
          required: false
          schema:
            type: string
        - name: custom_data_points
          in: query
          description: |
            Optional. Controls embedding of a custom_data_points field on
            the person.

            - Omit the param entirely to leave custom_data_points out of the
              response (default).
            - Pass `*` to embed every account-declared person_data_points key
              (subject to the allow-list).
            - Pass a comma-separated list (e.g. `key1,key2`) to narrow the
              embed to that subset. Unknown keys are silently dropped.
            - Pass an empty value to embed an explicit empty
              custom_data_points object.
            - The wildcard always wins, so `*,foo` behaves identically to
              `*`.

            Select-type values are resolved to their human-readable option
            labels rather than the underlying stored value. Internal jsonb
            keys are never returned.
          required: false
          schema:
            type: string
      responses:
        "200":
          description: person found
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      id: 1
                      address:
                        city: ""
                        state: ""
                        street: ""
                        country: ""
                        zipcode: ""
                      email: showperson@example.com
                      phone: ""
                      created_at: "2024-08-06T18:32:52.335Z"
                      first_name: Show
                      middle_name: ""
                      last_name: Person
                      tag_list: []
                      data: {}
                      referred_by:
                      notes: []
                      organizations: []
        "404":
          description: not found
        "401":
          description: Unauthorized
  "/api/v1/people/{id}/notes":
    post:
      operationId: add_person_note
      x-agent-tool: true
      summary: Add a note to a person
      tags:
        - Directory
      description: |
        Append a plain-text note to a person. The note is stored on the person and surfaced
        wherever person notes appear (including the prospect view if the person is a pipeline
        prospect's prospectable).
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Person ID
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                note:
                  type: object
                  properties:
                    body:
                      type: string
                    context:
                      type: string
                  required:
                    - body
              required:
                - note
      responses:
        "201":
          description: note created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/note_envelope"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Person not found
        "422":
          description: Validation error
  "/api/v1/research/investors":
    get:
      operationId: list_research_investors
      summary: List Decile Research Investors
      tags:
        - ResearchInvestors
      description: |
        Lists the shared "Decile Research" LP roster. The same roster is
        visible to every authenticated account. The response has a data array
        of roster entries and a pagination object with the total count, current
        page (0-indexed), and total pages. Each entry carries the research
        prospect `id` (use it with `POST /api/v1/research/investors/copy_to_pipeline`),
        the LP person details, organization name(s), stage name, and probability.
      security:
        - api_key: []
      parameters:
        - name: search
          in: query
          description: Case-insensitive filter over LP name, city, country, and organization name.
          required: false
          schema:
            type: string
        - name: stage
          in: query
          description: Filter by exact stage name.
          required: false
          schema:
            type: string
        - name: column
          in: query
          description: Sort column.
          required: false
          schema:
            type: string
            enum:
              - people.first_name
              - people.last_name
              - created_at
              - updated_at
        - name: direction
          in: query
          description: Sort direction (ascending by default).
          required: false
          schema:
            type: string
            enum:
              - asc
              - desc
        - name: page
          in: query
          description: Page number (0-indexed).
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: Roster returned
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: Research prospect id; pass to copy_to_pipeline.
                        first_name:
                          type: string
                          nullable: true
                        middle_name:
                          type: string
                          nullable: true
                        last_name:
                          type: string
                          nullable: true
                        email:
                          type: string
                          nullable: true
                        linkedin:
                          type: string
                          nullable: true
                        city:
                          type: string
                          nullable: true
                        country:
                          type: string
                          nullable: true
                        organization_names:
                          type: array
                          items:
                            type: string
                        stage_name:
                          type: string
                          nullable: true
                        probability:
                          type: integer
                          nullable: true
                  pagination:
                    type: object
                    properties:
                      total_count:
                        type: integer
                      current_page:
                        type: integer
                      total_pages:
                        type: integer
        "400":
          description: invalid parameter (e.g. bad sort column or direction)
        "401":
          description: Unauthorized
  "/api/v1/research/investors/copy_to_pipeline":
    post:
      operationId: copy_research_investors_to_pipeline
      summary: Copy Decile Research Investors To Pipeline
      tags:
        - ResearchInvestors
      description: |
        Copies selected entries from the shared "Decile Research" LP roster
        into one of the caller's own pipeline stages. Supply the research
        prospect ids (from `GET /api/v1/research/investors`) and the target
        `stage_id` (a stage in your account's investor pipeline). Each copy is
        processed asynchronously; the response reports how many copies were
        enqueued and any ids that did not match the roster.
      security:
        - api_key: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - research_prospect_ids
                - stage_id
              properties:
                research_prospect_ids:
                  type: array
                  items:
                    type: string
                  description: Research prospect ids from the roster to copy.
                stage_id:
                  type: string
                  description: Target pipeline stage id in your account.
      responses:
        "202":
          description: Copies enqueued
          content:
            application/json:
              schema:
                type: object
                properties:
                  enqueued:
                    type: integer
                  stage_id:
                    type: string
                  invalid_ids:
                    type: array
                    items:
                      type: string
        "401":
          description: Unauthorized
        "403":
          description: Forbidden — caller may not add prospects to the target pipeline.
        "404":
          description: Stage not found
        "422":
          description: No valid research prospect ids supplied
  "/api/v1/pipeline_prospects":
    get:
      operationId: list_pipeline_prospects
      x-agent-tool: true
      x-agent-category: context
      summary: List Pipeline Prospects
      tags:
        - PipelineProspects
      description: |
        Gets a paginated list of Prospects in a Pipeline. The response has a
        data array with the Prospects and a pagination object with the total
        count, current page, and total pages. Pages are 0-indexed, so the
        first page is page 0. The Prospects returned can be filtered and
        ordered. Order direction is ascending by default. Pass
        custom_data_points=* (or a narrowed subset) to embed each prospect's
        pipeline-declared custom data point values. For Select (Dropdown)
        data points, the human-readable option label is returned (not the
        underlying stored value).
      security:
        - api_key: []
      parameters:
        - name: pipeline_id
          in: query
          description: Pipeline ID
          required: true
          schema:
            type: string
        - name: include
          in: query
          description: |
            Comma-separated list of nested resources to embed. Allowed:
            `notes`, `referred_by`, `assigned`, `stage`, `capital_account`.
            Unknown values are ignored. Default (no `include=` supplied) embeds
            `assigned`, `stage`, `referred_by`, `notes`. Supplying `include=`
            REPLACES the default set.

            `capital_account` embeds fund/KYC/onboarding details on
            closing-pipeline prospects (`prospectable_type` ==
            `FirmAdmin::CapitalAccount`); the key is omitted for other
            prospect types.
          required: false
          schema:
            type: string
        - name: fields
          in: query
          description: |
            Comma-separated top-level fields to return. Allowed: `id`,
            `last_contact`, `next_contact`, `rating`, `probability`,
            `prospectable_type`, `prospectable_id`, `capital_commitment`,
            `created_at`, `updated_at`. Unknown fields are ignored. `id` and
            `name` are always present.
          required: false
          schema:
            type: string
        - name: custom_data_points
          in: query
          description: |
            Optional. Controls embedding of a custom_data_points field on
            each prospect.

            - Omit the param entirely to leave custom_data_points out of the
              response (default).
            - Pass `*` to embed every pipeline-declared custom data point
              key (subject to the pipeline allow-list).
            - Pass a comma-separated list (e.g. `key1,key2`) to narrow the
              embed to that subset. Unknown keys are silently dropped.
            - Pass an empty value to embed an explicit empty
              custom_data_points object.
            - The wildcard always wins, so `*,foo` behaves identically to
              `*`.

            Select-type values are resolved to their human-readable option
            labels rather than the underlying stored value. Internal jsonb
            keys (e.g. actionable_url) are never returned.
          required: false
          schema:
            type: string
        - name: name
          in: query
          description: Name includes this string (case-insensitive)
          schema:
            type: string
        - name: created_before
          in: query
          description: Only prospects created on or before this date
          schema:
            type: string
        - name: created_after
          in: query
          description: Only prospects created on or after this date
          schema:
            type: string
        - name: updated_before
          in: query
          description: Only prospects updated on or before this date
          schema:
            type: string
        - name: updated_after
          in: query
          description: Only prospects updated on or after this date
          schema:
            type: string
        - name: last_contact_before
          in: query
          description: Only prospects last contacted on or before this date
          schema:
            type: string
        - name: last_contact_after
          in: query
          description: Only prospects last contacted on or after this date
          schema:
            type: string
        - name: next_contact_before
          in: query
          description: Only prospects with a next contact on or before this date
          schema:
            type: string
        - name: next_contact_after
          in: query
          description: Only prospects with a next contact on or after this date
          schema:
            type: string
        - name: stage_name
          in: query
          description:
            The name of the Pipeline Stage that the Prospect is in includes
            this string (case-insensitive)
          schema:
            type: string
        - name: stage_id
          in: query
          description: The ID of the Pipeline Stage that the Prospect is in
          schema:
            type: string
        - name: stage_before
          in: query
          description:
            The Prospect is in this stage ID or any stage before it in the
            pipeline
          schema:
            type: string
        - name: stage_after
          in: query
          description:
            The Prospect is in this stage ID or any stage after it in the
            pipeline
          schema:
            type: string
        - name: tags
          in: query
          description: The prospect has these tags (comma-separated)
          schema:
            type: string
        - name: page
          in: query
          description: Page number (0-indexed)
          default: 0
          schema:
            type: integer
        - name: order_by
          in: query
          enum:
            - name
            - last_contact
            - next_contact
            - created_at
            - updated_at
            - stage
          description:
            "Field to order by:\n * `name` \n * `last_contact` \n * `next_contact`
            \n * `created_at` \n * `updated_at` \n * `stage` \n "
          default: stage
          schema:
            type: string
        - name: order_direction
          in: query
          enum:
            - asc
            - desc
          description: Order direction
          default: asc
          schema:
            type: string
      responses:
        "200":
          description: no errors
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      - id: 15
                        created_at: "2024-08-06T18:33:18.863Z"
                        updated_at: "2024-08-06T18:33:18.863Z"
                        rating:
                        probability: 25
                        prospectable_type: Person
                        last_contact: "2024-08-20T13:01:00.000Z"
                        next_contact: "2024-09-02T12:00:00.000Z"
                        data:
                          custom_field: custom value
                          another field: another value
                        tag_list:
                          - tag1
                          - tags2
                        stage:
                          id: 246
                          name: Added
                        referred_by:
                          id: 3
                          email: user@apple.com
                          phone:
                          first_name: Alice
                          middle_name: ""
                          last_name: Albright
                        notes:
                          - body: This is a note
                            created_at: "2024-08-28T18:26:41.063Z"
                            author:
                              first_name: Apple
                              last_name: Owner
                        assigned_name: Young Buck
                    pagination:
                      total_count: 1
                      current_page: 0
                      total_pages: 1
        "400":
          description: bad request
        "404":
          description: not found
    post:
      operationId: create_pipeline_prospects
      x-agent-tool: true
      summary: Create Pipeline Prospects
      tags:
        - PipelineProspects
      description:
        Creates Person/Organization pipeline prospects for a given pipeline.
        Only the first 100 organizations and first 100 people will be processed.A
        valid request will return a json object with two keys, organizations and people.
        Each key will have a created, duplicates, and errors array. The created array
        will contain the names of the prospects that were created. The duplicates
        array will contain the names of the prospects that were not created because
        they already exist in the database. The errors array will contain the names
        of the prospects that were not created because of an error.
      security:
        - api_key: []
      parameters: []
      responses:
        "200":
          description: no errors or creations
          content:
            application/json:
              examples:
                no_creates:
                  value:
                    organizations:
                      created: []
                      duplicates:
                        - Existing Org
                      errors: []
                    people:
                      created: []
                      duplicates:
                        - Test Person
                      errors: []
        "201":
          description: pipeline prospect created
          content:
            application/json:
              examples:
                at_least_one_created:
                  value:
                    organizations:
                      created:
                        - Test API Org
                      duplicates: []
                      errors: []
                    people:
                      created: []
                      duplicates:
                        - Test Person
                      errors: []
        "400":
          description: bad request
        "401":
          description: Unauthorized
        "404":
          description: not found
        "422":
          description: unprocessable entity
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                no_creates_and_has_errors:
                  value:
                    error:
                      code: validation_failed
                      message: Some prospects could not be created
                      details:
                        organizations:
                          created: []
                          duplicates: []
                          errors:
                            - ": Validation failed: Name can't be blank"
                        people:
                          created: []
                          duplicates: []
                          errors: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                pipeline_id:
                  type: string
                stage_id:
                  type: string
                prospects:
                  type: object
                  properties:
                    organizations:
                      type: array
                      items:
                        type: object
                        properties:
                          name:
                            type: string
                          probability:
                            type: number
                          rating:
                            type: number
                          tag_list:
                            type: string
                          note:
                            type: string
                          company_url:
                            type: string
                          short_description:
                            type: string
                          custom_data_points:
                            type: object
                          address:
                            "$ref": "#/components/schemas/address"
                          referred_by:
                            "$ref": "#/components/schemas/associated_person"
                          primary_contact:
                            "$ref": "#/components/schemas/associated_person"
                        required:
                          - name
                    people:
                      type: array
                      items:
                        type: object
                        properties:
                          first_name:
                            type: string
                          middle_name:
                            type: string
                          last_name:
                            type: string
                          email:
                            type: string
                          phone:
                            type: string
                          linkedin:
                            type: string
                          probability:
                            type: number
                          rating:
                            type: number
                          tag_list:
                            type: string
                          note:
                            type: string
                          address:
                            "$ref": "#/components/schemas/address"
                          referred_by:
                            "$ref": "#/components/schemas/associated_person"
                          organizations:
                            type: array
                            items:
                              type: object
                              properties:
                                name:
                                  type: string
                                title:
                                  type: string
                        required:
                          - first_name
                          - last_name
                          - email
              required:
                - pipeline_id
                - prospects
    patch:
      operationId: update_pipeline_prospects
      x-agent-tool: true
      summary: Update pipeline prospects
      tags:
        - PipelineProspects
      description: |
        Updates one or more prospects in a pipeline. Send `pipeline_id` (query or body) and a JSON array `prospects`.
        Each element can identify the prospect by `id` (preferred), exact `name`, or for Person prospectables by `email`.

        **Stage moves:** pass `stage_id` at the top level and/or per prospect. Omit `stage_id` to leave the stage unchanged.

        **Metadata:** `probability`, `rating`, `next_contact`, and `last_contact` can be updated. Send JSON `null` for
        `next_contact` or `last_contact` to clear them

        **Notes:** pass `note` to append a note (same as the single-prospect upsert).

        **Tags:** pass `tag_list` (comma-separated) to add tags and/or `remove_tag_list` (comma-separated) to remove tags. Tags not present on a prospect are ignored on removal.

        **Single prospect:** `PATCH /api/v1/pipeline_prospects/{id}` accepts the same fields without wrapping in `prospects`.
      security:
        - api_key: []
      parameters:
        - name: pipeline_id
          in: query
          required: true
          description: Pipeline ID (can also be sent in the JSON body)
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - pipeline_id
                - prospects
              properties:
                pipeline_id:
                  type: string
                stage_id:
                  type: string
                  description: Applied to each prospect item that does not set its own `stage_id`
                prospects:
                  type: array
                  items:
                    type: object
                    properties:
                      id:
                        type: string
                        description: Pipeline prospect id
                      name:
                        type: string
                        description: Exact display name match (ambiguous if multiple)
                      email:
                        type: string
                        description: Person prospect email (must match exactly one prospect in the pipeline)
                      stage_id:
                        type: string
                      probability:
                        type: integer
                      rating:
                        type: integer
                      next_contact:
                        type: string
                        format: date-time
                        nullable: true
                      last_contact:
                        type: string
                        format: date-time
                        nullable: true
                      note:
                        type: string
                      tag_list:
                        type: string
                        description: Comma-separated tags to add to the prospect.
                      remove_tag_list:
                        type: string
                        description: Comma-separated tags to remove from the prospect. Tags not present are ignored.
                      custom_data_points:
                        type: object
                      assigned_id:
                        type: integer
                        nullable: true
                        description: AccountUser id to assign as the prospect owner. Pass null to unassign. Must belong to the prospect's account.
      responses:
        "200":
          description: |
            At least one prospect updated. The `errors` array may be non-empty when some rows failed to
            resolve or update while others succeeded. Each error entry contains an `error` message and,
            when available, the originating `params` or the resolved prospect's `id`/`name`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  updated_prospects:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        name:
                          type: string
                  errors:
                    type: array
                    description: Per-row errors. Possible messages include `No prospect found with id <id>`, `No prospect found with email <email>`, `Multiple prospects match email <email>`, `Invalid id <id>`, and `No unambiguous matching prospect found`.
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        name:
                          type: string
                        error:
                          type: string
                        params:
                          type: object
        "400":
          description: bad request
        "404":
          description: |
            Returned when the pipeline itself is not found, when `prospects` is empty, or when every row
            failed to resolve to a matching prospect (so there were no successful updates).
        "401":
          description: Unauthorized
  "/api/v1/pipeline_prospects/{id}":
    patch:
      operationId: update_pipeline_prospect
      x-agent-tool: true
      summary: Update one pipeline prospect
      tags:
        - PipelineProspects
      description: |
        Same fields as the batch `PATCH /api/v1/pipeline_prospects` body (except `prospects`). `pipeline_id` is required
        (query or body). The prospect id is taken from the path. Unlike the batch endpoint, this endpoint returns a
        singular response shape (no `prospects`/`errors` wrapper).
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: pipeline_id
          in: query
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                pipeline_id:
                  type: string
                stage_id:
                  type: string
                probability:
                  type: integer
                rating:
                  type: integer
                next_contact:
                  type: string
                  format: date-time
                  nullable: true
                last_contact:
                  type: string
                  format: date-time
                  nullable: true
                note:
                  type: string
                tag_list:
                  type: string
                  description: Comma-separated tags to add to the prospect.
                remove_tag_list:
                  type: string
                  description: Comma-separated tags to remove from the prospect. Tags not present are ignored.
                custom_data_points:
                  type: object
                assigned_id:
                  type: integer
                  nullable: true
                  description: AccountUser id to assign as the prospect owner. Pass null to unassign. Must belong to the prospect's account.
      responses:
        "200":
          description: Prospect updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  id:
                    type: integer
                  name:
                    type: string
        "400":
          description: |
            Bad request. Returned when the id in the path is malformed (e.g. not an integer and not a valid
            encoded id) or when required parameters are missing.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "404":
          description: |
            Pipeline not found, or no prospect with the given id exists in this pipeline.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "422":
          description: |
            The prospect was resolved but the update failed (for example invalid datetime value).
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
        "401":
          description: Unauthorized
  "/api/v1/pipeline_prospects/{pipeline_prospect_id}/notes":
    post:
      operationId: add_pipeline_prospect_note
      x-agent-tool: true
      summary: Add a note to a pipeline prospect
      tags:
        - PipelineProspects
      description: |
        Append a plain-text note in the context of a pipeline prospect. To match the in-app
        behavior, the note is stored against the prospect's prospectable (Person /
        Organization) when one exists, falling back to the prospect itself otherwise. When
        `note.context` is omitted, the prospect's pipeline name is used as the context.
      security:
        - api_key: []
      parameters:
        - name: pipeline_prospect_id
          in: path
          required: true
          description: Pipeline prospect ID (numeric or obscure-encoded)
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                note:
                  type: object
                  properties:
                    body:
                      type: string
                    context:
                      type: string
                      description: Optional. Defaults to the prospect's pipeline name.
                  required:
                    - body
              required:
                - note
      responses:
        "201":
          description: note created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/note_envelope"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Pipeline prospect not found
        "422":
          description: Validation error
  "/api/v1/pipeline_prospect":
    post:
      operationId: upsert_pipeline_prospect
      x-agent-tool: true
      summary: Create/Update a Pipeline Prospect
      tags:
        - PipelineProspects
      description: |
        Create or update an existing pipeline prospect in a given pipeline. The request must contain exactly one of
        `person` or `organization`. On **create**, `stage_id` selects the stage. On **update**, the prospect's stage is
        **not** changed unless you pass `apply_stage_id_to_existing: true` (then `stage_id` is applied to an existing
        prospect in this pipeline only).

        To move stages or update `next_contact`, `probability`, `rating`, or append notes without using this upsert,
        prefer `PATCH /api/v1/pipeline_prospects` (batch) or `PATCH /api/v1/pipeline_prospects/{id}` (single).

        A successful response includes `success`, `pipeline_prospect_id`, and `changes` (field name to [old, new]).
      security:
        - api_key: []
      parameters: []
      responses:
        "201":
          description: pipeline prospect created/updated
          content:
            application/json:
              examples:
                created:
                  value:
                    status: success
                    pipeline_prospect_id: 1
                    changes:
                      first_name:
                        -
                        - Jane
                      last_name:
                        -
                        - Jones
                      email:
                        -
                        - jjones@decilehub.com
        "400":
          description: bad request
        "401":
          description: Unauthorized
        "404":
          description: not found
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                pipeline_id:
                  type: string
                stage_id:
                  type: string
                apply_stage_id_to_existing:
                  type: boolean
                  description: If true and the person/organization already has a prospect in this pipeline, apply `stage_id` (or first stage) to move them.
                prospect:
                  type: object
                  properties:
                    probability:
                      type: number
                    rating:
                      type: number
                    tag_list:
                      type: string
                    custom_data_points:
                      type: object
                    organization:
                      type: object
                      properties:
                        name:
                          type: string
                        note:
                          type: string
                        company_url:
                          type: string
                        short_description:
                          type: string
                        custom_data_points:
                          type: object
                        address:
                          "$ref": "#/components/schemas/address"
                        referred_by:
                          "$ref": "#/components/schemas/associated_person"
                        primary_contact:
                          "$ref": "#/components/schemas/associated_person"
                      required:
                        - name
                    person:
                      type: object
                      properties:
                        first_name:
                          type: string
                        middle_name:
                          type: string
                        last_name:
                          type: string
                        email:
                          type: string
                        phone:
                          type: string
                        linkedin:
                          type: string
                        tag_list:
                          type: string
                        note:
                          type: string
                        custom_data_points:
                          type: object
                        address:
                          "$ref": "#/components/schemas/address"
                        referred_by:
                          "$ref": "#/components/schemas/associated_person"
                        organizations:
                          type: array
                          items:
                            type: object
                            properties:
                              name:
                                type: string
                              title:
                                type: string
                      required:
                        - first_name
                        - last_name
                        - email
              required:
                - pipeline_id
                - prospect
  "/api/v1/pipeline_prospects/{pipeline_prospect_id}/action_executions":
    get:
      operationId: list_pipeline_action_executions
      x-agent-tool: true
      summary: List a prospect's pipeline action executions
      tags:
        - PipelineProspects
      description: |
        Lists the `PipelineActionExecution` rows for a single prospect — i.e. the
        actions queued to run (or that have run / been skipped) on this prospect
        at its current stage. Each row includes a human-readable description and
        a per-type `target` block with foreign-key names resolved server-side, so
        a caller (especially an agent / MCP client) can show the user *what the
        action would do* before triggering it.

        **Defaults to `state=pending`.** Pass `state=completed`, `state=skipped`,
        or `state=all` to broaden. The state filter applies to ROOT executions —
        children of returned roots are always included regardless of state, so
        callers can see what's already been done within an in-progress task.

        **`final_action_for_stage`** rows (the workflow gate that holds a
        prospect at its current stage — not user-pickable) are excluded by
        default. Pass `include_final=true` to include them.

        ### Response shape

        Tree-shaped — `data` contains only ROOT executions (each represents
        one task in the UI), and each root carries a `children` array with its
        subtasks. Subtasks within a root are intended to execute in order.
        Roots are ordered by `position`, then `scheduled_at` (NULLs last);
        children are ordered by `position` within their parent.

        - `prospect`: `{ id, name, stage_id, stage_name }` envelope so the caller
          knows the prospect's current stage in the same round-trip.
        - `data`: array of root executions. Each row:
          - `id` (string) — execution id (integer rendered as string).
          - `state` — one of `pending`, `completed`, `skipped`.
          - `action_type` — one of the 15 `PipelineAction` types
            (`send_email`, `move_to_stage`, `assign_user`, `apply_tag`,
            `set_variable`, `record_task_completed`, `chat`, `execute_job`,
            `copy_to_pipeline`, `final_action_for_stage`,
            `move_to_stage_from_date_cell`, `base_post_reply`,
            `start_questionnaire`, `create_user_notification`).
          - `description` — human-readable summary, e.g. `"Send email Welcome"`,
            `"Change stage to Qualified"`, `"Apply tag Lead"`. Plaintext (no
            `<em>` tags).
          - `scheduled_at` — ISO8601 or `null`.
          - `automate` — boolean; whether the periodic worker will fire this
            without manual completion.
          - `action_id` — id of the source `PipelineAction` template (may be
            `null` if the template was deleted; `type` is denormalised on the
            execution and survives template deletion).
          - `parent_id` — `null` on root rows, set to the parent root's id on
            children (children appear inside their root's `children` array, but
            still carry `parent_id` so flat traversals see the link).
          - `target` — type-specific resolved payload (see below). May be
            `null` for types with no resolvable target.
          - `next_pending_id` (root rows only) — id of the next leaf the
            caller should pass to the execute endpoint (first pending child
            in position order, else the root if it's still pending, else
            `null`). Mirrors the UI's "next" leaf. Prefer this over the
            root's own id when the root has pending children: executing a
            root with pending children CASCADES — the worker runs every
            child in order then the root, in one call, with no per-subtask
            note prompt. Walking via `next_pending_id` mirrors the UI's
            per-subtask flow.
          - `children` (root rows only) — array of child rows with the same
            shape (minus their own `children` and `next_pending_id`).

        ### Per-type `target` shape

        | `action_type` | `target` keys |
        | --- | --- |
        | `send_email` | `email_template_id`, `email_template_name` |
        | `move_to_stage` | `stage_id`, `stage_name` |
        | `final_action_for_stage` | `stage_id`, `stage_name` (omitted/blank stage_id means "hold at this stage") |
        | `move_to_stage_from_date_cell` | `stage_id`, `stage_name` |
        | `assign_user` | `assignee_id`, `assignee_name` |
        | `apply_tag` | `tag` |
        | `set_variable` | `variable_name`, `value`, `use_today` |
        | `execute_job` | `job_class` (raw class name; review before triggering) |
        | `copy_to_pipeline` | `pipeline_id`, `pipeline_name` |
        | `chat` | `chat_agenda_id`, `chat_agenda_name` |
        | `record_task_completed` | `task_description` |
        | `base_post_reply` | `reply_text` (truncated to 200 chars) |

        Other `action_type` values currently return `null` for `target`.
      security:
        - api_key: []
      parameters:
        - name: pipeline_prospect_id
          in: path
          description: Pipeline prospect id.
          required: true
          schema:
            type: string
        - name: state
          in: query
          description: |
            Filter ROOT executions by state. Defaults to `pending`. Pass `all`
            to return roots in every state. Children of returned roots are
            always included regardless of state.
          required: false
          schema:
            type: string
            enum:
              - pending
              - completed
              - skipped
              - all
            default: pending
        - name: include_final
          in: query
          description: |
            When true, include `final_action_for_stage` rows. Default false.
          required: false
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: ok
          content:
            application/json:
              examples:
                pending_actions:
                  summary: Default pending root executions for a prospect, including a task with subtasks
                  value:
                    prospect:
                      id: 183
                      name: "Acme Fund I — LP 17"
                      stage_id: 394
                      stage_name: Qualified
                    data:
                      - id: "421"
                        state: pending
                        action_type: send_email
                        description: "Send email Welcome"
                        scheduled_at: "2026-05-08T09:00:00Z"
                        automate: true
                        action_id: "1812"
                        parent_id:
                        target:
                          email_template_id: "55"
                          email_template_name: Welcome
                        next_pending_id: "421"
                        children: []
                      - id: "422"
                        state: pending
                        action_type: record_task_completed
                        description: "Submit BD review"
                        scheduled_at:
                        automate: false
                        action_id: "1813"
                        parent_id:
                        target:
                          task_description: "Submit BD review"
                        next_pending_id: "424"
                        children:
                          - id: "423"
                            state: completed
                            action_type: record_task_completed
                            description: "Collect BD docs"
                            scheduled_at:
                            automate: false
                            action_id: "1814"
                            parent_id: "422"
                            target:
                              task_description: "Collect BD docs"
                          - id: "424"
                            state: pending
                            action_type: record_task_completed
                            description: "Send to compliance"
                            scheduled_at:
                            automate: false
                            action_id: "1815"
                            parent_id: "422"
                            target:
                              task_description: "Send to compliance"
        "400":
          description: invalid `state` value
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "401":
          description: unauthorized (missing/invalid API token)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "403":
          description: forbidden
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "404":
          description: prospect not found in the token's account
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/pipeline_prospects/{pipeline_prospect_id}/action_executions/{id}/execute":
    post:
      operationId: execute_pipeline_action_execution
      x-agent-tool: true
      summary: Execute a single pipeline action execution
      tags:
        - PipelineProspects
      description: |
        Executes one `PipelineActionExecution` for the prospect — sends the
        templated email, moves the stage, applies the tag, runs the configured
        background job, etc., according to the execution's `action_type` and
        snapshotted `data`, then advances any chained automated actions.

        **The action runs as authored.** This endpoint only triggers an
        existing pending execution. An optional `note` body field is honored
        for `record_task_completed` actions (attached when marking the task
        complete) — see the request body below. Other
        body fields are silently dropped.

        **One execution per call.** This endpoint deliberately does not loop or
        bulk-execute — agent / MCP callers are expected to list pending
        executions, surface each action's resolved description to the user,
        and call this endpoint per confirmed action.

        **Idempotency:** if the execution is already `completed` or `skipped`,
        returns 422 with `code: already_finalized`. Validation gates or
        missing prerequisites surface as 422 with
        `code: action_execution_error`.

        Response shape mirrors `GET /action_executions`: a `prospect`
        envelope (so callers see the resulting `stage_id` / `stage_name` if
        the action transitioned the prospect) plus a single `execution`
        object with the post-execute `state`, resolved `description`, and
        per-type `target` block (see the GET endpoint for the per-type
        target table).
      security:
        - api_key: []
      parameters:
        - name: pipeline_prospect_id
          in: path
          required: true
          description: Pipeline prospect id.
          schema:
            type: string
        - name: id
          in: path
          required: true
          description: PipelineActionExecution id.
          schema:
            type: string
      requestBody:
        required: false
        description: |
          Optional `note` and `data` mirror the UI's task-complete modal.

          `note` is only meaningful for `record_task_completed` actions; it's
          saved as a note on the prospect's associated Person/Organization
          (or on the prospect itself when it has no such associated record),
          with the pipeline name as `context` and the API token's user as
          `author`. Ignored for any other action type.

          `data` is allowlisted per action type — anything outside the
          allowlist is silently dropped. Callers cannot mutate
          `state` / `scheduled_at` from this endpoint, and `data` keys for
          action types not listed below are ignored.

          | action_type    | allowed `data` keys                   | notes |
          | -------------- | ------------------------------------- | ----- |
          | `apply_tag`    | `tag`                                 | description rewritten to `"Apply tag <tag>"` |
          | `assign_user`  | `assign_to_id`                        | `account_va` / `account_fa` stored as `{ assign_to_role: ... }` |
          | `set_variable` | `variable_name`, `value`, `use_today` | when `use_today` is truthy, stored `value` is `Date.current.to_s`; description rewritten |
          | `send_email`   | `rich_subject`, `body`, `cc`, `bcc`, `attachments_attributes`, `new_attachment_signed_ids`, `email_from_actor`, `scheduled_at`, `email_template_id`, `custom_substituted_variables`, `test_email`, `test_email_to_address`, `notify_users` | All keys are submitted **flat** at `data.<key>` — the controller nests the content-shaped keys (`rich_subject`, `body`, `cc`, `bcc`, `attachments_attributes`, `new_attachment_signed_ids`, `email_from_actor`, `scheduled_at`) under `pipeline_email_content` server-side before persisting. **Callers MUST NOT pre-nest** under `pipeline_email_content` — doing so silently drops the overrides. `cc` / `bcc` accept the same heterogeneous form as `POST /api/v1/emails` (raw email string, `{person_id: "..."}`, or `{internal_user_id: <id>}`); they're normalized to a comma-joined string before persisting. `test_email_to_address` is singular here (mirrors the UI); `POST /api/v1/emails` uses the plural `test_email_to_addresses` array. |
        content:
          application/json:
            schema:
              type: object
              properties:
                note:
                  type: string
                  description: |
                    Free-text note to record on the prospect alongside the
                    task completion. Only honored for `record_task_completed`
                    action types.
                data:
                  type: object
                  description: |
                    Per-type confirm-time overrides. Allowlisted — see the
                    table above. Keys outside the per-type allowlist are
                    silently dropped.
                  additionalProperties: true
              examples:
                record_task_completed_with_note:
                  value:
                    note: "Called them, left a voicemail."
                apply_tag_with_data:
                  value:
                    data:
                      tag: "High Priority"
                set_variable_with_today:
                  value:
                    data:
                      variable_name: "Person.last_contacted_at"
                      use_today: "1"
                send_email_overrides:
                  summary: Override subject/body and add a cc recipient at execute time
                  value:
                    data:
                      rich_subject: "Welcome to Acme Fund I"
                      body: "<p>Hi {{Person.first_name}}, thanks for joining.</p>"
                      cc:
                        - "ops@example.com"
                        - person_id: "1042"
                      custom_substituted_variables:
                        "custom.greeting": "Hello"
      responses:
        "200":
          description: ok
          content:
            application/json:
              examples:
                executed:
                  summary: Executed a send_email action; prospect stage unchanged
                  value:
                    prospect:
                      id: 183
                      name: "Acme Fund I — LP 17"
                      stage_id: 394
                      stage_name: Qualified
                    execution:
                      id: "421"
                      state: completed
                      action_type: send_email
                      description: "Send email Welcome"
                      scheduled_at: "2026-05-08T09:00:00Z"
                      automate: true
                      action_id: "1812"
                      target:
                        email_template_id: "55"
                        email_template_name: Welcome
        "401":
          description: unauthorized (missing/invalid API token)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "403":
          description: forbidden
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "404":
          description: prospect or execution not found in the token's account
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: |
            Execution already completed/skipped (`code: already_finalized`) or
            worker rejected the action (`code: action_execution_error`).
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
              examples:
                already_finalized:
                  value:
                    error:
                      code: already_finalized
                      message: "Execution is already completed and cannot be re-executed."
                action_execution_error:
                  value:
                    error:
                      code: action_execution_error
                      message: "KYC must be passed before this action can run."
                invalid_data:
                  value:
                    error:
                      code: invalid_data
                      message: "Tag can't be blank"
  "/api/v1/pipeline_prospects/{pipeline_prospect_id}/action_executions/{id}/preview":
    post:
      operationId: preview_pipeline_action_execution
      x-agent-tool: true
      summary: Preview a pending send_email action execution
      tags:
        - PipelineProspects
      description: |
        Renders the would-be sent email for a pending `send_email`
        `PipelineActionExecution` — substitutes variables, resolves cc/bcc,
        and reports unresolved variables / blocker warnings — without
        sending or mutating the execution. The recipient is fixed to the
        prospect that owns the execution; you can override the email body,
        subject, cc/bcc, attachments, scheduled_at, the from-actor, the
        template, and `custom_substituted_variables` at preview time.

        **Defaults come from the execution's persisted
        `data[:pipeline_email_content]`** (the authored content snapshot).
        Any override key you submit replaces the corresponding persisted
        value for this preview only — nothing is written back to the
        execution. Override keys are flat at the top level of the request
        body (mirrors `POST /api/v1/emails/preview`), not nested under a
        wrapper.

        **`cc` / `bcc` semantics:** absent → fall back to the execution's
        persisted cc/bcc. Explicit `[]` clears (distinct from absent).
        Accepted entry shapes mirror `POST /api/v1/emails/preview`: raw
        email string, `{person_id: "..."}`, or
        `{internal_user_id: <numeric_id>}`. Malformed entries return 422
        `invalid_recipient_format`.

        **Non-send_email actions return 422 `unsupported_action_type`.**
        Use `GET /api/v1/pipeline_prospects/{id}/action_executions` to
        confirm the `action_type` first.

        Response mirrors `POST /api/v1/emails/preview` plus a
        `pipeline_action_execution` block carrying the execution `id`,
        `state`, and resolved `description`.
      security:
        - api_key: []
      parameters:
        - name: pipeline_prospect_id
          in: path
          required: true
          description: Pipeline prospect id.
          schema:
            type: string
        - name: id
          in: path
          required: true
          description: PipelineActionExecution id.
          schema:
            type: string
      requestBody:
        required: false
        description: |
          All keys optional. Omitted keys fall back to the execution's
          persisted `pipeline_email_content`. Unknown keys are silently
          dropped per allowlist semantics.
        content:
          application/json:
            schema:
              type: object
              properties:
                rich_subject:
                  type: string
                  description: HTML subject override.
                body:
                  type: string
                  description: HTML body override.
                cc:
                  type: array
                  description: |
                    Override cc list. Each entry is one of: raw email string,
                    `{person_id}`, or `{internal_user_id}`. Absent → fall back
                    to persisted cc. Explicit `[]` clears.
                  items:
                    oneOf:
                      - type: string
                      - type: object
                        properties:
                          person_id:
                            type: string
                      - type: object
                        properties:
                          internal_user_id:
                            type: integer
                bcc:
                  type: array
                  description: Same shape as `cc`.
                  items:
                    oneOf:
                      - type: string
                      - type: object
                        properties:
                          person_id:
                            type: string
                      - type: object
                        properties:
                          internal_user_id:
                            type: integer
                attachments_attributes:
                  type: object
                  description: Override attachment attribute map (mirrors the UI form).
                  additionalProperties: true
                new_attachment_signed_ids:
                  type: array
                  description: ActiveStorage signed blob ids for newly uploaded attachments.
                  items:
                    type: string
                email_from_actor:
                  type: string
                  description: From-actor URI override (e.g. `user:42`).
                scheduled_at:
                  type: string
                  format: date-time
                  description: ISO 8601 schedule time override.
                email_template_id:
                  type: string
                  description: Override the template used for rendering this preview.
                custom_substituted_variables:
                  type: object
                  description: Map of `custom.<name>` → value substitutions.
                  additionalProperties:
                    type: string
              examples:
                no_overrides:
                  summary: Preview with persisted content (no overrides)
                  value: {}
                with_overrides:
                  summary: Override subject + add a cc person
                  value:
                    rich_subject: "Welcome — updated"
                    cc:
                      - person_id: "1042"
                    custom_substituted_variables:
                      "custom.greeting": "Hello"
      responses:
        "200":
          description: Preview envelope.
          content:
            application/json:
              examples:
                ok:
                  summary: Preview rendered, ready_to_send=true
                  value:
                    recipient:
                      person_id: "5512"
                      name: "Jane Investor"
                      email: jane@example.com
                      missing_email: false
                    from:
                      actor_uri: "user:42"
                      display: "Houston Searcy <houston@decilegroup.com>"
                    cc:
                      - ops@example.com
                    bcc: []
                    rendered:
                      subject: "Welcome to Acme Fund I"
                      body_html: "<p>Hi Jane, thanks for joining.</p>"
                      body_text: "Hi Jane, thanks for joining."
                    template:
                      id: "320"
                      name: "Welcome"
                      pipeline_id: "43nOmK6a"
                      decile_admin_only: false
                    variables:
                      resolved:
                        - name: "Person.first_name"
                          value: "Jane"
                      unresolved: []
                    attachments:
                      - id: "88"
                        filename: "welcome.pdf"
                        source: template
                    warnings: []
                    ready_to_send: true
                    pipeline_action_execution:
                      id: "421"
                      state: pending
                      description: "Send email Welcome"
        "401":
          description: unauthorized (missing/invalid API token)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "404":
          description: prospect or execution not found in the token's account
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: |
            Action is not `send_email` (`code: unsupported_action_type`), or
            a recipient list entry was malformed (`code:
            invalid_recipient_format`).
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
              examples:
                unsupported_action_type:
                  value:
                    error:
                      code: unsupported_action_type
                      message: "Preview is only supported for send_email actions; this execution is apply_tag."
                invalid_recipient_format:
                  value:
                    error:
                      code: invalid_recipient_format
                      message: "Recipient entry must be a string email, {person_id}, or {internal_user_id}."
  "/api/v1/pipelines":
    get:
      operationId: list_pipelines
      x-agent-tool: true
      x-agent-category: context
      summary: List pipelines for the current account
      tags:
        - Pipelines
      description: |
        Returns the active pipelines for the current account. Pass
        `kind=investor` to filter to investor pipelines. Rows are ordered
        with the account's primary investor pipeline first (when one is
        marked); prefer the `is_primary: true` row when multiple investor
        pipelines exist. The `id` field is the value to pass to any
        pipeline-scoped endpoint (e.g. `/api/v1/pipeline_prospects?pipeline_id=…`).
      security:
        - api_key: []
      parameters:
        - name: kind
          in: query
          description: Optional pipeline-type filter. Unknown values return 422.
          required: false
          schema:
            type: string
            enum:
              - recruiting
              - investor
              - investment
              - connector
              - closing
              - clean
              - custom
              - capital_call
              - company_funding_round
              - account_organization_fund
              - portfolio
              - event_attendance
              - base_post
              - newsletter
              - start_fund_closing
              - investor_research
              - lp_consent
              - spv_closing
      responses:
        "200":
          description: Pipelines found
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    account_id:
                      type: string
                    id:
                      type: string
                    name:
                      type: string
                    type:
                      type: string
                      description: Pipeline category (matches the `kind` query values).
                    is_primary:
                      type: boolean
                      description: TRUE on the account's primary pipeline of this type.
                    allowed_prospect_types:
                      type: array
                      items:
                        type: string
                  required:
                    - account_id
                    - id
                    - name
                    - type
                    - is_primary
                    - allowed_prospect_types
        "401":
          description: Unauthorized
        "422":
          description: Unknown kind value
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
  "/api/v1/pipelines/{id}":
    get:
      operationId: get_pipeline
      x-agent-tool: true
      summary: Retrieves info for a specific pipeline
      tags:
        - Pipelines
      description: Returns basic information for a specific pipeline and its stages.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Pipeline found
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  total_prospect_count:
                    type: integer
                  allowed_prospect_types:
                    type: array
                    items:
                      type: string
                  stages:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        prospect_count:
                          type: integer
        "401":
          description: Unauthorized
  "/api/v1/pipelines/{id}/metrics":
    get:
      summary: Pipeline metrics — current state + 7-day-ago snapshot
      operationId: get_pipeline_metrics
      x-agent-tool: true
      tags:
        - Pipelines
      description: |
        Returns aggregate counts/values for a pipeline plus a 7-days-ago
        snapshot for week-over-week deltas. The caller is expected to
        compute deltas in its prose layer.
      security:
        - api_key: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  current_state:
                    $ref: "#/components/schemas/PipelineMetricsSnapshot"
                  snapshot_7d_ago:
                    $ref: "#/components/schemas/PipelineMetricsSnapshot"
        "401":
          description: Unauthorized
        "404":
          description: pipeline not found or out of account scope
  "/api/v1/pipelines/{pipeline_id}/data_points":
    post:
      operationId: create_pipeline_data_point
      x-agent-tool: true
      summary: Create a custom data point for a pipeline type (account-wide fan-out)
      tags:
        - Pipelines
      description:
        "Create a custom data point (Variable) on the account, scoped to the
        **type** of the targeted pipeline (investor / company / etc). Data
        points are not attached to a single pipeline — they live at the
        account level under a type-specific context (e.g.
        `investor_data_points`, `company_data_points`). The `pipeline_id` in
        the path is used only to derive that type context. When
        `add_as_column: true`, the resulting kanban column is fanned out to
        **every pipeline of that type** in the account, not just the targeted
        pipeline (this matches the UI behavior). Account admin authorization
        is required (account admins, or decile admins acting on the account).
        The `select` format requires a non-empty `select_options` array."
      security:
        - api_key: []
      parameters:
        - name: pipeline_id
          in: path
          required: true
          description:
            "Obscure ID of any pipeline of the desired type. Used to derive
            the data point's type context (investor / company / etc). The
            data point applies to the account and, when `add_as_column` is
            true, fans out as a column to all pipelines of this type in the
            account — not just this one."
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - data_point
              properties:
                data_point:
                  type: object
                  required:
                    - name
                    - format
                  properties:
                    name:
                      type: string
                      description: Display name for the data point
                    format:
                      type: string
                      description: "Format key — controls how the data point is rendered and validated."
                      # Canonical list lives in Ruby at
                      # Api::V1::PipelineDataPointsController::FORMAT_NAMES (keys).
                      # Drift between this enum and that constant fails CI via
                      # test/controllers/api/v1/pipeline_data_points_controller_test.rb.
                      enum:
                        - string
                        - paragraph
                        - url
                        - number
                        - date
                        - date_us
                        - currency_us
                        - currency_eu
                        - percent
                        - percent_rounded
                        - select
                        - file_url
                    description:
                      type: string
                      description: Optional helper text shown to users editing the data point.
                    add_as_column:
                      type: boolean
                      description:
                        "If true, surface the data point as a kanban column.
                        The column is added to **all pipelines of the same
                        type** as the targeted `pipeline_id` in the account
                        (fan-out behavior matching the UI), not just the
                        targeted pipeline."
                    select_options:
                      type: array
                      items:
                        type: string
                      description: Required when `format` is `select`. The dropdown options.
      responses:
        "201":
          description: Data point created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                  name:
                    type: string
                  value:
                    type: string
                  format:
                    type: string
                    description: The short format key (echoes the request).
                  context:
                    type: string
                    description: "Variable context, e.g. `investor_data_points`."
                  description:
                    type: string
                    nullable: true
                  add_as_column:
                    type: boolean
                  select_options:
                    type: array
                    nullable: true
                    items:
                      type: string
        "400":
          description: Request body missing the `data_point` wrapper
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden (caller is not an account admin or decile admin for the account)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Pipeline not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Validation failed (missing/invalid name, unknown format, or select format with empty `select_options`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/deals/share":
    post:
      operationId: create_deal_share
      x-agent-tool: true
      summary: Create or update a Deal Share
      tags:
        - Deal Shares
      description:
        "Share a deal from the caller's account into the network feed. Idempotent
        per organization: if a Deal Share already exists for the given organization
        in the caller's account its attributes are updated; otherwise a new record
        is created. The target organization must belong to the caller's account —
        use POST /api/v1/organization to create or upsert one first. Pass selected_files
        to attach existing org / deal-memo / term-sheet files the caller account owns;
        foreign or unknown ids are skipped. The response attachments list confirms what
        was attached."
      security:
        - api_key: []
      parameters: []
      responses:
        "201":
          description: Deal Share created
          content:
            application/json:
              examples:
                created:
                  value:
                    success: true
                    deals_share_id: 42
                    organization_id: 7
                    created_at: "2026-04-20T12:00:00Z"
                    updated_at: "2026-04-20T12:00:00Z"
                    attachments:
                      - id: 99
                        name: pitch_deck.pdf
        "200":
          description: Existing Deal Share updated
          content:
            application/json:
              examples:
                updated:
                  value:
                    success: true
                    deals_share_id: 42
                    organization_id: 7
                    created_at: "2026-04-20T12:00:00Z"
                    updated_at: "2026-04-20T12:30:00Z"
                    attachments:
                      - id: 99
                        name: pitch_deck.pdf
        "400":
          description: Request body missing deals_share wrapper
        "401":
          description: Unauthorized
        "404":
          description: Organization not found (not in caller's account)
        "422":
          description: Validation failed (missing required field or invalid value)
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                deals_share:
                  type: object
                  properties:
                    organization_id:
                      type: string
                      description: ID of an Organization in the caller's account
                    company_name:
                      type: string
                    the_bet:
                      type: string
                      description: Thesis for why this deal is interesting
                    referring_manager_name:
                      type: string
                    referring_manager_email:
                      type: string
                      format: email
                    founder:
                      type: string
                    short_description:
                      type: string
                    region:
                      type: string
                    country_address:
                      type: string
                    company_email:
                      type: string
                      format: email
                    deck_url:
                      type: string
                    round:
                      type: string
                      description: "Funding round key (e.g. pre_seed, seed, series_a); see CompanyFundingRound.rounds"
                    anticipated_close_date:
                      type: string
                      format: date
                    why:
                      type: string
                    investor_role:
                      type: string
                      enum:
                        - investing_lead
                        - investing_not_lead
                        - invested_prior_round
                        - maybe_investing
                        - advising
                        - not_investing
                        - interesting_deal
                        - founder
                    deal_source:
                      type: string
                      enum:
                        - cold_inreach
                        - conference
                        - direct_founder_relationship
                        - personal_network
                        - pitch_session
                        - prospecting
                        - vc_deal_flow
                        - portfolio_company
                        - known_founder_referral
                        - known_investor_invested
                        - known_investor_not_invested
                        - syndication_group
                        - venture_studio
                    traction_metrics:
                      type: string
                    amount_raising:
                      type: number
                    deal_terms:
                      type: string
                    valuation:
                      type: number
                    industry_list:
                      type: array
                      items:
                        type: string
                    selected_files:
                      type: array
                      items:
                        type: string
                      description: "Ids of existing files the caller account owns to attach (e.g. organization_123, deal_memo_45, term_sheet_67). Foreign/unknown ids are skipped."
                  required:
                    - organization_id
                    - company_name
                    - the_bet
                    - referring_manager_name
                    - referring_manager_email
              required:
                - deals_share
  "/api/v1/deals/shares":
    get:
      operationId: list_deal_shares
      x-agent-tool: true
      summary: List the deal shares submitted by the current account
      tags:
        - Deal Shares
      description: |
        Returns the deal shares the caller's account has submitted into the network feed.
        Each row carries the full submission attributes plus the computed `stage`.

        Filter with `search`, `industry`, `stage`, and `round`. Sort with `column` +
        `direction`. Pagination is keyset — pass `pagination.next_page_token` from a
        response back as `page_token=` to fetch the next page.
      security:
        - api_key: []
      parameters:
        - name: search
          in: query
          required: false
          description: Free-text match over `company_name`, `short_description`, `region`, and `country_address`.
          schema:
            type: string
        - name: industry
          in: query
          required: false
          description: Filter to shares tagged with this industry.
          schema:
            type: string
        - name: stage
          in: query
          required: false
          description: Computed stage bucket filter.
          schema:
            type: string
            enum:
              - New Deals (Filtered on Thesis)
              - Unfiltered New Deals
              - Expired Deals
              - Expiring Deals
        - name: round
          in: query
          required: false
          description: Funding round filter (e.g. `seed`, `a`, `b`).
          schema:
            type: string
        - name: column
          in: query
          required: false
          description: Sort column. Unknown columns fall back to `company_name`.
          schema:
            type: string
            default: company_name
            enum:
              - company_name
              - created_at
              - amount_raising
              - anticipated_close_date
              - round
        - name: direction
          in: query
          required: false
          description: Sort direction. Sending anything other than `asc`/`desc` returns 400.
          schema:
            type: string
            default: asc
            enum:
              - asc
              - desc
        - name: page_token
          in: query
          required: false
          description: Opaque keyset cursor returned by a previous call as `pagination.next_page_token`.
          schema:
            type: string
        - name: per_page
          in: query
          required: false
          description: Page size. Capped at 100; defaults to 25.
          schema:
            type: integer
            default: 25
            maximum: 100
      responses:
        "200":
          description: paginated list of the caller account's deal shares
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      - id: 42
                        organization_id: 7
                        company_name: Acme Corp
                        the_bet: Strong team and differentiated wedge
                        referring_manager_name: Alice Referrer
                        referring_manager_email: alice@example.com
                        round: seed
                        amount_raising: 2000000
                        industry_list:
                          - Fintech
                        stage: Unfiltered New Deals
                        created_at: "2026-04-20T12:00:00Z"
                        updated_at: "2026-04-20T12:00:00Z"
                    pagination:
                      next_page_token: eyJjb21wYW55X25hbWUiOiJBY21lIENvcnAiLCJpZCI6NDJ9
                      has_more: true
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/DealShareDetail"
                  pagination:
                    type: object
                    properties:
                      next_page_token:
                        type: string
                        nullable: true
                      has_more:
                        type: boolean
        "400":
          description: invalid parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
  "/api/v1/deals/shares/{id}":
    get:
      operationId: get_deal_share
      x-agent-tool: true
      summary: Get a single deal share owned by the current account
      tags:
        - Deal Shares
      description: |
        Returns one deal share submitted by the caller's account, with the full
        submission attributes and the computed `stage`. Returns 404 if the share
        does not belong to the caller's account.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Deal share id.
          schema:
            type: integer
      responses:
        "200":
          description: The deal share
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DealShareDetail"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: delete_deal_share
      x-agent-tool: true
      summary: Delete a deal share owned by the current account
      tags:
        - Deal Shares
      description: |
        Permanently deletes a deal share owned by the caller's account. The lookup
        is scoped to the caller's account, so an unknown id or one belonging to
        another account returns 404 and deletes nothing. This action cannot be undone.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Deal share id.
          schema:
            type: integer
      responses:
        "200":
          description: The deal share was deleted
          content:
            application/json:
              examples:
                deleted:
                  value:
                    success: true
                    message: Deal share deleted successfully
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/deals/shares/{id}/copy_to_pipeline":
    post:
      operationId: copy_deal_to_pipeline
      x-agent-tool: true
      summary: Copy a shared deal into a pipeline stage
      tags:
        - Deal Shares
      description:
        "Copy a network-feed shared deal into one of the caller account's
        pipeline stages. Creates (or reuses) an organization in the caller's
        account and adds it as a prospect to the given stage, asynchronously.
        The pipeline_stage_id MUST belong to the caller's account. Requires the
        caller account's shared deal feed to be unlocked
        (deal_sharing_unlocked_until present and in the future); decile admins
        and full-access account admins bypass that gate."
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Deals::Share id from the network feed
          schema:
            type: string
      responses:
        "202":
          description: Copy accepted and enqueued
          content:
            application/json:
              examples:
                accepted:
                  value:
                    success: true
                    deals_share_id: 42
                    pipeline_stage_id: 7
                    status: processing
        "401":
          description: Unauthorized
        "403":
          description: Shared deal feed locked for the caller account
        "404":
          description: Deal share not found in the network feed, or pipeline stage not in the caller's account
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                pipeline_stage_id:
                  type: string
                  description: Target PipelineStage id in the caller's account
              required:
                - pipeline_stage_id
  "/api/v1/deals/shares/ai_auto_fill":
    post:
      operationId: enrich_deal_share
      x-agent-tool: true
      summary: AI auto-fill enrichment for a deal share
      tags:
        - Deal Shares
      description:
        "Run AI auto-fill enrichment for a deal share. Given an organization in
        the caller's account and a list of deal-share form field names, the
        model reasons over the data Hub already holds (organization profile,
        deal memos, notes, documents) and returns suggested values for those
        fields. The organization MUST belong to the caller's account. The call
        runs the LLM synchronously, so response latency tracks the model call.
        Field names use the form-input shape, e.g. `deals_share[region]` or
        `deals_share[industry_list][]`."
      security:
        - api_key: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                organization_id:
                  type: string
                  description: ID of an Organization in the caller's account
                fields:
                  type: array
                  items:
                    type: string
                  description: Deal-share form field names to enrich, e.g. ['deals_share[region]', 'deals_share[industry_list][]']
              required:
                - organization_id
                - fields
      responses:
        "200":
          description: Enrichment completed
          content:
            application/json:
              examples:
                completed:
                  value:
                    data:
                      region: US
                      country_address: United States
                      traction_metrics: "$100M ARR, 50% YoY growth"
                    status: completed
        "401":
          description: Unauthorized
        "404":
          description: Organization not found in the caller's account
        "422":
          description: Enrichment failed (e.g. the AI response could not be parsed)
          content:
            application/json:
              examples:
                failed:
                  value:
                    status: failed
                    error: Invalid JSON response from AI service
  "/api/v1/deal_memos":
    get:
      operationId: list_deal_memos
      x-agent-tool: true
      summary: List deal memos for the current account
      tags:
        - Deal Memos
      description: |
        Returns a paginated list of deal memos belonging to the caller's
        account. Each row is a compact summary suitable for triage; use
        `GET /api/v1/deal_memos/{id}` for the full payload (counterfactual,
        ratings, links, question answers).

        Pagination is keyset — pass `pagination.next_page_token` from a
        response back as `page_token=` to fetch the next page.
      security:
        - api_key: []
      parameters:
        - name: organization_id
          in: query
          required: false
          description: Filter to deal memos for a specific organization id.
          schema:
            type: integer
        - name: stage
          in: query
          required: false
          description: Filter to a single stage (active, review, ic_review, ic_review_submitted, ic_pre_approved, ic_approved, closed).
          schema:
            type: string
            enum:
              - active
              - review
              - ic_review
              - ic_review_submitted
              - ic_pre_approved
              - ic_approved
              - closed
        - name: page_token
          in: query
          required: false
          description: Opaque keyset cursor returned by a previous call as `pagination.next_page_token`.
          schema:
            type: string
        - name: per_page
          in: query
          required: false
          description: Page size. Capped at 100; defaults to 25.
          schema:
            type: integer
            default: 25
            maximum: 100
      responses:
        "200":
          description: paginated list of deal memos
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      - id: 101
                        stage: active
                        investment_type: initial
                        organization_id: 7
                        organization_name: Acme Inc.
                        close_type: null
                        created_at: "2026-05-14T12:00:00Z"
                        updated_at: "2026-05-14T12:00:00Z"
                        account_review_enabled: true
                    pagination:
                      next_page_token: eyJpZCI6MTAxfQ
                      has_more: true
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        stage:
                          type: string
                        investment_type:
                          type: string
                        organization_id:
                          type: integer
                        organization_name:
                          type: string
                          nullable: true
                        close_type:
                          type: string
                          nullable: true
                        created_at:
                          type: string
                          format: date-time
                        updated_at:
                          type: string
                          format: date-time
                        account_review_enabled:
                          type: boolean
                          description: Whether the caller's account has the deal memo review workflow enabled (account-level flag). When false, submit_for_review and close are unavailable from the web UI.
                  pagination:
                    type: object
                    properties:
                      next_page_token:
                        type: string
                        nullable: true
                      has_more:
                        type: boolean
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "422":
          description: Invalid stage filter
    post:
      operationId: create_deal_memo
      x-agent-tool: true
      summary: Start a Deal Memo
      tags:
        - Deal Memos
      description:
        "Start a new deal memo for an organization in the caller's account. The
        config template is resolved automatically from the account's latest deal
        memo config. Fails with 422 if the organization already has a non-closed
        deal memo. The target organization must belong to the caller's account.
        Set `copy_last_deal_memo` to true to clone the organization's most recent
        closed deal memo instead of starting a blank one. File attachments are not
        supported via the API."
      security:
        - api_key: []
      parameters: []
      responses:
        "201":
          description: Deal Memo created
          content:
            application/json:
              examples:
                created:
                  value:
                    data:
                      id: 101
                      stage: active
                      investment_type: initial
                      created_at: "2026-05-14T12:00:00Z"
                      organization_id: 7
                      organization_name: Acme Inc.
        "400":
          description: Request body missing deal_memo wrapper or organization_id
        "401":
          description: Unauthorized
        "403":
          description: Caller cannot manage investments for this account
        "404":
          description: Organization not found (not in caller's account)
        "422":
          description: Validation failed, or the organization already has a non-closed deal memo
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                deal_memo:
                  type: object
                  properties:
                    organization_id:
                      type: string
                      description: ID of an Organization in the caller's account
                    investment_type:
                      type: string
                      enum:
                        - initial
                        - follow on
                        - secondary
                      description: Investment type. Defaults to initial.
                    description:
                      type: string
                    counterfactual:
                      type: string
                      description: Counterfactual analysis text for the deal memo
                    company_url:
                      type: string
                      description: Company URL; updates the organization's company_url if set
                    start_ai_orchestrator:
                      type: boolean
                      description: Whether to start the AI orchestrator for this deal memo
                    copy_last_deal_memo:
                      type: boolean
                      description: Copy the organization's last closed deal memo instead of starting a blank one
                    links_attributes:
                      type: array
                      description: Reference links to attach to the deal memo
                      items:
                        type: object
                        properties:
                          name:
                            type: string
                            description: Link label
                          link:
                            type: string
                            description: Link URL
                    organization_attributes:
                      type: object
                      description: Nested organization updates applied alongside the deal memo
                      properties:
                        id:
                          type: string
                          description: ID of the organization
                        company_funding_rounds_attributes:
                          type: array
                          description: Company funding rounds for the organization
                          items:
                            type: object
                            properties:
                              id:
                                type: string
                                description: ID of an existing funding round (for updates)
                              position:
                                type: integer
                              close_date:
                                type: string
                                description: Close date (YYYY-MM-DD)
                              investment:
                                type: number
                              round:
                                type: string
                              amount_raised:
                                type: number
                              security:
                                type: string
                              valuation_cap:
                                type: number
                              price_per_share:
                                type: number
                              valuation_type:
                                type: string
                              _destroy:
                                type: boolean
                                description: Set true to remove an existing funding round
                  required:
                    - organization_id
              required:
                - deal_memo
  "/api/v1/deal_memos/{id}":
    get:
      operationId: get_deal_memo
      x-agent-tool: true
      summary: Get a single Deal Memo by id
      tags:
        - Deal Memos
      description: |
        Returns the full deal memo payload — organization, counterfactual,
        links, ratings summary, and question answers — for an agent to inspect
        before acting on the memo. Attachments, due-diligence tasks, and
        comments are not included; fetch them via their own endpoints.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Deal Memo id.
          schema:
            type: string
      responses:
        "200":
          description: the deal memo
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      id: 101
                      stage: active
                      close_type: null
                      investment_type: initial
                      created_at: "2026-05-14T12:00:00Z"
                      updated_at: "2026-05-14T12:00:00Z"
                      account_review_enabled: true
                      deal_memo_config_id: 5
                      organization:
                        id: 7
                        name: Acme Inc.
                        company_url: https://acme.example.com
                      description: API created memo
                      counterfactual: What if we passed?
                      links:
                        - id: 12
                          name: Pitch deck
                          link: https://example.com/deck
                      ratings_summary:
                        average_score: 4.2
                        raters_count: 3
                      question_answers:
                        - id: 9
                          question: What is the moat?
                          answer: Network effects in vertical SaaS.
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: object
                    properties:
                      account_review_enabled:
                        type: boolean
                        description: Whether the caller's account has the deal memo review workflow enabled (account-level flag). When false, submit_for_review and close are unavailable from the web UI.
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Deal Memo not found in caller's account
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    patch:
      operationId: update_deal_memo
      x-agent-tool: true
      summary: Update a Deal Memo
      tags:
        - Deal Memos
      description:
        "Update an existing deal memo in the caller's account. Rejects updates
        with 422 when the deal memo is closed. Does not modify ratings, answers,
        attachments, or due-diligence tasks."
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID of the Deal Memo to update
      responses:
        "200":
          description: Deal Memo updated
          content:
            application/json:
              examples:
                ok:
                  value:
                    data:
                      id: 101
                      stage: active
                      investment_type: initial
                      close_type: null
                      created_at: "2026-05-14T12:00:00Z"
                      organization_id: 7
                      organization_name: Acme Inc.
        "400":
          description: Request body missing deal_memo wrapper
        "401":
          description: Unauthorized
        "403":
          description: Caller cannot manage investments for this account
        "404":
          description: Deal Memo not found in caller's account
        "422":
          description: Validation failed, or the deal memo is closed
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                deal_memo:
                  type: object
                  properties:
                    investment_type:
                      type: string
                      enum:
                        - initial
                        - follow on
                        - secondary
                    description:
                      type: string
                    counterfactual:
                      type: string
                    links_attributes:
                      type: array
                      description: Reference links on the deal memo (provide id to update or _destroy to remove)
                      items:
                        type: object
                        properties:
                          id:
                            type: string
                          name:
                            type: string
                          link:
                            type: string
                          _destroy:
                            type: boolean
                    organization_attributes:
                      type: object
                      properties:
                        id:
                          type: string
                        company_funding_rounds_attributes:
                          type: array
                          items:
                            type: object
                            properties:
                              id:
                                type: string
                              position:
                                type: integer
                              close_date:
                                type: string
                              investment:
                                type: number
                              round:
                                type: string
                              amount_raised:
                                type: number
                              security:
                                type: string
                              valuation_cap:
                                type: number
                              price_per_share:
                                type: number
                              valuation_type:
                                type: string
                              _destroy:
                                type: boolean
              required:
                - deal_memo
  "/api/v1/deal_memos/{id}/submit_for_review":
    post:
      operationId: submit_deal_memo_for_review
      x-agent-tool: true
      summary: Submit a Deal Memo for review
      tags:
        - Deal Memos
      description:
        "Submit a deal memo for review by Decile Hub. Requires the account to
        have the Deal Memo Review product enabled (returns 422 otherwise).
        Optionally attach a submission comment via `submission_comment.content`."
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID of the Deal Memo to submit for review
      responses:
        "200":
          description: Deal Memo submitted for review
          content:
            application/json:
              examples:
                ok:
                  value:
                    data:
                      id: 101
                      stage: review
                      investment_type: initial
                      close_type: null
                      created_at: "2026-05-14T12:00:00Z"
                      organization_id: 7
                      organization_name: Acme Inc.
        "401":
          description: Unauthorized
        "403":
          description: Caller cannot manage investments for this account
        "404":
          description: Deal Memo not found in caller's account
        "422":
          description: Account does not have the Deal Memo Review product enabled, or validation failed
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                deal_memo:
                  type: object
                  properties:
                    submission_comment:
                      type: object
                      description: Optional comment to attach with the review submission
                      properties:
                        content:
                          type: string
                          description: Submission comment text
  "/api/v1/deal_memos/{id}/close":
    post:
      operationId: close_deal_memo
      x-agent-tool: true
      summary: Close a Deal Memo
      tags:
        - Deal Memos
      description:
        "Close a deal memo with an outcome of approve or pass. Moves the
        underlying prospect to the configured pipeline stage and dispatches the
        close notification."
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID of the Deal Memo to close
      responses:
        "200":
          description: Deal Memo closed
          content:
            application/json:
              examples:
                ok:
                  value:
                    data:
                      id: 101
                      stage: closed
                      investment_type: initial
                      close_type: approve
                      created_at: "2026-05-14T12:00:00Z"
                      organization_id: 7
                      organization_name: Acme Inc.
        "401":
          description: Unauthorized
        "403":
          description: Caller cannot manage investments for this account
        "404":
          description: Deal Memo not found in caller's account
        "422":
          description: close_type missing or invalid, or validation failed
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                deal_memo:
                  type: object
                  properties:
                    close_type:
                      type: string
                      enum:
                        - approve
                        - pass
                      description: Outcome of the close
                  required:
                    - close_type
              required:
                - deal_memo
  "/api/v1/portfolio_companies":
    get:
      operationId: list_portfolio_companies
      x-agent-tool: true
      summary: List portfolio companies for the current account
      tags:
        - PortfolioCompanies
      description: |
        Returns portfolio companies across every fund in the current account.
        Each row is a (fund, organization) pair — the fund's stake in a single
        underlying company. Filter by `fund_id` or `organization_id`.

        Pagination is keyset — pass `pagination.next_page_token` from a response
        back as `page_token=` to fetch the next page.
      security:
        - api_key: []
      parameters:
        - name: fund_id
          in: query
          required: false
          description: Filter to a single fund_detail id (`firm_admin_fund_details.id`).
          schema:
            type: integer
        - name: organization_id
          in: query
          required: false
          description: Filter to portfolio companies for a specific organization id.
          schema:
            type: integer
        - name: page_token
          in: query
          required: false
          description: Opaque keyset cursor returned by a previous call as `pagination.next_page_token`.
          schema:
            type: string
        - name: per_page
          in: query
          required: false
          description: Page size. Capped at 100; defaults to 25.
          schema:
            type: integer
            default: 25
            maximum: 100
        - name: fields
          in: query
          required: false
          description: Comma-separated top-level fields to return. Always returns at least `id` and `name`. Unknown fields are ignored.
          schema:
            type: string
      responses:
        "200":
          description: paginated list of portfolio companies
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      - id: 123
                        name: Acme Corp
                        legal_name: Acme Corporation, Inc.
                        display_legal_name: Acme Corporation, Inc.
                        organization_id: 456
                        firm_admin_fund_details_id: 7
                        entity_type: C-Corp
                        initial_investment_date: "2023-04-01"
                        investment_thesis: Strong founder, fast-growing market.
                        reserve_consumed: "25000.00"
                        board_representation:
                          board_seats: 1
                          observer_seats: 0
                          notes: Founder retains chair.
                        exit_strategy:
                          target_exit_year: 2028
                          exit_type: acquisition
                          notes: Strategic acquirers in adjacent verticals.
                        created_at: "2023-04-01T18:31:00Z"
                        updated_at: "2024-01-15T10:00:00Z"
                    pagination:
                      next_page_token: eyJpZCI6MTIzfQ
                      has_more: true
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortfolioCompany"
                  pagination:
                    type: object
                    properties:
                      next_page_token:
                        type: string
                        nullable: true
                      has_more:
                        type: boolean
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
  "/api/v1/portfolio_companies/{id}":
    get:
      operationId: get_portfolio_company
      x-agent-tool: true
      summary: Get a single portfolio company by id
      tags:
        - PortfolioCompanies
      description: |
        Returns one portfolio company — the same allowlisted fields surfaced by
        `GET /api/v1/portfolio_companies`.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Portfolio company id.
          schema:
            type: integer
        - name: fields
          in: query
          required: false
          description: Comma-separated top-level fields to return. Always returns at least `id` and `name`. Unknown fields are ignored.
          schema:
            type: string
      responses:
        "200":
          description: the portfolio company
          content:
            application/json:
              examples:
                default:
                  value:
                    id: 123
                    name: Acme Corp
                    legal_name: Acme Corporation, Inc.
                    display_legal_name: Acme Corporation, Inc.
                    organization_id: 456
                    firm_admin_fund_details_id: 7
                    entity_type: C-Corp
                    initial_investment_date: "2023-04-01"
                    investment_thesis: Strong founder, fast-growing market.
                    reserve_consumed: "25000.00"
                    board_representation:
                      board_seats: 1
                      observer_seats: 0
                      notes: Founder retains chair.
                    exit_strategy:
                      target_exit_year: 2028
                      exit_type: acquisition
                      notes: Strategic acquirers in adjacent verticals.
                    created_at: "2023-04-01T18:31:00Z"
                    updated_at: "2024-01-15T10:00:00Z"
              schema:
                $ref: "#/components/schemas/PortfolioCompany"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/portfolio_companies/{portfolio_company_id}/investments":
    get:
      operationId: list_portfolio_company_investments
      x-agent-tool: true
      summary: List investments for a portfolio company
      tags:
        - PortfolioCompanies
      description: |
        Returns the investments (accounting-transaction-driven tranches) belonging
        to a single portfolio company. Each row represents one investment tranche
        with share counts and timestamps.

        Pagination is keyset — pass `pagination.next_page_token` from a response
        back as `page_token=` to fetch the next page.
      security:
        - api_key: []
      parameters:
        - name: portfolio_company_id
          in: path
          required: true
          description: Portfolio company id.
          schema:
            type: integer
        - name: page_token
          in: query
          required: false
          description: Opaque keyset cursor returned by a previous call as `pagination.next_page_token`.
          schema:
            type: string
        - name: per_page
          in: query
          required: false
          description: Page size. Capped at 100; defaults to 25.
          schema:
            type: integer
            default: 25
            maximum: 100
        - name: fields
          in: query
          required: false
          description: Comma-separated top-level fields to return. Always returns at least `id`. Unknown fields are ignored.
          schema:
            type: string
      responses:
        "200":
          description: paginated list of investments
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      - id: 901
                        firm_admin_portfolio_companies_id: 123
                        firm_admin_accounting_transactions_id: 4567
                        shares: 1000
                        shares_decimal: "1000.0"
                        ignore_for_fmv_calc: false
                        tax_born_on: "2023-04-01"
                        created_at: "2023-04-01T18:31:00Z"
                        updated_at: "2024-01-15T10:00:00Z"
                    pagination:
                      next_page_token: eyJpZCI6OTAxfQ
                      has_more: true
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortfolioCompanyInvestment"
                  pagination:
                    type: object
                    properties:
                      next_page_token:
                        type: string
                        nullable: true
                      has_more:
                        type: boolean
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Portfolio company not found in current account
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/portfolio_company_investments/{id}":
    get:
      operationId: get_portfolio_company_investment
      x-agent-tool: true
      summary: Get a single portfolio company investment
      tags:
        - PortfolioCompanies
      description: |
        Returns a single investment tranche (accounting-transaction-driven) on a portfolio
        company, with applied overrides resolved.

        The response combines the column allowlist exposed by
        `list_portfolio_company_investments` (id, parent FKs, shares, shares_decimal,
        ignore_for_fmv_calc, tax_born_on, timestamps) with a fixed set of computed/resolved
        fields: `effective_shares`, `effective_tax_born_date`, `price_per_share`,
        `transaction_amount` (cost basis with write-off and convertible-debt conversion
        applied), and `transaction_at`.

        Raw admin levers (override_fmv*, override_investment_on, legal_cost_per_share,
        write_off_investment*, convertible_debt_new_cost) are intentionally hidden — the
        computed fields encode their resolved effect.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Portfolio company investment id.
          schema:
            type: integer
        - name: fields
          in: query
          required: false
          description: Comma-separated top-level fields to return. Addresses both column fields and computed fields. Always returns at least `id`. Unknown fields are ignored.
          schema:
            type: string
      responses:
        "200":
          description: a single investment with applied overrides resolved
          content:
            application/json:
              examples:
                default:
                  value:
                    id: 901
                    firm_admin_portfolio_companies_id: 123
                    firm_admin_accounting_transactions_id: 4567
                    shares: 1000
                    shares_decimal: "1000.0"
                    ignore_for_fmv_calc: false
                    tax_born_on: "2023-04-01"
                    created_at: "2023-04-01T18:31:00Z"
                    updated_at: "2024-01-15T10:00:00Z"
                    effective_shares: "1000.0"
                    effective_tax_born_date: "2023-04-01"
                    price_per_share: "100.0"
                    transaction_amount: "100000.0"
                    transaction_at: "2023-04-01T18:31:00Z"
              schema:
                $ref: "#/components/schemas/PortfolioCompanyInvestmentDetail"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Investment not found in current account
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/pacts/send_pact":
    post:
      operationId: send_pact
      x-agent-tool: true
      summary: Send a PACT email to a Person
      tags:
        - Pacts
      description:
        Mirrors the UI "Send PACT" button. Adds or moves the Person onto the investor
        pipeline's Send PACT stage, finds the configured PACT email template, and
        sends it. `pipeline_id` is required only when the
        account has more than one investor pipeline.
      security:
        - api_key: []
      parameters: []
      responses:
        "200":
          description: PACT email dispatched
          content:
            application/json:
              examples:
                ok:
                  value:
                    success: true
                    pipeline_prospect_id: 1234
                    pipeline_action_execution_id: 5678
        "400":
          description: pipeline_id required when multiple investor pipelines exist
        "401":
          description: Unauthorized
        "403":
          description: Caller lacks access to the investor pipeline
        "404":
          description: Person or pipeline not found
        "422":
          description: Pipeline is not an investor pipeline, or PACT stage / email template not configured
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                person_id:
                  type: string
                  description: Person ID in caller account
                pipeline_id:
                  type: string
                  description: Optional investor pipeline obscure id
              required:
                - person_id
  "/api/v1/lpas/send_lpa":
    post:
      operationId: send_lpa
      x-agent-tool: true
      summary: Send an LPA email to a Person
      tags:
        - Lpas
      description:
        Mirrors the UI "Send LPA" button. Resolves the target investor pipeline and
        its linked closing pipeline, moves the Person onto the investor pipeline's
        Closing stage, creates or finds their capital account on the fund, seats the
        closing-pipeline prospect on the Send LPA / Send questionnaire stage, finds
        the configured LPA email template, and sends it. `pipeline_id` is
        required only when the account has more than one investor pipeline. The
        returned `pipeline_prospect_id` refers to the closing-pipeline prospect
        (capital account), not the investor-pipeline prospect.
      security:
        - api_key: []
      parameters: []
      responses:
        "200":
          description: LPA email dispatched
          content:
            application/json:
              examples:
                ok:
                  value:
                    success: true
                    pipeline_prospect_id: 9012
                    pipeline_action_execution_id: 3456
        "400":
          description: pipeline_id required when multiple investor pipelines exist
        "401":
          description: Unauthorized
        "403":
          description: Caller lacks access to the investor pipeline
        "404":
          description: Person or pipeline not found
        "422":
          description: Pipeline is not an investor pipeline; no closing pipeline / fund linked; no LPA email template or target stage configured
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                person_id:
                  type: string
                  description: Person ID in caller account
                pipeline_id:
                  type: string
                  description: Optional investor pipeline obscure id
              required:
                - person_id
  "/api/v1/base/inbox":
    get:
      operationId: list_base_inbox
      x-agent-tool: true
      summary: List Decile Base inbox items
      tags:
        - Base
      description:
        Returns the authenticated user's Decile Base inbox — a paginated,
        time-ordered feed of posts and replies they are subscribed to or
        mentioned in. Available to any API token with Base access. When
        `channel_id` is supplied, the token's user must have access to that
        channel.
      security:
        - api_key: []
      parameters:
        - name: page
          in: query
          description: Page number (1-indexed)
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          description: Items per page (1–100, default 50)
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 100
        - name: channel_id
          in: query
          description: Restrict results to a single Base channel ID
          schema:
            type: integer
        - name: exclude_channel_ids
          in: query
          description: Comma-separated list of channel IDs to exclude
          schema:
            type: string
            example: "12,34,56"
        - name: since
          in: query
          description: ISO 8601 datetime — only items created/updated on or after this time
          schema:
            type: string
            format: date-time
            example: "2026-04-01T00:00:00Z"
        - name: user_ids
          in: query
          description: Comma-separated list of user IDs to filter by author
          schema:
            type: string
            example: "1,2,3"
      responses:
        "200":
          description: inbox returned
          content:
            application/json:
              examples:
                success:
                  value:
                    items:
                      - type: post
                        id: 101
                        post_id: 555
                        channel_id: 8
                        channel_name: decile-base
                        channel_type: Base::ConversationChannel
                        user:
                          id: 7
                          first_name: John
                          last_name: Doe
                          email: john@example.com
                        title: Welcome to Base
                        content: Hello world
                        url: https://www.decilehub.com/base/8/posts/555
                        attachments: []
                        unread: true
                        created_at: "2026-04-29T15:30:00Z"
                        updated_at: "2026-04-29T15:30:00Z"
                        replies_count: 3
                    meta:
                      page: 1
                      per_page: 50
                      total: 1
                      has_more: false
        "400":
          description: bad request (e.g. invalid `since` parameter)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                bad_since:
                  value:
                    error:
                      code: bad_request
                      message: "Invalid since parameter. Use ISO 8601 format."
        "401":
          description: Unauthorized — token missing, invalid, or expired
        "403":
          description:
            Forbidden — token lacks necessary Base access
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/base/channels":
    get:
      operationId: list_base_channels
      x-agent-tool: true
      summary: List accessible Decile Base channels
      tags:
        - Base
      description:
        Lists Base channels the authenticated user can see. Available to any
        API token with Base access. Returns the user's accessible question and
        conversation channels.
      security:
        - api_key: []
      parameters:
        - name: exclude_channel_ids
          in: query
          description: Comma-separated list of channel IDs to exclude from the response
          schema:
            type: string
            example: "12,34"
      responses:
        "200":
          description: channels returned
          content:
            application/json:
              examples:
                success:
                  value:
                    channels:
                      - id: 8
                        name: decile-base
                        friendly_name: Decile Base
                        type: Base::ConversationChannel
                        public: true
                        sensitive: false
                        parent_channel_id:
                        posts_count: 142
                        color: "#3366FF"
                      - id: 12
                        name: questions
                        friendly_name: Questions
                        type: Base::QuestionChannel
                        public: true
                        sensitive: false
                        parent_channel_id:
                        posts_count: 37
                        color: "#22AA88"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden — token lacks Base access
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/base/posts":
    post:
      operationId: create_base_post
      x-agent-tool: true
      summary: Create a Decile Base post
      tags:
        - Base
      description:
        Creates a new post in a Base channel. The `content` is parsed as
        Markdown and stored as rich text. Available to any API token with
        Base access; the acting user must also be authorized for the target
        channel. The post is always attributed to the user who owns the API
        token; impersonation is not supported.
      security:
        - api_key: []
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - post
              properties:
                post:
                  type: object
                  required:
                    - channel_id
                    - content
                  properties:
                    channel_id:
                      type: integer
                      description: Target Base channel ID
                    content:
                      type: string
                      description: Post body in Markdown
                    title:
                      type: string
                      description: Optional post title
      responses:
        "201":
          description: post created
          content:
            application/json:
              examples:
                success:
                  value:
                    success: true
                    post:
                      id: 555
                      title: Welcome to Base
                      created_at: "2026-04-29T15:30:00Z"
                      updated_at: "2026-04-29T15:30:00Z"
                      channel_id: 8
                      user_id: 7
                      user:
                        id: 7
                        first_name: John
                        last_name: Doe
                        email: john@example.com
                      content: Hello world
                      url: https://www.decilehub.com/base/8/posts/555
        "400":
          description: missing required parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missing_params:
                  value:
                    error:
                      code: bad_request
                      message: "Missing required parameters: channel_id, content"
                      field: channel_id
        "401":
          description: Unauthorized
        "403":
          description: Forbidden — token lacks Base write access, or user cannot post to channel
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: channel not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: validation failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                validation_failed:
                  value:
                    error:
                      code: validation_failed
                      message: Validation failed
                      details:
                        - "Title can't be blank"
  "/api/v1/base/posts/{id}":
    get:
      operationId: get_base_post
      x-agent-tool: true
      summary: Show a Decile Base post
      tags:
        - Base
      description:
        Returns a single Base post with its full thread of replies (ordered
        by creation time). Available to any API token with Base access; the
        acting user must also be authorized to view the post.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Base post ID
          schema:
            type: integer
      responses:
        "200":
          description: post returned
          content:
            application/json:
              examples:
                success:
                  value:
                    post:
                      id: 555
                      title: Welcome to Base
                      created_at: "2026-04-29T15:30:00Z"
                      updated_at: "2026-04-29T15:30:00Z"
                      channel_id: 8
                      user_id: 7
                      content: Hello world
                      url: https://www.decilehub.com/base/8/posts/555
                      user:
                        id: 7
                        first_name: John
                        last_name: Doe
                        email: john@example.com
                      replies:
                        - id: 9001
                          created_at: "2026-04-29T16:00:00Z"
                          updated_at: "2026-04-29T16:00:00Z"
                          base_post_id: 555
                          user_id: 8
                          parent_reply_id:
                          content: Thanks for the welcome!
                          user:
                            id: 8
                            first_name: Jane
                            last_name: Smith
                            email: jane@example.com
        "401":
          description: Unauthorized
        "403":
          description: Forbidden — token lacks Base access, or user cannot view post
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: post not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/base/replies":
    post:
      operationId: create_base_reply
      x-agent-tool: true
      summary: Create a Decile Base reply
      tags:
        - Base
      description:
        Creates a reply to an existing Base post. The `content` is parsed
        as Markdown and stored as rich text. Available to any API token with
        Base access; the acting user must also be authorized to view the parent
        post. The reply is always attributed to the user who owns the API
        token; impersonation is not supported.
      security:
        - api_key: []
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - reply
              properties:
                reply:
                  type: object
                  required:
                    - base_post_id
                    - content
                  properties:
                    base_post_id:
                      type: integer
                      description: ID of the parent Base post
                    content:
                      type: string
                      description: Reply body in Markdown
      responses:
        "201":
          description: reply created
          content:
            application/json:
              examples:
                success:
                  value:
                    success: true
                    reply:
                      id: 9001
                      created_at: "2026-04-29T16:00:00Z"
                      updated_at: "2026-04-29T16:00:00Z"
                      base_post_id: 555
                      user_id: 8
                      content: Thanks for the welcome!
                      user:
                        id: 8
                        first_name: Jane
                        last_name: Smith
                        email: jane@example.com
                      url: https://www.decilehub.com/base/8/posts/555#reply_9001
        "400":
          description: missing required parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missing_params:
                  value:
                    error:
                      code: bad_request
                      message: "Missing required parameters: base_post_id, content"
                      field: base_post_id
        "401":
          description: Unauthorized
        "403":
          description: Forbidden — token lacks Base write access, or user cannot reply
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: parent post not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: validation failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                validation_failed:
                  value:
                    error:
                      code: validation_failed
                      message: Validation failed
                      details:
                        - "Content can't be blank"
  "/api/v1/base/search":
    post:
      operationId: search_base
      x-agent-tool: true
      summary: Search Decile Base posts and articles
      tags:
        - Base
      description:
        Full-text search across Base posts (and Decile knowledge-base
        articles) via OpenSearch. Available to any API token with Base access.
        Searches the user's standard accessible channels and their child
        channels. Article results are suppressed when `channel_ids` is
        supplied.
      security:
        - api_key: []
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - search_term
              properties:
                search_term:
                  type: string
                  description: Free-text query
                channel_ids:
                  description: Optional list of channel IDs to restrict the search
                  oneOf:
                    - type: array
                      items:
                        type: integer
                    - type: string
                      description: Comma-separated channel IDs
                  example: [8, 12]
                channel_type:
                  type: string
                  description: Restrict to a single Base channel type
                  enum:
                    - question
                    - conversation
                authority:
                  type: string
                  description: Optional OpenSearch authority filter for article ranking
                article_limit:
                  type: integer
                  description: Maximum number of article results (default 5)
                  default: 5
                post_limit:
                  type: integer
                  description: Maximum number of post results (default 5)
                  default: 5
      responses:
        "200":
          description: search results returned
          content:
            application/json:
              examples:
                success:
                  value:
                    articles:
                      - id: 42
                        title: Getting started with Decile
                        url: https://help.decilehub.com/articles/42
                        score: 12.4
                    posts:
                      - id: 555
                        title: Welcome to Base
                        channel_id: 8
                        url: https://www.decilehub.com/base/8/posts/555
                        score: 9.1
        "400":
          description: missing search_term
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missing_search_term:
                  value:
                    error:
                      code: bad_request
                      message: search_term is required
                      field: search_term
        "401":
          description: Unauthorized
        "403":
          description: Forbidden — token lacks Base write access
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  "/api/v1/base/attachments/{id}":
    get:
      operationId: download_base_attachment
      x-agent-tool: true
      summary: Download a Decile Base attachment
      tags:
        - Base
      description:
        Downloads an Active Storage blob attached to a Base post or reply.
        The `id` is the signed blob ID returned by other Base endpoints (see
        the `attachments` array on inbox items). Available to any API token
        with Base access; the acting user must additionally be authorized to view at
        least one parent Base record the blob is attached to. Files larger than 50 MB respond
        with a 302 redirect to a short-lived (5 minute) pre-signed storage
        URL; smaller files are streamed inline as
        `application/octet-stream` with a `Content-Disposition` of `attachment`.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Signed Active Storage blob ID
          schema:
            type: string
      responses:
        "200":
          description: file streamed inline (blobs ≤ 50 MB)
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "302":
          description: redirect to pre-signed download URL (blobs > 50 MB)
          headers:
            Location:
              description: Short-lived (5 minute) storage URL
              schema:
                type: string
                format: uri
        "401":
          description: Unauthorized
        "403":
          description: Forbidden — token lacks Base access, or user cannot access blob
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: blob not found, signature invalid/expired, or file missing in storage
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                invalid_signature:
                  value:
                    error:
                      code: not_found
                      message: Invalid or expired attachment ID
                file_missing:
                  value:
                    error:
                      code: not_found
                      message: File not found in storage
  "/api/v1/email_templates":
    get:
      operationId: list_email_templates
      x-agent-tool: true
      summary: List email templates for the account
      tags:
        - Emails
      description: |
        Returns email templates visible to the authenticated account, ordered
        by id ascending and paginated 50 per page (0-indexed `page` param).
        Optionally filter by `pipeline_id` to restrict to templates authored
        on a specific pipeline. Use this to discover the `email_template_id`
        you'll pass to `POST /api/v1/emails/preview`,
        `POST /api/v1/emails`, or as a `send_email` action `data` override.

        Each list row carries enough to render a picker (name, pipeline,
        attachment count, admin/internal-only flags); use
        `GET /api/v1/email_templates/{id}` for the full subject/body and
        variable schema.
      security:
        - api_key: []
      parameters:
        - name: pipeline_id
          in: query
          description: Restrict to templates authored on this pipeline.
          schema:
            type: string
        - name: page
          in: query
          description: Page number (0-indexed). Page size is 50.
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: ok
          content:
            application/json:
              examples:
                success:
                  value:
                    data:
                      - id: "320"
                        name: Welcome
                        pipeline_id: "43"
                        pipeline_name: Investors
                        decile_admin_only_send: false
                        to_internal_actor_only: false
                        attachment_count: 1
                      - id: "321"
                        name: Follow-up
                        pipeline_id: "43"
                        pipeline_name: Investors
                        decile_admin_only_send: false
                        to_internal_actor_only: false
                        attachment_count: 0
                    pagination:
                      total_count: 2
                      current_page: 0
                      total_pages: 1
        "401":
          description: unauthorized (missing/invalid API token)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/email_templates/{id}":
    get:
      operationId: get_email_template
      x-agent-tool: true
      summary: Retrieve a single email template
      tags:
        - Emails
      description: |
        Returns the full template — index fields plus rendered (HTML)
        `rich_subject` / `body`, the resolved `variables` schema (each entry
        carries `name` and a `required` flag — `custom.*` variables are the
        ones callers must supply via `custom_substituted_variables` when
        previewing/sending), and any `attachments` (id, filename, byte
        size). Cross-account ids return 404.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Email template id.
          schema:
            type: string
      responses:
        "200":
          description: ok
          content:
            application/json:
              examples:
                success:
                  value:
                    id: "320"
                    name: Welcome
                    pipeline_id: "43"
                    pipeline_name: Investors
                    decile_admin_only_send: false
                    to_internal_actor_only: false
                    attachment_count: 1
                    rich_subject: "<div>Welcome to {{Pipeline.name}}</div>"
                    body: "<div>Hi {{Person.first_name}}, please supply {{custom.greeting}}.</div>"
                    variables:
                      - name: "Person.first_name"
                        required: false
                      - name: "Pipeline.name"
                        required: false
                      - name: "custom.greeting"
                        required: true
                    attachments:
                      - id: "88"
                        filename: "welcome.pdf"
                        byte_size: 42118
        "401":
          description: unauthorized (missing/invalid API token)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "404":
          description: Template not found in the token's account.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
              examples:
                not_found:
                  value:
                    error:
                      code: not_found
                      message: Email template not found
  "/api/v1/emails/preview":
    post:
      operationId: preview_email
      x-agent-tool: true
      summary: Preview a templated or freeform email before sending
      tags:
        - Emails
      description: |
        Resolves the target recipient, renders the chosen template (or
        freeform subject/body), substitutes variables, and reports
        unresolved variables / blocker warnings — without sending. Use this
        to surface the would-be email to a human (or agent) for
        confirmation before calling `POST /api/v1/emails`.

        **Target resolution: pass exactly one of**
        `pipeline_prospect_id`, `person_id`, `organization_id`, or
        `to_email`. Zero or multiple targets returns 422 (`missing_target`
        / `multiple_targets`). For `person_id` / `organization_id` /
        `to_email` targets, pipeline resolves in priority order:
        `email_template_id`'s pipeline → explicit `pipeline_id` →
        account default pipeline (connector → investor).
        Pass `pipeline_id` explicitly when the template is absent and
        you need the variable / actor scope to come from a specific
        pipeline. For `pipeline_prospect_id` the pipeline is derived
        from the prospect, and an explicit `pipeline_id` that doesn't
        match returns 422 `mismatched_pipeline`.

        **`cc` / `bcc`** — array of: raw email strings
        (`"alice@example.com"`), `{"person_id": "..."}` (account Person →
        email), or `{"internal_user_id": <numeric_id>}` (account user →
        email). Anything else returns 422 `invalid_recipient_format`.

        **Response envelope:** `recipient` (resolved person/email),
        `from` (the API token's user), `cc` / `bcc` (normalized email
        strings), `rendered` (subject + body html/text), `template`
        (id/name/pipeline/admin-only), `variables`
        (resolved / unresolved with reason), `attachments` (template +
        prospect-auto + signed-id uploads, tagged by `source`), `warnings`
        (blocker codes — `recipient_missing_email`,
        `decile_admin_only_template`, etc.), and `ready_to_send` (true
        only if no blockers and no unresolved variables).

        **Cross-account targets** (a `person_id` / `organization_id` /
        `pipeline_prospect_id` not visible to the token's account) return
        404.
      security:
        - api_key: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                pipeline_prospect_id:
                  type: string
                  description: Target an existing pipeline prospect (pipeline derived from prospect).
                person_id:
                  type: string
                  description: Target a Person; requires `pipeline_id`.
                organization_id:
                  type: string
                  description: Target an Organization; requires `pipeline_id`.
                to_email:
                  type: string
                  description: Target a raw email address; requires `pipeline_id`.
                pipeline_id:
                  type: string
                  description: Pipeline context for rendering. Required for non-prospect targets.
                email_template_id:
                  type: string
                  description: Template to render. Omit to render freeform `rich_subject` / `body`.
                rich_subject:
                  type: string
                  description: HTML subject override (also used freeform when no template).
                body:
                  type: string
                  description: HTML body override (also used freeform when no template).
                cc:
                  type: array
                  description: |
                    cc recipients. Each entry is one of: raw email string,
                    `{person_id: "..."}`, or `{internal_user_id: <id>}`.
                  items:
                    oneOf:
                      - type: string
                      - type: object
                        properties:
                          person_id:
                            type: string
                      - type: object
                        properties:
                          internal_user_id:
                            type: integer
                bcc:
                  type: array
                  description: Same shape as `cc`.
                  items:
                    oneOf:
                      - type: string
                      - type: object
                        properties:
                          person_id:
                            type: string
                      - type: object
                        properties:
                          internal_user_id:
                            type: integer
                attachments_attributes:
                  type: object
                  description: Mirror of the UI attachment-attributes form payload.
                  additionalProperties: true
                new_attachment_signed_ids:
                  type: array
                  description: ActiveStorage signed blob ids for new uploads to attach.
                  items:
                    type: string
                custom_substituted_variables:
                  type: object
                  description: Map of `custom.<name>` → value for unsupplied template variables.
                  additionalProperties:
                    type: string
                email_from_actor:
                  type: string
                  description: From-actor URI override (e.g. `user:42`).
                scheduled_at:
                  type: string
                  format: date-time
                  description: ISO 8601 schedule time (informational at preview time).
            examples:
              prospect_target:
                summary: Preview a template against an existing prospect
                value:
                  pipeline_prospect_id: "183"
                  email_template_id: "320"
                  custom_substituted_variables:
                    "custom.greeting": "Hello"
              person_target_with_cc:
                summary: Preview against a Person with a cc list
                value:
                  pipeline_id: "43nOmK6a"
                  person_id: "1042"
                  email_template_id: "320"
                  cc:
                    - "ops@example.com"
                    - person_id: "1043"
      responses:
        "200":
          description: Preview envelope.
          content:
            application/json:
              examples:
                ok:
                  value:
                    recipient:
                      person_id: "5512"
                      name: "Jane Investor"
                      email: jane@example.com
                      missing_email: false
                    from:
                      actor_uri: "user:42"
                      display: "Houston Searcy <houston@decilegroup.com>"
                    cc:
                      - ops@example.com
                    bcc: []
                    rendered:
                      subject: "Welcome to Acme Fund I"
                      body_html: "<p>Hi Jane, thanks for joining.</p>"
                      body_text: "Hi Jane, thanks for joining."
                    template:
                      id: "320"
                      name: "Welcome"
                      pipeline_id: "43nOmK6a"
                      decile_admin_only: false
                    variables:
                      resolved:
                        - name: "Person.first_name"
                          value: "Jane"
                      unresolved: []
                    attachments:
                      - id: "88"
                        filename: "welcome.pdf"
                        source: template
                    warnings: []
                    ready_to_send: true
        "401":
          description: unauthorized (missing/invalid API token)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "404":
          description: Target person / organization / prospect not in the token's account.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: |
            Target resolution failed (`missing_target`, `multiple_targets`,
            `mismatched_pipeline`) or a cc/bcc entry was malformed
            (`invalid_recipient_format`).
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
              examples:
                missing_target:
                  value:
                    error:
                      code: missing_target
                      message: "Specify one of pipeline_prospect_id, person_id, organization_id, to_email."
                multiple_targets:
                  value:
                    error:
                      code: multiple_targets
                      message: "Specify exactly one of: pipeline_prospect_id, person_id, organization_id, to_email."
                mismatched_pipeline:
                  value:
                    error:
                      code: mismatched_pipeline
                      message: "pipeline_id does not match the pipeline of pipeline_prospect_id."
                invalid_recipient_format:
                  value:
                    error:
                      code: invalid_recipient_format
                      message: "Recipient entry must be a string email, {person_id}, or {internal_user_id}."
  "/api/v1/emails":
    post:
      operationId: send_email
      x-agent-tool: true
      summary: Compose and send (or schedule, or test-send) an email
      tags:
        - Emails
      description: |
        Sends the previewed email. Request body is identical to
        `POST /api/v1/emails/preview` PLUS a required `confirm: true`
        acknowledgement, plus an optional `test_email_to_addresses` array
        for test-sends.

        **Two-step flow.** Always call `POST /api/v1/emails/preview` first
        and surface the envelope (especially `warnings`, `variables.unresolved`,
        `recipient.missing_email`) to a human or agent. Only call this
        endpoint once the preview's `ready_to_send` is true (or after
        supplying any missing `custom_substituted_variables`). Submitting
        without `confirm: true` returns 422 `confirmation_required` — by
        design, to prevent accidental sends.

        **Send modes** (`status` in response):
        - `sent` — default; dispatched immediately. `delivered_at` is the
          `PipelineEmail.created_at` ISO 8601.
        - `scheduled` — set when `scheduled_at` is supplied. `scheduled_at`
          in the response echoes the scheduled time.
        - `test_sent` — set when `test_email_to_addresses` is non-empty.
          The recipient is unchanged but the actual delivery goes to the
          test addresses; the prospect record is **not** persisted as a
          side effect (the synthesized-recipient persistence step is
          skipped). Use for sanity-checks during template authoring.

        **Server-side blockers** (before the send is attempted) surface
        as 422 with the matching preview-blocker code:
        `unresolved_variables`, `recipient_missing_email`,
        `decile_admin_only_template`. All preview-time errors
        (`missing_target`, `multiple_targets`, `mismatched_pipeline`,
        `invalid_recipient_format`) apply here too.
      security:
        - api_key: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - confirm
              properties:
                confirm:
                  type: boolean
                  description: |
                    Must be `true` (or the string `"true"`, `1`, `"1"`) to
                    acknowledge an intentional send. Missing/falsey →
                    422 `confirmation_required`.
                test_email_to_addresses:
                  type: array
                  description: |
                    If non-empty, send-as-test to these literal email
                    addresses instead of the resolved recipient.
                    Response `status` becomes `test_sent` and no prospect
                    record is persisted as a side effect.
                  items:
                    type: string
                pipeline_prospect_id:
                  type: string
                person_id:
                  type: string
                organization_id:
                  type: string
                to_email:
                  type: string
                pipeline_id:
                  type: string
                email_template_id:
                  type: string
                rich_subject:
                  type: string
                body:
                  type: string
                cc:
                  type: array
                  description: Same shape as `POST /api/v1/emails/preview` cc.
                  items:
                    oneOf:
                      - type: string
                      - type: object
                        properties:
                          person_id:
                            type: string
                      - type: object
                        properties:
                          internal_user_id:
                            type: integer
                bcc:
                  type: array
                  description: Same shape as `cc`.
                  items:
                    oneOf:
                      - type: string
                      - type: object
                        properties:
                          person_id:
                            type: string
                      - type: object
                        properties:
                          internal_user_id:
                            type: integer
                attachments_attributes:
                  type: object
                  additionalProperties: true
                new_attachment_signed_ids:
                  type: array
                  items:
                    type: string
                custom_substituted_variables:
                  type: object
                  additionalProperties:
                    type: string
                email_from_actor:
                  type: string
                scheduled_at:
                  type: string
                  format: date-time
            examples:
              send_to_prospect:
                summary: Send a template immediately to an existing prospect
                value:
                  pipeline_prospect_id: "183"
                  email_template_id: "320"
                  confirm: true
              schedule_to_person:
                summary: Schedule a template to a Person 1 hour from now
                value:
                  pipeline_id: "43nOmK6a"
                  person_id: "1042"
                  email_template_id: "320"
                  scheduled_at: "2026-05-11T16:00:00Z"
                  confirm: true
              test_send:
                summary: Test-send to two literal addresses
                value:
                  pipeline_prospect_id: "183"
                  email_template_id: "320"
                  confirm: true
                  test_email_to_addresses:
                    - "qa@example.com"
                    - "houston@decilegroup.com"
      responses:
        "200":
          description: ok
          content:
            application/json:
              examples:
                sent:
                  summary: Sent immediately
                  value:
                    pipeline_email_id: "9921"
                    status: sent
                    recipient:
                      person_id: "5512"
                      name: "Jane Investor"
                      email: jane@example.com
                      missing_email: false
                    rendered:
                      subject: "Welcome to Acme Fund I"
                      body_html: "<p>Hi Jane, thanks for joining.</p>"
                      body_text: "Hi Jane, thanks for joining."
                    delivered_at: "2026-05-11T15:01:22Z"
                    scheduled_at: null
                scheduled:
                  summary: Scheduled for later
                  value:
                    pipeline_email_id: "9922"
                    status: scheduled
                    recipient:
                      person_id: "5512"
                      name: "Jane Investor"
                      email: jane@example.com
                      missing_email: false
                    rendered:
                      subject: "Welcome to Acme Fund I"
                      body_html: "<p>Hi Jane, thanks for joining.</p>"
                      body_text: "Hi Jane, thanks for joining."
                    delivered_at: null
                    scheduled_at: "2026-05-11T16:00:00Z"
                test_sent:
                  summary: Test-sent to literal addresses
                  value:
                    pipeline_email_id: null
                    status: test_sent
                    recipient:
                      person_id: "5512"
                      name: "Jane Investor"
                      email: jane@example.com
                      missing_email: false
                    rendered:
                      subject: "Welcome to Acme Fund I"
                      body_html: "<p>Hi Jane, thanks for joining.</p>"
                      body_text: "Hi Jane, thanks for joining."
                    delivered_at: null
                    scheduled_at: null
        "401":
          description: unauthorized (missing/invalid API token)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "404":
          description: Target person / organization / prospect not in the token's account.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "422":
          description: |
            `confirmation_required` (missing `confirm: true`),
            `unresolved_variables`, `recipient_missing_email`,
            `decile_admin_only_template`, plus all preview-time codes
            (`missing_target`, `multiple_targets`, `mismatched_pipeline`,
            `invalid_recipient_format`).
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
              examples:
                confirmation_required:
                  value:
                    error:
                      code: confirmation_required
                      message: "Pass `confirm: true` to acknowledge that you intend to send this email."
                unresolved_variables:
                  value:
                    error:
                      code: unresolved_variables
                      message: "Template has unresolved variables; supply them via custom_substituted_variables and retry."
                recipient_missing_email:
                  value:
                    error:
                      code: recipient_missing_email
                      message: "Recipient prospect has no email address."
                decile_admin_only_template:
                  value:
                    error:
                      code: decile_admin_only_template
                      message: "This template can only be sent by Decile admins."
  "/api/v1/variables":
    get:
      summary: List firm variables
      tags:
        - Variables
      description: |
        Returns the variable picker for a pipeline + the authenticated
        user. Mirrors exactly what the user sees in the email composer's
        variable list — same per-user policy filtering.

        The `display_format` field is the merge-tag form (e.g.
        `email.fund_thesis`, `sender.name`) that gets wrapped in `{{...}}`
        inside an email template body. `select_options` is only present
        when `format_name == "Select (Dropdown)"`. The `id` is opaque —
        treat it as a string and pass it back to
        `GET /api/v1/variables/{id}` to look up a single row.
      security:
        - api_key: []
      parameters:
        - name: pipeline_id
          in: query
          description: Pipeline id (obscure or numeric). Required — variables are scoped per pipeline and per user.
          required: true
          schema:
            type: string
        - name: page
          in: query
          description: 0-indexed page number. Page size is 50.
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: Variables found
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/variable"
                  pagination:
                    type: object
                    properties:
                      total_count:
                        type: integer
                      current_page:
                        type: integer
                      total_pages:
                        type: integer
        "400":
          description: Bad request (pipeline_id missing)
        "401":
          description: Unauthorized
        "404":
          description: Pipeline not found
  "/api/v1/variables/{id}":
    get:
      summary: Show a single variable
      tags:
        - Variables
      description: |
        Returns a single variable from the pipeline's picker for the
        authenticated user. The `id` path parameter is the value returned
        by `GET /api/v1/variables`; treat it as opaque. Unknown ids,
        variables not surfaced in the user's picker, and variables not
        accessible to the authenticated account return 404.
      security:
        - api_key: []
      parameters:
        - name: id
          in: path
          required: true
          description: Variable id as returned by the list endpoint.
          schema:
            type: string
        - name: pipeline_id
          in: query
          required: true
          description: Pipeline id (obscure or numeric). Required — variables are scoped per pipeline and per user.
          schema:
            type: string
      responses:
        "200":
          description: Variable found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/variable"
        "400":
          description: Bad request (pipeline_id missing)
        "401":
          description: Unauthorized
        "404":
          description: Not found
  "/api/v1/agent_platform/file_search":
    post:
      tags:
        - agent_platform
      summary: Semantic search across the caller's account corpus
      description: |
        Returns ranked chunks from the caller account's data room + any
        in-message conversation-scoped chunks the caller can read.
        Optional conversation_id filter restricts results to that
        conversation (caller must have access to it).
      security:
        - api_key: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - q
              properties:
                q:
                  type: string
                  maxLength: 2000
                conversation_id:
                  type: integer
                attachment_ids:
                  type: array
                  items:
                    type: integer
                limit:
                  type: integer
                  minimum: 1
                  maximum: 50
                  default: 25
      responses:
        "200":
          description: Matches array
        "400":
          description: Input validation failed
        "401":
          description: Missing or invalid ApiToken
        "403":
          description: Caller lacks access to the requested conversation_id filter
servers:
  - url: "{protocol}://{host}"
    variables:
      protocol:
        default: https
      host:
        default: www.decilehub.com
components:
  securitySchemes:
    api_key:
      type: apiKey
      name: Authorization
      in: header
  schemas:
    attached_image:
      type: object
      nullable: true
      description: >
        An attached image (Person picture / Organization logo). Null when no
        image is attached. `url` is a signed ActiveStorage download link to the
        original file.
      properties:
        url:
          type: string
          description: Absolute, signed download URL for the original image.
        content_type:
          type: string
          example: image/png
        byte_size:
          type: integer
        filename:
          type: string
    PortfolioCompany:
      type: object
      description: A fund's stake in a single underlying organization.
      properties:
        id:
          type: integer
        name:
          type: string
          description: Display name of the underlying organization.
        organization_id:
          type: integer
        firm_admin_fund_details_id:
          type: integer
          description: FK to firm_admin_fund_details.id — the fund holding this company.
        entity_type:
          type: string
          nullable: true
        initial_investment_date:
          type: string
          format: date
          nullable: true
        investment_thesis:
          type: string
          nullable: true
        reserve_consumed:
          type: string
          nullable: true
          description: Decimal as string. USD already deployed from the fund's reserves.
        board_representation:
          type: object
          description: JSONB bag of board seat details. Keys may include board_seats, observer_seats, notes.
        exit_strategy:
          type: object
          description: JSONB bag of free-form exit strategy notes. Keys may include notes, target_exit_year, exit_type.
        legal_name:
          type: string
          nullable: true
          description: Legal entity name.
        display_legal_name:
          type: string
          nullable: true
          description: Legal name suitable for display.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PortfolioCompanyInvestment:
      type: object
      description: One investment tranche on a portfolio company, driven by an underlying accounting transaction.
      properties:
        id:
          type: integer
        firm_admin_portfolio_companies_id:
          type: integer
          description: FK to firm_admin_portfolio_companies.id — the parent portfolio company.
        firm_admin_accounting_transactions_id:
          type: integer
          nullable: true
          description: FK to firm_admin_accounting_transactions.id — the source transaction that created this investment.
        shares:
          type: integer
          nullable: true
          description: Integer share count. Legacy column; prefer shares_decimal for calculations.
        shares_decimal:
          type: string
          nullable: true
          description: Decimal share count as string (numeric 20,8). Primary share column since 2025-09-08.
        ignore_for_fmv_calc:
          type: boolean
          description: TRUE = exclude this tranche from FMV calculations.
        tax_born_on:
          type: string
          format: date
          nullable: true
          description: Date used for tax holding period start. Defaults to transaction_at when NULL.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PortfolioCompanyInvestmentDetail:
      type: object
      description: |
        A single investment tranche on a portfolio company with applied overrides resolved.
        Combines the `PortfolioCompanyInvestment` column allowlist with computed/resolved
        fields. Sparse `fields=` can address either group.
      properties:
        id:
          type: integer
        firm_admin_portfolio_companies_id:
          type: integer
          description: FK to firm_admin_portfolio_companies.id — the parent portfolio company.
        firm_admin_accounting_transactions_id:
          type: integer
          nullable: true
          description: FK to firm_admin_accounting_transactions.id — the source transaction that created this investment.
        shares:
          type: integer
          nullable: true
          description: Integer share count. Legacy column; prefer shares_decimal for calculations.
        shares_decimal:
          type: string
          nullable: true
          description: Decimal share count as string (numeric 20,8). Primary share column since 2025-09-08.
        ignore_for_fmv_calc:
          type: boolean
          description: TRUE = exclude this tranche from FMV calculations.
        tax_born_on:
          type: string
          format: date
          nullable: true
          description: Date used for tax holding period start. Defaults to transaction_at when NULL.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        effective_shares:
          type: string
          description: Resolved share count (shares_decimal if present, else shares, else 0).
        effective_tax_born_date:
          type: string
          format: date
          nullable: true
          description: Resolved tax-holding-period start (tax_born_on if present, else transaction_at::date).
        price_per_share:
          type: string
          description: Resolved per-share price. Uses legal_cost_per_share when set; otherwise transaction_amount / effective_shares.
        transaction_amount:
          type: string
          description: Resolved cost basis (USD). Applies write-off (returns -write_off_investment_new_cost) and convertible-debt conversion logic when applicable; otherwise sourced from the underlying accounting transaction.
        transaction_at:
          type: string
          format: date-time
          nullable: true
          description: Timestamp of the underlying accounting transaction.
    ActivityEntry:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
          nullable: true
          description: Cached display name of the subject at the time of the event.
        subject_type:
          type: string
          nullable: true
        subject_id:
          type: integer
          nullable: true
        entryable_type:
          type: string
          nullable: true
          description: Fully qualified Log::ActivityEntries::* class name.
        user_id:
          type: integer
          nullable: true
          description: Actor user id; null for automated/system events.
        account_id:
          type: integer
        origin:
          type: string
          nullable: true
          description: Per-request origin tag (api, mcp, internal_service, chrome_extension); null = web/UI/system.
        created_at:
          type: string
          format: date-time
        entryable:
          type: object
          nullable: true
          description: Present only when include=entryable is requested. Minimal type-tagged shape.
          properties:
            type:
              type: string
              description: Short entryable class name (e.g. Note, Audit, Gmail).
            action:
              type: string
              nullable: true
    ActivityEntryDetail:
      type: object
      description: Single activity entry returned by GET /api/v1/activity_entries/{id}, including the full entryable body.
      properties:
        id:
          type: integer
        name:
          type: string
          nullable: true
        subject_type:
          type: string
          nullable: true
        subject_id:
          type: integer
          nullable: true
        entryable_type:
          type: string
          nullable: true
        entryable_id:
          type: integer
          nullable: true
        user_id:
          type: integer
          nullable: true
        account_id:
          type: integer
        origin:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        entryable:
          type: object
          nullable: true
          description: |
            Full type-specific body. Always carries a `type` key (short entryable
            class name); remaining keys vary by type.
          properties:
            type:
              type: string
              description: Short entryable class name (e.g. Note, Audit, Gmail, AmlKyc).
            action:
              type: string
              nullable: true
              description: Present on Audit, CapitalAccount, Gmail, EmailDispatch, LpMatching, CapTable.
            item_type:
              type: string
              nullable: true
              description: Audit — class name of the changed record.
            item_id:
              type: integer
              nullable: true
              description: Audit — id of the changed record.
            changes:
              type: object
              nullable: true
              description: 'Audit — changed-attribute diff `{ "col": [old, new] }`, with redacted columns removed.'
            subject:
              type: string
              nullable: true
              description: Gmail / EmailDispatch — email subject line.
            from:
              type: string
              nullable: true
              description: Gmail / EmailDispatch — sender address.
            to:
              type: string
              nullable: true
              description: Gmail — recipient address(es).
            email_template:
              type: string
              nullable: true
              description: EmailDispatch — template name, or "Custom (without template)".
            body:
              type: string
              nullable: true
              description: Note body, or full Gmail / EmailDispatch email body.
            delivered_at:
              type: string
              format: date-time
              nullable: true
              description: EmailDispatch — first delivery event timestamp.
            opened_at:
              type: string
              format: date-time
              nullable: true
              description: EmailDispatch — first open event timestamp.
            recipients:
              type: object
              nullable: true
              description: EmailDispatch — resolved to/cc/bcc recipient lines.
            links_clicked:
              type: object
              nullable: true
              description: 'EmailDispatch — clicked URLs mapped to click counts.'
            attachments:
              type: array
              nullable: true
              items:
                type: string
              description: EmailDispatch — attachment filenames.
            description:
              type: string
              nullable: true
              description: CompletedTask description.
            data:
              type: object
              nullable: true
              description: Merge and PipelineActivity / CapTable payload.
            aml_status:
              type: string
              nullable: true
            kyc_status:
              type: string
              nullable: true
            summary:
              type: string
              nullable: true
              description: ChatSummary text.
    DealShare:
      type: object
      description: A deal share submitted by the caller's account into the network feed.
      properties:
        id:
          type: integer
        organization_id:
          type: integer
          nullable: true
        company_name:
          type: string
        the_bet:
          type: string
        referring_manager_name:
          type: string
        referring_manager_email:
          type: string
        founder:
          type: string
          nullable: true
        short_description:
          type: string
          nullable: true
        region:
          type: string
          nullable: true
        country_address:
          type: string
          nullable: true
        company_email:
          type: string
          nullable: true
        deck_url:
          type: string
          nullable: true
        round:
          type: string
          nullable: true
        anticipated_close_date:
          type: string
          format: date
          nullable: true
        why:
          type: string
          nullable: true
        investor_role:
          type: string
          nullable: true
        deal_source:
          type: string
          nullable: true
        traction_metrics:
          type: string
          nullable: true
        amount_raising:
          type: number
          nullable: true
        deal_terms:
          type: string
          nullable: true
        valuation:
          type: number
          nullable: true
        industry_list:
          type: array
          items:
            type: string
        stage:
          type: string
          description: Computed stage (e.g. "Unfiltered New Deals", "Expiring Deals", "Expired Deals").
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    DealShareList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/DealShare"
    DealShareDetail:
      type: object
      properties:
        data:
          $ref: "#/components/schemas/DealShare"
    PipelineMetricsSnapshot:
      type: object
      properties:
        total_prospects:
          type: integer
        by_stage:
          type: object
          additionalProperties:
            type: integer
        committed_amount:
          type: integer
        weighted_pipeline:
          type: integer
        as_of:
          type: string
          format: date-time
    note_envelope:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: integer
            body:
              type: string
            context:
              type: string
              nullable: true
            created_at:
              type: string
              format: date-time
            author:
              type: object
              properties:
                id:
                  type: integer
                first_name:
                  type: string
                last_name:
                  type: string
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              example: invalid_timezone
            message:
              type: string
              example: Invalid timezone 'Invalid/Timezone'
            field:
              type: string
              nullable: true
              example: event_when_timezone
            valid_values:
              type: array
              nullable: true
              items:
                type: string
              example:
                - America/New_York
                - Etc/UTC
            details:
              nullable: true
              oneOf:
                - type: array
                  items:
                    oneOf:
                      - type: string
                      - type: object
                - type: object
              example:
                - Event title can't be blank
    Entity:
      type: object
      description:
        Identity block returned by `GET /api/v1/entities` (list) and as the
        outer object on `GET /api/v1/entities/{id}` (show). Show responses
        also include a `details` key (see `EntityDetails`).
      required:
        - id
        - name
        - kind
        - is_active
      properties:
        id:
          type: integer
        name:
          type: string
          description: Display name (fund_details.entity_name when present, else organization name, else account name).
        kind:
          type: string
          enum:
            - fund
            - spv
            - holding
            - management_company
            - general_partnership
            - unknown
        currency:
          type: string
          nullable: true
          example: USD
        fiscal_year_end:
          type: string
          nullable: true
          description:
            "`MM-DD` (e.g. `12-31`) for fund-kind entities with a configured
            fiscal_year_end_month; null otherwise."
          example: "12-31"
        is_active:
          type: boolean
          description: "`true` when the entity is active / not hidden (`hidden_from_left_nav=false`)."
    EntityDetails:
      type: object
      description:
        Detail block on `GET /api/v1/entities/{id}`. `gp_carry`,
        `new_management_fee_system_cutover_date`, and
        `new_carry_system_cutover_date` are only present when the entity
        is a fund (kind=`fund`, `spv`, or `holding`).
      required:
        - currency
        - currency_symbol
        - fiscal_year_end
        - entity_name
        - logo_url
        - is_active
        - kind
      properties:
        currency:
          type: string
          nullable: true
        currency_symbol:
          type: string
          nullable: true
        fiscal_year_end:
          type: string
          nullable: true
          example: "12-31"
        entity_name:
          type: string
        logo_url:
          type: string
          nullable: true
        is_active:
          type: boolean
        kind:
          type: string
          enum:
            - fund
            - spv
            - holding
            - management_company
            - general_partnership
            - unknown
        gp_carry:
          type: number
          format: double
          nullable: true
          description: Fund-only. Float on a 0–1 scale (e.g. `0.20` means 20%); null when unset.
        new_management_fee_system_cutover_date:
          type: string
          format: date
          nullable: true
          description: Fund-only.
        new_carry_system_cutover_date:
          type: string
          format: date
          nullable: true
          description: Fund-only.
        thesis:
          type: string
          nullable: true
          description: Fund-only. Free-text investment thesis.
        target_fund_size:
          type: integer
          nullable: true
          description: Fund-only. Whole-dollar target raise; 0 when unset.
        description:
          type: string
          nullable: true
          description: Fund-only. Public-facing fund description.
        news:
          type: string
          nullable: true
          description: Fund-only. LP-facing news/announcements.
        deck_url:
          type: string
          nullable: true
          description: Fund-only. URL to the fund pitch deck.
        cap_table_link:
          type: string
          nullable: true
          description: Fund-only. URL to the external cap table.
        effective_date:
          type: string
          format: date
          nullable: true
          description: Fund-only. ISO date (YYYY-MM-DD).
        first_close_date:
          type: string
          format: date
          nullable: true
          description: Fund-only. ISO date (YYYY-MM-DD).
        fund_life_years:
          type: number
          format: double
          nullable: true
          description: Fund-only. Expected fund lifetime in years; 0.0 when unset.
    EntityShowResponse:
      description:
        Combined identity + details payload returned by `GET /api/v1/entities/{id}`,
        `POST /api/v1/entities`, and `PATCH /api/v1/entities/{id}`. The `calculations`
        block is only present on the GET endpoint when `?include=calculations` is
        supplied.
      allOf:
        - $ref: "#/components/schemas/Entity"
        - type: object
          required:
            - details
          properties:
            details:
              $ref: "#/components/schemas/EntityDetails"
            calculations:
              $ref: "#/components/schemas/EntityCalculations"
    EntityCreateRequest:
      type: object
      description: |
        Request body for `POST /api/v1/entities`. `kind` and `name` are required;
        all other fields are optional. Fund-only fields (everything below
        `is_active`) are accepted only when `kind` is `fund`, `spv`, or
        `holding`; sending them on a non-fund kind returns 422.
      required:
        - kind
        - name
      properties:
        kind:
          type: string
          enum:
            - fund
            - spv
            - holding
            - management_company
            - general_partnership
        name:
          type: string
          description: Display name. Used for the new Organization, the entity_name on the kind-specific details row, and downstream callback names (DataRoom, etc.).
        is_active:
          type: boolean
          default: true
          description: When false, the new entity is created with `hidden_from_left_nav=true`.
        fiscal_year_end:
          type: string
          description: "`MM-DD`; DD must be the canonical last day of MM (e.g. `12-31`, `03-31`, `02-28`). Fund kinds only."
          example: "12-31"
        gp_carry:
          type: string
          description: "Decimal carry rate stored on `fund_details.carry_percentage` (e.g. `'0.20'` = 20%). Fund kinds only."
          example: "0.20"
        thesis:
          type: string
          description: "Free-text investment thesis. Fund kinds only. Writing this field triggers async industry/region tagging."
        target_fund_size:
          type: integer
          minimum: 0
          description: "Whole-dollar target raise (0 = unset). Fund kinds only."
        description:
          type: string
          description: "Public-facing fund description. Fund kinds only."
        news:
          type: string
          description: "LP-facing news/announcements. Fund kinds only."
        deck_url:
          type: string
          description: "URL to the fund pitch deck. Fund kinds only."
        cap_table_link:
          type: string
          description: "URL to the external cap table. Fund kinds only."
        effective_date:
          type: string
          format: date
          description: "ISO date (YYYY-MM-DD). Fund kinds only."
        first_close_date:
          type: string
          format: date
          description: "ISO date (YYYY-MM-DD). Fund kinds only."
        fund_life_years:
          type: number
          minimum: 0
          description: "Expected fund lifetime in years. Fund kinds only."
    EntityUpdateRequest:
      type: object
      description: |
        Request body for `PATCH /api/v1/entities/{id}`. All fields are optional;
        only fields explicitly sent are applied. `kind`, if present, MUST match
        the entity's current kind (sending a differing value returns 422) — the
        entity kind is immutable post-create. Fund-only fields are rejected
        with 422 when the target entity is a `management_company` or
        `general_partnership`.
      properties:
        kind:
          type: string
          description: Must match the entity's current kind if supplied. Sending a different kind returns 422.
          enum:
            - fund
            - spv
            - holding
            - management_company
            - general_partnership
        name:
          type: string
          description: New display name. Applied to the entity's Organization and (for fund/management_company/general_partnership kinds) mirrored into the corresponding `*_details.entity_name`.
        is_active:
          type: boolean
          description: When set, flips `hidden_from_left_nav` (true => visible => hidden_from_left_nav=false).
        fiscal_year_end:
          type: string
          description: "`MM-DD`; same format rules as on create. Fund kinds only."
        gp_carry:
          type: string
          description: "Decimal carry rate. Fund kinds only."
        thesis:
          type: string
          description: "Fund kinds only."
        target_fund_size:
          type: integer
          minimum: 0
          description: "Fund kinds only."
        description:
          type: string
          description: "Fund kinds only."
        news:
          type: string
          description: "Fund kinds only."
        deck_url:
          type: string
          description: "Fund kinds only."
        cap_table_link:
          type: string
          description: "Fund kinds only."
        effective_date:
          type: string
          format: date
          description: "Fund kinds only."
        first_close_date:
          type: string
          format: date
          description: "Fund kinds only."
        fund_life_years:
          type: number
          minimum: 0
          description: "Fund kinds only."
    EntityCalculations:
      type: object
      description: |
        Fund-level dashboard values, present on `GET /api/v1/entities/{id}` only
        when `?include=calculations` is supplied. Numeric values are decimal strings
        (currency 2dp, ratios 4dp); investor counts are integers. Per-window
        field presence: `capital_activity.invested` and `fees.reserved_for_fees`
        appear only when `window=now`.
      required:
        - fund
        - commitments
        - capital_activity
        - fees
        - investable_capital
        - performance
        - portfolio
        - investors
        - meta
      properties:
        fund:
          $ref: "#/components/schemas/EntityCalculationsFund"
        commitments:
          $ref: "#/components/schemas/EntityCalculationsCommitments"
        capital_activity:
          $ref: "#/components/schemas/EntityCalculationsCapitalActivity"
        fees:
          $ref: "#/components/schemas/EntityCalculationsFees"
        investable_capital:
          type: string
          nullable: true
          description: 2dp decimal string. Window-specific (now vs lifetime).
        performance:
          $ref: "#/components/schemas/EntityCalculationsPerformance"
        portfolio:
          $ref: "#/components/schemas/EntityCalculationsPortfolio"
        investors:
          $ref: "#/components/schemas/EntityCalculationsInvestors"
        meta:
          $ref: "#/components/schemas/EntityCalculationsMeta"
    EntityCalculationsFund:
      type: object
      required:
        - target_fund_size
        - fund_size
        - general_partnership_name
      properties:
        target_fund_size:
          type: string
          nullable: true
          description: 2dp decimal string.
        fund_size:
          type: string
          nullable: true
          description: 2dp decimal string. Currently mirrors `target_fund_size` (falls back to 0 when unset).
        general_partnership_name:
          type: string
          nullable: true
          description: Display name of the parent general partnership entity, when present.
    EntityCalculationsCommitments:
      type: object
      required:
        - total_committed
        - lp_commitments
        - committed
      properties:
        total_committed:
          type: string
          nullable: true
          description: "`account_organization.fund_total_commitment`. 2dp decimal string."
        lp_commitments:
          type: string
          nullable: true
          description: "LP commitments, excluding the GP. 2dp decimal string."
        committed:
          type: string
          nullable: true
          description: |
            Window-specific. `window=now` returns committed capital as of
            today; `window=lifetime` returns the forecast committed capital.
            2dp decimal string.
    EntityCalculationsCapitalActivity:
      type: object
      description: |
        `invested` is present only when `window=now` — it is dropped entirely
        from the response (not returned as null) when `window=lifetime`.
      required:
        - called
        - contributed
        - paid_in
      properties:
        called:
          type: string
          nullable: true
          description: Total amount called across active capital accounts. 2dp decimal string.
        contributed:
          type: string
          nullable: true
          description: "Capital contributions. 2dp decimal string."
        paid_in:
          type: string
          nullable: true
          description: "Total paid in across capital accounts. 2dp decimal string."
        invested:
          type: string
          nullable: true
          description: Present only when `window=now`. Capital invested. 2dp decimal string.
    EntityCalculationsFees:
      type: object
      description: |
        `reserved_for_fees` is present only when `window=now` — it is dropped
        entirely from the response (not returned as null) when `window=lifetime`.
        `total_fees` is `management_fees + admin_fees + other_fees` for the chosen
        window (realized fee deductions only) and does NOT include `reserved_for_fees`
        even on the `now` window — those are a forward-looking reservation, not a
        realized deduction. Fees are emitted as negative values per project convention.
      required:
        - total_fees
        - management_fees
        - admin_fees
        - other_fees
      properties:
        total_fees:
          type: string
          nullable: true
          description: |
            `management_fees + admin_fees + other_fees` for the chosen window.
            Realized fee deductions only. `reserved_for_fees` is excluded.
            2dp decimal string.
        management_fees:
          type: string
          nullable: true
          description: Window-specific. 2dp decimal string.
        admin_fees:
          type: string
          nullable: true
          description: Window-specific. 2dp decimal string.
        other_fees:
          type: string
          nullable: true
          description: Window-specific. 2dp decimal string.
        reserved_for_fees:
          type: string
          nullable: true
          description: Present only when `window=now`. Capital reserved for fees. 2dp decimal string.
    EntityCalculationsPerformance:
      type: object
      required:
        - net_irr
      properties:
        net_irr:
          type: string
          nullable: true
          description: |
            Fund-wide net IRR. 4dp decimal
            string. Null when the underlying cash flows can't produce a finite rate.
    EntityCalculationsPortfolio:
      type: object
      required:
        - fmv
        - fmv_cap_adjusted
      properties:
        fmv:
          type: string
          nullable: true
          description: Portfolio fair-market value. 2dp decimal string.
        fmv_cap_adjusted:
          type: string
          nullable: true
          description: Cap-adjusted portfolio FMV. 2dp decimal string.
    EntityCalculationsInvestors:
      type: object
      required:
        - lp_total_count
        - lp_active_count
      properties:
        lp_total_count:
          type: integer
          description: Total LP capital-account count for the fund.
        lp_active_count:
          type: integer
          description: Count of LP capital accounts with `is_capital_account_enabled=true`.
    EntityCalculationsMeta:
      type: object
      required:
        - window
        - as_of
        - currency
      properties:
        window:
          type: string
          enum:
            - now
            - lifetime
          description: Echo of the requested window.
        as_of:
          type: string
          format: date
          description: ISO date the calculation values were computed against.
        currency:
          type: string
          description: Entity's currency, falling back to `USD`.
    JournalEntryCreateRequest:
      type: object
      description:
        Request body for creating a single journal entry (double-entry GL
        posting). `debit_code` and `credit_code` are GL account codes that must
        differ and both resolve to a known accounting account. `amount` must be
        numeric and greater than 0. `transaction_at` is an ISO date
        (YYYY-MM-DD). An optional counterparty is attached via the
        `counterparty_type` + `counterparty_id` pair.
      required:
        - debit_code
        - credit_code
        - amount
        - transaction_at
      properties:
        debit_code:
          type: string
          description: GL account code for the debit side (e.g. "1100"). Must differ from credit_code.
        credit_code:
          type: string
          description: GL account code for the credit side (e.g. "1000"). Must differ from debit_code.
        amount:
          type: string
          description: Transaction amount as a numeric string greater than 0.
        transaction_at:
          type: string
          format: date
          description: Effective date of the entry (ISO date YYYY-MM-DD).
        comment:
          type: string
          nullable: true
          description: Optional free-text note.
        counterparty_type:
          type: string
          nullable: true
          description: Optional polymorphic counterparty type (FirmAdmin::CapitalAccount, FirmAdmin::PortfolioCompany, Organization, Person). Requires counterparty_id.
        counterparty_id:
          type: integer
          nullable: true
          description: Optional counterparty id; only meaningful alongside counterparty_type.
    JournalEntryBulkCreateRequest:
      type: object
      description:
        Request body for atomically creating many journal entries under one
        entity. `journal_entries` holds 1 to 100 rows, each a full independent
        entry with the same shape and rules as JournalEntryCreateRequest. Every
        row is validated before any is created; if any row is invalid the whole
        batch is rejected and nothing is created.
      required:
        - journal_entries
      properties:
        journal_entries:
          type: array
          minItems: 1
          maxItems: 100
          description: 1 to 100 journal entry rows, validated all-or-nothing.
          items:
            $ref: "#/components/schemas/JournalEntryCreateRequest"
    JournalEntryBulkErrors:
      type: object
      description:
        Per-row validation failures for a bulk create. Each entry identifies the
        zero-based row `index` plus the offending `field` and a human-readable
        `message`. When present the batch created nothing.
      required:
        - errors
      properties:
        errors:
          type: array
          items:
            type: object
            required:
              - index
              - field
              - message
            properties:
              index:
                type: integer
                description: Zero-based index of the offending row in the request array.
              field:
                type: string
              message:
                type: string
    JournalEntry:
      type: object
      description:
        A double-entry general-ledger posting (FirmAdmin::AccountingTransaction)
        under an entity. `transaction_amount` is the absolute amount (always
        positive; direction is determined by the debit/credit account
        assignment). `counterparty` is null when the entry has no counterparty.
      required:
        - id
        - transaction_at
        - transaction_amount
        - reconciled
        - pending
        - debit
        - credit
      properties:
        id:
          type: integer
        transaction_at:
          type: string
          format: date-time
          nullable: true
        transaction_amount:
          type: string
          description: Absolute amount as a decimal string.
          nullable: true
        reconciled:
          type: boolean
          nullable: true
        pending:
          type: boolean
          nullable: true
        comment:
          type: string
          nullable: true
        debit:
          type: object
          nullable: true
          description: Debit-side ledger account.
          properties:
            code:
              type: string
            name:
              type: string
        credit:
          type: object
          nullable: true
          description: Credit-side ledger account.
          properties:
            code:
              type: string
            name:
              type: string
        counterparty:
          type: object
          nullable: true
          description: Polymorphic counterparty (CapitalAccount, PortfolioCompany, Organization, ...).
          properties:
            type:
              type: string
            id:
              type: integer
            name:
              type: string
              nullable: true
    CapitalAccountIdentity:
      type: object
      description:
        Identity block shared by the list (`GET /api/v1/entities/{entity_id}/capital_accounts`)
        and show (`GET /api/v1/capital_accounts/{id}`) responses. The calculations endpoints
        do NOT include this block — single-CA returns only the `commitments`/`period_activity`/
        `performance`/`meta` groups, and bulk-calc rows carry only `id` and `name` alongside
        the four calculation groups.
      required:
        - id
        - name
        - vehicle_type
        - partner_type
        - is_capital_account_enabled
        - is_gp_carry
      properties:
        id:
          type: integer
        name:
          type: string
          description: Capital account display name.
        vehicle_type:
          type: string
          nullable: true
        partner_type:
          type: string
          enum:
            - limited_partner
            - general_partner
        admitted_on:
          type: string
          format: date
          nullable: true
        lp_signature_at:
          type: string
          format: date-time
          nullable: true
        is_capital_account_enabled:
          type: boolean
        kyc_passed:
          type: boolean
          nullable: true
        aml_passed:
          type: boolean
          nullable: true
        is_gp_carry:
          type: boolean
        fund:
          type: object
          nullable: true
          properties:
            id:
              type: integer
            name:
              type: string
    Commitment:
      type: object
      required:
        - id
        - committed_capital
        - committed_at
      properties:
        id:
          type: integer
        committed_capital:
          type: integer
          format: int64
          description: Whole-currency amount (no decimals — column is `bigint`).
        committed_at:
          type: string
          format: date-time
          nullable: true
    PrimaryContact:
      type: object
      nullable: true
      description: Primary contact for the capital account; null when not set.
      properties:
        id:
          type: integer
        name:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
    CapitalAccountContact:
      type: object
      description: |
        A contact on a capital account. `contact_status_type` is the
        mailing/communication role; the boolean flags describe the legal
        relationship to the account (joint primary, spouse, trustee,
        entity owner/signatory, trust beneficiary). Multiple booleans can
        be true for the same person.
      required:
        - id
        - contact_status_type
        - is_primary_individual
        - is_spouse
        - is_trustee
        - is_trust_beneficiary
        - is_entity_owner
        - is_entity_signatory
      properties:
        id:
          type: integer
        name:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        title:
          type: string
          nullable: true
        contact_status_type:
          type: string
          enum:
            - none
            - primary
            - additional
        is_primary_individual:
          type: boolean
          nullable: true
        is_spouse:
          type: boolean
          nullable: true
        is_trustee:
          type: boolean
          nullable: true
        is_trust_beneficiary:
          type: boolean
          nullable: true
        is_entity_owner:
          type: boolean
          nullable: true
        is_entity_signatory:
          type: boolean
          nullable: true
    EntityRef:
      type: object
      nullable: true
      required:
        - id
        - name
        - kind
      properties:
        id:
          type: integer
        name:
          type: string
        kind:
          type: string
          enum:
            - fund
            - spv
            - holding
            - management_company
            - general_partnership
            - unknown
    AccountingAccount:
      type: object
      description: |
        A chart-of-accounts entry / GL account code, shared across the account.
        `code` drives debit/credit automation and follows standard GL ranges
        (1xxx=assets, 2xxx=liabilities, 3xxx=equity, 4xxx=revenue,
        6xxx/7xxx=expenses).
      required:
        - id
        - code
        - name
        - description
        - is_1099_eligible
      properties:
        id:
          type: integer
        code:
          type: string
          nullable: true
          description: Ledger account code, e.g. "1000" (bank), "1100" (investments), "6000" (mgmt fees).
        name:
          type: string
          nullable: true
          description: Human-readable account name, e.g. "Cash & Cash Equivalents".
        description:
          type: string
          nullable: true
          description: Optional long-form description of the account purpose.
        is_1099_eligible:
          type: boolean
          nullable: true
          description: True if transactions against this account are eligible for 1099 reporting.
    CapitalCallHeader:
      type: object
      description: |
        Header view of a capital call. `amount_being_called` is the sum of
        the per-LP amounts being called. `total_wired`, `total_outstanding`,
        and `percentage_funded` are derived from reconciled capital-call
        payment transactions within the call's
        `created_at..(closed_at || now)` window. `fund_total_commitment` is the
        sum of active commitments across the parent fund's capital accounts
        (constant across all rows on an index page, since a page is scoped to
        a single fund).
      required:
        - id
        - name
        - status
        - percentage_to_call
        - notes
        - pipeline_id
        - amount_being_called
        - total_wired
        - total_outstanding
        - percentage_funded
        - fund_total_commitment
        - created_at
        - closed_at
        - due_date
        - entity
      properties:
        id:
          type: integer
        name:
          type: string
        status:
          type: string
          enum:
            - draft
            - open
            - closed
        percentage_to_call:
          type: number
          format: float
        notes:
          type: string
          nullable: true
        pipeline_id:
          type: integer
          nullable: true
          description: Id of the capital-call communication pipeline. Nullable on draft calls created before the pipeline is provisioned.
        amount_being_called:
          type: string
          description: Decimal string sum across per-LP details.
        total_wired:
          type: string
          description: Decimal string sum of reconciled capital-call payment transactions whose `transaction_at` falls in `created_at..(closed_at || now)`.
        total_outstanding:
          type: string
          description: "`amount_being_called - total_wired` as a decimal string. Negative when LPs have overpaid in aggregate."
        percentage_funded:
          type: number
          format: float
          nullable: true
          description: "`total_wired / amount_being_called` as a float (0.0–1.0). Null when `amount_being_called` is zero."
        fund_total_commitment:
          type: string
          description: "Sum of `committed_capital` across active commitments for the parent fund. Decimal string."
        created_at:
          type: string
          format: date-time
        closed_at:
          type: string
          format: date-time
          nullable: true
        due_date:
          type: string
          format: date
          nullable: true
        entity:
          type: object
          nullable: true
          properties:
            id:
              type: integer
            name:
              type: string
    CapitalCallDetail:
      type: object
      description: Per-LP line item under a capital call.
      required:
        - id
        - capital_account
        - amount_being_called
        - committed_capital
        - wired_amount
        - wire_date
        - variance
        - payment_status
        - prospect_id
      properties:
        id:
          type: integer
        capital_account:
          type: object
          nullable: true
          properties:
            id:
              type: integer
            name:
              type: string
        amount_being_called:
          type: string
        committed_capital:
          type: string
        wired_amount:
          type: string
        wire_date:
          type: string
          format: date-time
          nullable: true
        variance:
          type: string
          description: wired_amount − amount_being_called (decimal string).
        payment_status:
          type: string
          enum:
            - fully_paid
            - underpaid
            - overpaid
            - unpaid
            - prepaid
            - over_commitment
            - any_variance
            - unknown
        prospect_id:
          type: integer
          nullable: true
          description: Id of this LP's prospect on the parent call's pipeline. Null when the prospect has been removed or never provisioned.
    CapitalAccountTransfer:
      type: object
      description:
        Transfer entry oriented at the requested capital account.
        `effective_at` is aliased from the underlying `transferred_at`
        column.
      required:
        - id
        - kind
        - from_capital_account_id
        - to_capital_account_id
        - amount
      properties:
        id:
          type: integer
        kind:
          type: string
          enum:
            - outbound
            - inbound
          description:
            "`outbound` when the requested CA is the
            `from_capital_account`; `inbound` otherwise."
        effective_at:
          type: string
          format: date-time
          description: Aliased from the underlying `transferred_at` column (`NOT NULL`).
        from_capital_account_id:
          type: integer
        to_capital_account_id:
          type: integer
        amount:
          type: integer
          format: int64
          description: Whole-currency amount (no decimals — column is `bigint`).
    PeriodCommitments:
      type: object
      description:
        Commitment + paid-in figures. All values are decimal strings;
        currency fields use 2 decimal places, ratio fields use 4. Nil
        values are returned as `null`.
      properties:
        committed_capital:
          type: string
          nullable: true
        total_committed_capital:
          type: string
          nullable: true
          description: Fund-wide aggregate (NOT scoped by `active`/`partner_type` filters in the bulk endpoint).
        lp_committed_capital:
          type: string
          nullable: true
        gp_committed_capital:
          type: string
          nullable: true
        percentage_of_total_commitments:
          type: string
          nullable: true
          description: Ratio (4 decimal places). Null in `totals`.
        percentage_of_lp_commitments:
          type: string
          nullable: true
          description: Ratio. Null in `totals`.
        percentage_of_funds_called:
          type: string
          nullable: true
          description: Ratio. Null in `totals`.
        amount_paid_in_for_capital_accounts:
          type: string
          nullable: true
        amount_paid_in_for_capital_accounts_percentage:
          type: string
          nullable: true
          description: Ratio. Null in `totals`.
        outstanding_capital_contribution:
          type: string
          nullable: true
        capital_call_receivable:
          type: string
          nullable: true
        capital_call_prepaid:
          type: string
          nullable: true
    PeriodActivity:
      type: object
      description:
        Per-period ledger movements. All values are decimal strings
        (currency, 2 decimal places); null when not applicable.
      properties:
        beginning_balance:
          type: string
          nullable: true
        capital_contribution:
          type: string
          nullable: true
        syndication_cost:
          type: string
          nullable: true
        management_fees:
          type: string
          nullable: true
        incentive_fees:
          type: string
          nullable: true
        net_operating_gain:
          type: string
          nullable: true
        late_interest:
          type: string
          nullable: true
        unrealized_gain:
          type: string
          nullable: true
        carried_interest_accrued:
          type: string
          nullable: true
        net_realized_gain:
          type: string
          nullable: true
        deemed_gain:
          type: string
          nullable: true
        distributions:
          type: string
          nullable: true
        in_kind_distributions:
          type: string
          nullable: true
        transfers:
          type: string
          nullable: true
        ending_balance:
          type: string
          nullable: true
    PeriodPerformance:
      type: object
      description:
        Performance ratios. Decimal strings (4 decimal places). In the
        bulk endpoint's `totals` block these are always `null` because
        per-CA ratios don't aggregate cleanly across capital accounts.
      properties:
        dpi:
          type: string
          nullable: true
        rvpi:
          type: string
          nullable: true
        tvpi:
          type: string
          nullable: true
    PeriodMeta:
      type: object
      required:
        - period_start
        - period_end
        - currency
        - management_fee_system
        - carry_system
        - cutover_dates
      properties:
        period_start:
          type: string
          format: date
        period_end:
          type: string
          format: date
        currency:
          type: string
          example: USD
        management_fee_system:
          type: string
          enum:
            - legacy
            - new
            - hybrid
          description:
            "`legacy` when the window ends before the management-fee
            cutover; `new` when it starts on/after; `hybrid` when it
            straddles. Funds without a cutover date are always `legacy`."
        carry_system:
          type: string
          enum:
            - legacy
            - new
            - hybrid
        cutover_dates:
          type: object
          required:
            - management_fee
            - carry
          properties:
            management_fee:
              type: string
              format: date
              nullable: true
            carry:
              type: string
              format: date
              nullable: true
    BulkCalculationsRow:
      type: object
      description:
        One row in the bulk-calculations response. Always has `id` and
        `name`. The `commitments`, `period_activity`, `performance`, and
        `meta` groups are present by default; supplying `?fields=` may
        drop entire groups from the response (a group with zero
        requested fields is omitted from both rows and totals).
      required:
        - id
        - name
      properties:
        id:
          type: integer
        name:
          type: string
        commitments:
          $ref: "#/components/schemas/PeriodCommitments"
        period_activity:
          $ref: "#/components/schemas/PeriodActivity"
        performance:
          $ref: "#/components/schemas/PeriodPerformance"
        meta:
          $ref: "#/components/schemas/PeriodMeta"
    BulkCalculationsTotals:
      type: object
      description:
        Fund-level totals across the filtered scope (NOT the current
        page). Per-CA ratio fields and the performance ratios (`dpi`,
        `rvpi`, `tvpi`) are always `null` in totals — per-account ratios
        do not sum into a fund-level figure. Currency fields are summed
        across the filtered scope. As with rows, supplying
        `?fields=` may drop entire groups from this object.
      properties:
        commitments:
          $ref: "#/components/schemas/PeriodCommitments"
        period_activity:
          $ref: "#/components/schemas/PeriodActivity"
        performance:
          $ref: "#/components/schemas/PeriodPerformance"
        meta:
          $ref: "#/components/schemas/PeriodMeta"
    AccessType:
      type: string
      enum:
        - public_access
        - private_access
    RsvpStatus:
      type: string
      enum:
        - "no"
        - "yes"
        - waiting_list
    TimeZone:
      type: string
      enum:
        - Africa/Algiers
        - Africa/Cairo
        - Africa/Casablanca
        - Africa/Harare
        - Africa/Johannesburg
        - Africa/Monrovia
        - Africa/Nairobi
        - America/Argentina/Buenos_Aires
        - America/Bogota
        - America/Caracas
        - America/Chicago
        - America/Chihuahua
        - America/Denver
        - America/Godthab
        - America/Guatemala
        - America/Guyana
        - America/Halifax
        - America/Indiana/Indianapolis
        - America/Juneau
        - America/La_Paz
        - America/Lima
        - America/Los_Angeles
        - America/Mazatlan
        - America/Mexico_City
        - America/Monterrey
        - America/Montevideo
        - America/New_York
        - America/Phoenix
        - America/Puerto_Rico
        - America/Regina
        - America/Santiago
        - America/Sao_Paulo
        - America/St_Johns
        - America/Tijuana
        - Asia/Almaty
        - Asia/Baghdad
        - Asia/Baku
        - Asia/Bangkok
        - Asia/Chongqing
        - Asia/Colombo
        - Asia/Dhaka
        - Asia/Hong_Kong
        - Asia/Irkutsk
        - Asia/Jakarta
        - Asia/Jerusalem
        - Asia/Kabul
        - Asia/Kamchatka
        - Asia/Karachi
        - Asia/Kathmandu
        - Asia/Kolkata
        - Asia/Krasnoyarsk
        - Asia/Kuala_Lumpur
        - Asia/Kuwait
        - Asia/Magadan
        - Asia/Muscat
        - Asia/Novosibirsk
        - Asia/Rangoon
        - Asia/Riyadh
        - Asia/Seoul
        - Asia/Shanghai
        - Asia/Singapore
        - Asia/Srednekolymsk
        - Asia/Taipei
        - Asia/Tashkent
        - Asia/Tbilisi
        - Asia/Tehran
        - Asia/Tokyo
        - Asia/Ulaanbaatar
        - Asia/Urumqi
        - Asia/Vladivostok
        - Asia/Yakutsk
        - Asia/Yekaterinburg
        - Asia/Yerevan
        - Atlantic/Azores
        - Atlantic/Cape_Verde
        - Atlantic/South_Georgia
        - Australia/Adelaide
        - Australia/Brisbane
        - Australia/Canberra
        - Australia/Darwin
        - Australia/Hobart
        - Australia/Melbourne
        - Australia/Perth
        - Australia/Sydney
        - Etc/GMT+12
        - Etc/UTC
        - Europe/Amsterdam
        - Europe/Athens
        - Europe/Belgrade
        - Europe/Berlin
        - Europe/Bratislava
        - Europe/Brussels
        - Europe/Bucharest
        - Europe/Budapest
        - Europe/Copenhagen
        - Europe/Dublin
        - Europe/Helsinki
        - Europe/Istanbul
        - Europe/Kaliningrad
        - Europe/Kiev
        - Europe/Lisbon
        - Europe/Ljubljana
        - Europe/London
        - Europe/Madrid
        - Europe/Minsk
        - Europe/Moscow
        - Europe/Paris
        - Europe/Prague
        - Europe/Riga
        - Europe/Rome
        - Europe/Samara
        - Europe/Sarajevo
        - Europe/Skopje
        - Europe/Sofia
        - Europe/Stockholm
        - Europe/Tallinn
        - Europe/Vienna
        - Europe/Vilnius
        - Europe/Volgograd
        - Europe/Warsaw
        - Europe/Zagreb
        - Europe/Zurich
        - Pacific/Apia
        - Pacific/Auckland
        - Pacific/Chatham
        - Pacific/Fakaofo
        - Pacific/Fiji
        - Pacific/Guadalcanal
        - Pacific/Guam
        - Pacific/Honolulu
        - Pacific/Majuro
        - Pacific/Midway
        - Pacific/Noumea
        - Pacific/Pago_Pago
        - Pacific/Port_Moresby
        - Pacific/Tongatapu
    associated_person:
      type: object
      properties:
        first_name:
          type: string
        middle_name:
          type: string
        last_name:
          type: string
        email:
          type: string
        phone:
          type: string
      required:
        - first_name
        - last_name
    address:
      type: object
      properties:
        street:
          type: string
        city:
          type: string
        state:
          type: string
        country:
          type: string
        zipcode:
          type: string
    financial_report_job:
      type: object
      properties:
        id:
          type: integer
        status:
          type: string
          enum:
            - queued
            - running
            - succeeded
            - failed
        created_at:
          type: string
          format: date-time
        started_at:
          type: string
          format: date-time
          nullable: true
        finished_at:
          type: string
          format: date-time
          nullable: true
        status_url:
          type: string
          format: uri
        error_message:
          type: string
          nullable: true
        files:
          type: array
          description:
            One combined file per job. Each entry lists the report types
            included as worksheets / sections inside that file.
          items:
            type: object
            properties:
              report_types:
                type: array
                items:
                  type: string
                  enum:
                    - balance_sheet
                    - income_statement
                    - schedule_of_investments
                    - statement_of_changes
              filename:
                type: string
              content_type:
                type: string
              byte_size:
                type: integer
              download_url:
                type: string
                format: uri
                nullable: true
    variable:
      type: object
      description: |
        A merge-tag variable usable in email templates (e.g.
        `{{fund_thesis}}`). The `id` is opaque — pass it back to
        `GET /api/v1/variables/{id}` to look up the row.
      properties:
        id:
          type: string
          description: Opaque variable id. Treat as a string.
        name:
          type: string
        context:
          type: string
          nullable: true
          description: |
            Namespace string (e.g. `email`, `global`, `person_data_points`,
            `organization_data_points`). May be `null`.
        description:
          type: string
          nullable: true
        value:
          type: string
          nullable: true
          description: |
            Literal substitution content for variables that carry one
            (e.g. the fund thesis text for `{{email.fund_thesis}}`).
            `null` when the variable's value is resolved dynamically at
            send time.
        format_name:
          type: string
          nullable: true
          description: |
            Human-readable format name (e.g. `String`, `Paragraph`,
            `Currency (US)`, `Date (US)`, `Select (Dropdown)`, `URL`).
            `null` for policy-protected variables whose format is not
            statically known.
        display_format:
          type: string
          description: |
            Merge-tag form (e.g. `email.fund_thesis`). Wrap in `{{...}}`
            to substitute in an email template body.
        select_options:
          type: array
          nullable: true
          description: |
            Present only when `format_name == "Select (Dropdown)"`. Either
            a flat array of strings (label == value) or an array of
            `[label, value]` pairs.
          items:
            oneOf:
              - type: string
              - type: array
                items:
                  type: string
      required:
        - id
        - name
        - context
        - format_name
        - display_format
    pagination:
      type: object
      properties:
        total_count:
          type: integer
        current_page:
          type: integer
        total_pages:
          type: integer
    Task:
      type: object
      description: A Hub Task with its lifecycle status, origin, and assignment.
      properties:
        id:
          type: integer
        title:
          type: string
        description:
          type: string
          nullable: true
        status:
          type: string
          enum:
            - open
            - in_progress
            - completed
            - cancelled
        origin:
          type: string
          enum:
            - agent_suspension
            - user
            - system
        due_date:
          type: string
          format: date
          nullable: true
        deep_link_url:
          type: string
          nullable: true
        assignee_id:
          type: integer
        created_by_user_id:
          type: integer
          nullable: true
        account_id:
          type: integer
        account_organization_id:
          type: integer
          nullable: true
        parent_task_id:
          type: integer
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - title
        - status
        - origin
        - assignee_id
        - account_id
