> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go.aiinsurance.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Record Payment

> Records a payment against the invoice — a gesture the server resolves into
per-line settlement MARKS, executing one `invoice.payment_recorded` action
per mark in one transaction and returning the CREATED MARK ROWS
(`paymentIds` + `createdPayments`), each mark the handle for
[removing it](/api-reference/financials/remove-payment).

**Two modes, exactly one per request.** Send either `payBalanceDue: true`
or an `allocations` array — both together, or neither, is a `400`.

  - **`payBalanceDue: true`** settles the whole document: one mark per
    OPEN line item at that line's full remaining, in the document's
    line-item order, BOTH directions at once. The invoice comes back
    `paid` with `balanceDueCents: 0`, and the gesture's net cash equals
    the `balanceDueCents` you asked to pay, by construction. Only the
    literal `true` is accepted (`payBalanceDue: false` is a `400`) — to
    pay less, name the lines.
  - **`allocations`** marks exactly the line items you name, for exactly
    the cents you name. Nothing is spread, split, or inferred: a line you
    do not name is untouched, and a line you name for less than its
    remaining stays open.

**Allocation amounts are in the LINE's frame.** `allocations[].amountCents`
settles its own line's remaining **toward zero**, so it carries the sign of
that remaining — this is NOT the oriented net-cash frame `balanceDueCents`
reads in. A payable-direction line with 8,000 remaining takes a mark of
`+8000`; a receivable-direction line with 3,000 remaining takes a mark of
`+3000` too (money in, but the line's own remaining is positive). Each
amount must be a nonzero integer, and a `lineItemId` may appear at most
once per request (both `400`s).

**Per-mark guards (`422`).** Every mark must name a line item on the
document (`422 UNKNOWN_ID`), be nonzero and carry its line's remaining's
sign — a mark against a settled line, or one running opposite its line's
remaining, is `422 INVALID_AMOUNT`, as is a `payBalanceDue` against a
document with nothing open — and its magnitude may not exceed its line's
remaining's (`422 AMOUNT_EXCEEDS_BALANCE_DUE`). The bounds are per line;
there is no document-scalar bound. The payment date must not be in the
future (`422 FUTURE_DATE`).

**Reserve erosion.** On an invoice in a reserved category,
`erodeReserves` is REQUIRED (`true` = the marks consume the linked
event's reserves; eroding requires an event link —
`422 ERODE_REQUIRES_EVENT`). On an operating-category invoice the field
must be omitted (`422 ERODE_RESERVES_MISMATCH` either way it is
misused). There is no headroom bound: the marks' oriented reserve effect
applies to the remaining reserve unbounded — the remaining reserve is a
SIGNED balance, and eroding past the estimate takes it below zero (the
books' statement that payments have outrun an estimate that still
stands), never a `422`.

**Approval gate (`422 NOT_APPROVED`).** Money may only post against an
APPROVED invoice (`approved: true` on the invoice row) — `paid ⇒
approved`. On an UNAPPROVED invoice the outcome depends on the calling
key's permissions, mirroring the in-app behaviour where a sufficiently
authorized user's payment auto-approves:

  - the key holds `company.payment:approve` → the invoice is APPROVED
    automatically as part of this request, in the same transaction and
    before the payment marks, and the payment posts. The response's
    invoice row comes back `approved: true`; the auto-approval is
    deliberately NOT listed in `journalIds` (that array is the payment
    gesture's marks, so an idempotent replay returns an identical body).
  - the key does NOT hold it → `422 NOT_APPROVED`. Approve the invoice
    first — via [approve](/api-reference/financials/approve-invoice) with a
    key that holds the permission, or in the app.

A draft is rejected as `422 INVOICE_DRAFT` regardless of approval: no
money may post against an unposted document.

**Concurrency + idempotency.** Requires `If-Match` (the invoice's current
`headJournalId`) and a client-minted `actionId`. The `actionId` anchors
the FIRST mark's journal action; sibling marks take server-minted ids in
the same transaction. An identical retry replays the original outcome —
the full `paymentIds` set included. Reusing an `actionId` with different
`allocations` is `409 ACTION_ID_REUSED`.

**Required permission:** `company.payment:update`




## OpenAPI

````yaml /openapi/generated-external-api.yaml post /api/v1/companies/{companyId}/financials/invoices/{invoiceId}/payments
openapi: 3.0.3
info:
  title: AI Insurance External API
  description: External API for AI Insurance platform
  version: 1.0.0
  contact:
    email: support@aiinsurance.io
servers:
  - url: https://go.aiinsurance.io
    description: Production
security:
  - ApiKeyAuth: []
paths:
  /api/v1/companies/{companyId}/financials/invoices/{invoiceId}/payments:
    post:
      tags:
        - Financials
      summary: Record Payment
      description: >
        Records a payment against the invoice — a gesture the server resolves
        into

        per-line settlement MARKS, executing one `invoice.payment_recorded`
        action

        per mark in one transaction and returning the CREATED MARK ROWS

        (`paymentIds` + `createdPayments`), each mark the handle for

        [removing it](/api-reference/financials/remove-payment).


        **Two modes, exactly one per request.** Send either `payBalanceDue:
        true`

        or an `allocations` array — both together, or neither, is a `400`.

          - **`payBalanceDue: true`** settles the whole document: one mark per
            OPEN line item at that line's full remaining, in the document's
            line-item order, BOTH directions at once. The invoice comes back
            `paid` with `balanceDueCents: 0`, and the gesture's net cash equals
            the `balanceDueCents` you asked to pay, by construction. Only the
            literal `true` is accepted (`payBalanceDue: false` is a `400`) — to
            pay less, name the lines.
          - **`allocations`** marks exactly the line items you name, for exactly
            the cents you name. Nothing is spread, split, or inferred: a line you
            do not name is untouched, and a line you name for less than its
            remaining stays open.

        **Allocation amounts are in the LINE's frame.**
        `allocations[].amountCents`

        settles its own line's remaining **toward zero**, so it carries the sign
        of

        that remaining — this is NOT the oriented net-cash frame
        `balanceDueCents`

        reads in. A payable-direction line with 8,000 remaining takes a mark of

        `+8000`; a receivable-direction line with 3,000 remaining takes a mark
        of

        `+3000` too (money in, but the line's own remaining is positive). Each

        amount must be a nonzero integer, and a `lineItemId` may appear at most

        once per request (both `400`s).


        **Per-mark guards (`422`).** Every mark must name a line item on the

        document (`422 UNKNOWN_ID`), be nonzero and carry its line's remaining's

        sign — a mark against a settled line, or one running opposite its line's

        remaining, is `422 INVALID_AMOUNT`, as is a `payBalanceDue` against a

        document with nothing open — and its magnitude may not exceed its line's

        remaining's (`422 AMOUNT_EXCEEDS_BALANCE_DUE`). The bounds are per line;

        there is no document-scalar bound. The payment date must not be in the

        future (`422 FUTURE_DATE`).


        **Reserve erosion.** On an invoice in a reserved category,

        `erodeReserves` is REQUIRED (`true` = the marks consume the linked

        event's reserves; eroding requires an event link —

        `422 ERODE_REQUIRES_EVENT`). On an operating-category invoice the field

        must be omitted (`422 ERODE_RESERVES_MISMATCH` either way it is

        misused). There is no headroom bound: the marks' oriented reserve effect

        applies to the remaining reserve unbounded — the remaining reserve is a

        SIGNED balance, and eroding past the estimate takes it below zero (the

        books' statement that payments have outrun an estimate that still

        stands), never a `422`.


        **Approval gate (`422 NOT_APPROVED`).** Money may only post against an

        APPROVED invoice (`approved: true` on the invoice row) — `paid ⇒

        approved`. On an UNAPPROVED invoice the outcome depends on the calling

        key's permissions, mirroring the in-app behaviour where a sufficiently

        authorized user's payment auto-approves:

          - the key holds `company.payment:approve` → the invoice is APPROVED
            automatically as part of this request, in the same transaction and
            before the payment marks, and the payment posts. The response's
            invoice row comes back `approved: true`; the auto-approval is
            deliberately NOT listed in `journalIds` (that array is the payment
            gesture's marks, so an idempotent replay returns an identical body).
          - the key does NOT hold it → `422 NOT_APPROVED`. Approve the invoice
            first — via [approve](/api-reference/financials/approve-invoice) with a
            key that holds the permission, or in the app.

        A draft is rejected as `422 INVOICE_DRAFT` regardless of approval: no

        money may post against an unposted document.


        **Concurrency + idempotency.** Requires `If-Match` (the invoice's
        current

        `headJournalId`) and a client-minted `actionId`. The `actionId` anchors

        the FIRST mark's journal action; sibling marks take server-minted ids in

        the same transaction. An identical retry replays the original outcome —

        the full `paymentIds` set included. Reusing an `actionId` with different

        `allocations` is `409 ACTION_ID_REUSED`.


        **Required permission:** `company.payment:update`
      operationId: recordFinancialsInvoicePayment
      parameters:
        - $ref: '#/components/parameters/companyId'
        - name: invoiceId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Invoice identifier
        - $ref: '#/components/parameters/ifMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - actionId
                - paymentDate
              properties:
                actionId:
                  type: string
                  format: uuid
                  description: >-
                    Client-minted idempotency key — becomes the FIRST created
                    mark's journal id (sibling marks mint their own). An
                    identical retry replays the original outcome (the created
                    `paymentIds` included); reuse with a different payload is
                    `409 ACTION_ID_REUSED`
                author:
                  type: string
                  minLength: 1
                  maxLength: 255
                  description: >-
                    Optional display label for the source system's author (e.g.
                    the integrator-side user). Stamped as the journal record's
                    display attribution; the acting principal stays the External
                    API service user, so a label can never impersonate an in-app
                    user. Ignored on idempotent replays
                payBalanceDue:
                  type: boolean
                  enum:
                    - true
                  description: >-
                    Settle the WHOLE document: one mark per open line item at
                    that line's full remaining, both directions at once, so the
                    gesture's net cash equals `balanceDueCents`. Only the
                    literal `true` is accepted. Mutually exclusive with
                    `allocations` — send exactly one of the two
                allocations:
                  type: array
                  minItems: 1
                  description: >-
                    Explicit per-line marks — exactly the line items named, for
                    exactly the cents named; nothing is spread onto an unnamed
                    line. Mutually exclusive with `payBalanceDue` — send exactly
                    one of the two. Each `lineItemId` may appear at most once
                  items:
                    type: object
                    additionalProperties: false
                    required:
                      - lineItemId
                      - amountCents
                    properties:
                      lineItemId:
                        type: string
                        format: uuid
                        description: >-
                          A line item on this invoice (`422 UNKNOWN_ID`
                          otherwise)
                      amountCents:
                        type: integer
                        description: >-
                          Cents applied to that line, in the LINE's frame — it
                          settles the line's remaining toward zero, so it
                          carries that remaining's sign and may not exceed its
                          magnitude. Must be nonzero
                paymentDate:
                  type: string
                  format: date
                  description: >-
                    The payment date, stamped on every created mark (ISO
                    `YYYY-MM-DD`); must not be in the future
                memo:
                  type: string
                  description: Free-text memo, stamped on every created mark
                erodeReserves:
                  type: boolean
                  description: >-
                    REQUIRED on a reserved-category invoice — whether the
                    created marks erode the linked event's reserves; must be
                    OMITTED on an operating-category invoice. No headroom bound
                    applies: the remaining reserve is a signed balance and may
                    cross zero
            examples:
              payBalanceDue:
                summary: >-
                  Settle the whole balance due on a reserved-category invoice —
                  every open line, both directions
                value:
                  actionId: 5e7f9a1b-3c4d-4e6f-8a0b-9c1d2e3f4a5b
                  payBalanceDue: true
                  paymentDate: '2026-06-10'
                  memo: 'check #100'
                  erodeReserves: true
              partialAllocation:
                summary: >-
                  A partial payment aimed at one line of a mixed-direction
                  invoice (the receivable line is left open)
                value:
                  actionId: 6f8a0b2c-4d5e-4f7a-9b1c-0d2e3f4a5b6c
                  allocations:
                    - lineItemId: 550e8400-e29b-41d4-a716-446655440300
                      amountCents: 200000
                  paymentDate: '2026-06-10'
                  erodeReserves: true
              splitAllocation:
                summary: >-
                  One gesture settling both directions of a mixed-direction
                  invoice explicitly — each amount in its own line's frame
                value:
                  actionId: 7a9b1c3d-5e6f-4a8b-9c0d-1e2f3a4b5c6d
                  allocations:
                    - lineItemId: 550e8400-e29b-41d4-a716-446655440300
                      amountCents: 800000
                    - lineItemId: 550e8400-e29b-41d4-a716-446655440301
                      amountCents: 300000
                  paymentDate: '2026-06-10'
      responses:
        '200':
          description: >-
            The refreshed invoice state plus the created MARK rows: `paymentIds`
            are the server-minted removal handles in mark order, and
            `createdPayments` the full mark rows they name (each also present in
            the refreshed `payments`)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/FinancialsV2InvoiceWriteResponse'
                  - type: object
                    required:
                      - paymentIds
                      - createdPayments
                    properties:
                      paymentIds:
                        type: array
                        items:
                          type: string
                          format: uuid
                        description: >-
                          The created marks' server-minted ids, in mark order
                          (the request's `allocations` order, or line-item order
                          under `payBalanceDue`) — each the handle for `DELETE
                          …/payments/{paymentId}`
                      createdPayments:
                        type: array
                        items:
                          $ref: '#/components/schemas/FinancialsV2Payment'
                        description: The created mark rows, in mark order
              examples:
                recorded:
                  summary: >-
                    A 200,000 allocation aimed at the payable line of a
                    mixed-direction invoice — the receivable line stays open
                  value:
                    journalIds:
                      - 5e7f9a1b-3c4d-4e6f-8a0b-9c1d2e3f4a5b
                    paymentIds:
                      - 550e8400-e29b-41d4-a716-446655440800
                    createdPayments:
                      - id: 550e8400-e29b-41d4-a716-446655440800
                        journalId: 5e7f9a1b-3c4d-4e6f-8a0b-9c1d2e3f4a5b
                        lineItemId: 550e8400-e29b-41d4-a716-446655440300
                        amountCents: 200000
                        paymentDate: '2026-06-10'
                        erodeReserves: true
                        memo: 'check #100'
                        createdAt: '2026-06-10T09:00:00.000Z'
                    invoice:
                      id: 550e8400-e29b-41d4-a716-446655440700
                      invoiceNumber: INV-389538-455
                      categoryId: 550e8400-e29b-41d4-a716-446655440100
                      status: partially_paid
                      linkedEvent:
                        id: 550e8400-e29b-41d4-a716-446655440200
                        displayName: 'Claim #1042'
                      linkedPolicy: null
                      linkedPayee: null
                      incurredDate: '2026-06-01'
                      dueDate: null
                      memo: Defense costs less subrogation recovery
                      fieldData: null
                      lineItems:
                        - lineItemId: 550e8400-e29b-41d4-a716-446655440300
                          lineItemTypeId: 550e8400-e29b-41d4-a716-446655440110
                          memo: Fees (payable direction)
                          amountCents: 800000
                        - lineItemId: 550e8400-e29b-41d4-a716-446655440301
                          lineItemTypeId: 550e8400-e29b-41d4-a716-446655440111
                          memo: Recovery (receivable direction)
                          amountCents: 300000
                      totalAmountCents: 500000
                      amountPaidCents: 200000
                      balanceDueCents: 300000
                      paidDate: null
                      lastPaymentDate: '2026-06-10'
                      voidedAt: null
                      deletedAt: null
                      createdAt: '2026-06-01T10:30:00.000Z'
                      updatedAt: '2026-06-10T09:00:00.000Z'
                      headJournalId: 5e7f9a1b-3c4d-4e6f-8a0b-9c1d2e3f4a5b
                    payments:
                      - id: 550e8400-e29b-41d4-a716-446655440800
                        journalId: 5e7f9a1b-3c4d-4e6f-8a0b-9c1d2e3f4a5b
                        lineItemId: 550e8400-e29b-41d4-a716-446655440300
                        amountCents: 200000
                        paymentDate: '2026-06-10'
                        erodeReserves: true
                        memo: 'check #100'
                        createdAt: '2026-06-10T09:00:00.000Z'
        '400':
          description: >-
            Bad Request — a malformed payload (both `allocations` and
            `payBalanceDue`, neither of them, `payBalanceDue: false`, an empty
            `allocations` array, a zero allocation amount, a repeated
            `lineItemId`), or a missing/malformed `If-Match` header
            (`IF_MATCH_REQUIRED` / `IF_MATCH_INVALID`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                bothModes:
                  summary: Both modes in one request
                  value:
                    error:
                      code: VALIDATION_ERROR
                      message: >-
                        Exactly one of `allocations` or `payBalanceDue` is
                        required — supply explicit per-line marks, or ask for
                        the whole balance
                      userMessages:
                        - >-
                          Exactly one of `allocations` or `payBalanceDue` is
                          required — supply explicit per-line marks, or ask for
                          the whole balance
                duplicateLineItem:
                  summary: The same line item named twice
                  value:
                    error:
                      code: VALIDATION_ERROR
                      message: >-
                        allocations may name each lineItemId at most once —
                        combine a line’s marks into one entry
                      userMessages:
                        - >-
                          allocations may name each lineItemId at most once —
                          combine a line’s marks into one entry
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: >-
            Not Found — no invoice with this id, or the company does not have
            this financials surface enabled (indistinguishable by design)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invoiceNotFound:
                  summary: Invoice not found
                  value:
                    error:
                      code: NotFoundError
                      message: Invoice not found
                      userMessages:
                        - Invoice not found
        '409':
          $ref: '#/components/responses/FinancialsV2IfMatchConflict'
        '422':
          $ref: '#/components/responses/FinancialsV2PreconditionFailed'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  parameters:
    companyId:
      name: companyId
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Company identifier
    ifMatch:
      name: If-Match
      in: header
      required: true
      schema:
        type: string
        format: uuid
      description: >-
        The invoice's current `headJournalId` — the optimistic-concurrency
        watermark every single-invoice write after creation must send. Read it
        off any invoice read or write response and echo it verbatim (a bare
        uuid; an entity-tag dressing of it — `"uuid"` or `W/"uuid"` — is also
        accepted). Missing or malformed is a `400` (`IF_MATCH_REQUIRED` /
        `IF_MATCH_INVALID`); a stale value is a `409 IF_MATCH_CONFLICT` whose
        body carries the current invoice. An idempotent `actionId` replay
        short-circuits BEFORE the watermark is evaluated.
  schemas:
    FinancialsV2InvoiceWriteResponse:
      type: object
      description: >-
        The response of every single-invoice write — refreshed read state, not
        an ack: the emitted journal id(s), THE invoice row (identical shape to
        the reads — one invoice representation everywhere; its `headJournalId`
        is the next `If-Match`), and the invoice's live payments. An idempotent
        `actionId` replay returns this same shape rebuilt from the original
        outcome, server-minted values included.
      required:
        - journalIds
        - invoice
        - payments
      properties:
        journalIds:
          type: array
          minItems: 1
          items:
            type: string
            format: uuid
          description: >-
            Every journal id the write emitted — the anchor action's id (the
            `actionId` you supplied) first, then any engine-minted siblings (a
            multi-mark payment's additional marks) and any companion the horizon
            rule composed (e.g. the reserve unwind of a pre-horizon eroding
            payment's removal, or a delete's payment sweep)
        invoice:
          $ref: '#/components/schemas/FinancialsV2Invoice'
        payments:
          type: array
          description: The invoice's live payment marks after the write, newest first
          items:
            $ref: '#/components/schemas/FinancialsV2Payment'
    FinancialsV2Payment:
      type: object
      description: >-
        A recorded payment MARK on an invoice: one row = one per-line settlement
        mark against `lineItemId`. `amountCents` is signed in the LINE's frame —
        its sign matches the line's open remaining, and the mark settles that
        line toward zero. One journal action per mark (`journalId` is the record
        that created it), each independently removable by `id`; one gesture
        (e.g. paying a document's whole balance due) may create several marks.
        `erodeReserves` is the caller's choice at record time (`null` where the
        invoice's category has no reserves to erode).
      required:
        - id
        - journalId
        - lineItemId
        - amountCents
        - paymentDate
        - erodeReserves
        - memo
        - createdAt
      properties:
        id:
          type: string
          format: uuid
          description: The payment mark's id — the handle for removing it
        journalId:
          type: string
          format: uuid
          description: The journal record that recorded the mark
        lineItemId:
          type: string
          format: uuid
          description: The invoice line item the mark settles
        amountCents:
          type: integer
          description: >-
            The mark's amount in signed integer cents, in its line item's frame
            — the sign matches the line's remaining and the mark settles it
            toward zero
        paymentDate:
          type: string
          format: date
          description: The payment date (ISO `YYYY-MM-DD`)
        erodeReserves:
          type: boolean
          nullable: true
          description: >-
            Whether the mark erodes the linked event's reserves; `null` where
            the invoice's category carries no reserves
        memo:
          type: string
          nullable: true
          description: Free-text memo, or `null`
        createdAt:
          type: string
          format: date-time
          description: When the mark was recorded
    ErrorResponse:
      type: object
      description: Standard error response for all external API endpoints
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable error code
              example: VALIDATION_ERROR
            message:
              type: string
              description: Human-readable error message
              example: 'submissionId: Required field is missing'
            userMessages:
              type: array
              description: >-
                Clean, verbatim-displayable messages — one entry per failure,
                free of error-code tags, field paths, and internal noise.
                Suitable for showing to end users as-is.
              items:
                type: string
              example:
                - Exposures of type 'company' require an address
            details:
              type: array
              description: Additional details for validation errors (field-level errors)
              items:
                type: object
                properties:
                  field:
                    type: string
                    description: The field that caused the error
                    example: submissionId
                  message:
                    type: string
                    description: Description of the field error
                    example: Required field is missing
    FinancialsV2Invoice:
      type: object
      description: >-
        THE invoice representation — the same shape everywhere an endpoint
        returns an invoice (listing rows, the detail read, and every write's
        refreshed-row response). `headJournalId` is the invoice's current
        journal head — the optimistic-concurrency token subsequent writes echo
        back as `If-Match`.
      required:
        - id
        - invoiceNumber
        - categoryId
        - status
        - linkedEvent
        - linkedPolicy
        - linkedPayee
        - incurredDate
        - dueDate
        - memo
        - fieldData
        - lineItems
        - totalAmountCents
        - amountPaidCents
        - balanceDueCents
        - draftAmountPaidCents
        - paidDate
        - lastPaymentDate
        - voidedAt
        - deletedAt
        - approved
        - approvedAt
        - createdAt
        - updatedAt
        - headJournalId
      properties:
        id:
          type: string
          format: uuid
          description: The invoice's id
        invoiceNumber:
          type: string
          description: The invoice's system-minted display number
        categoryId:
          type: string
          format: uuid
          description: The transaction category the invoice belongs to
        status:
          type: string
          enum:
            - no_charges
            - owed
            - partially_paid
            - paid
            - voided
            - deleted
          description: >-
            Derived status, first match wins: `deleted` → `voided` →
            `no_charges` (every line item's amount is zero) → `paid` (every line
            item is settled — its amount minus its live marks is exactly zero) →
            `owed` (no live payment marks) → `partially_paid`. Settlement is per
            line: no scalar makes a document paid, and a zero-due document with
            unsettled lines still reads `owed`
        linkedEvent:
          type: object
          nullable: true
          description: >-
            The linked event, display-ready (the server resolves the display
            name), or `null` when not linked to an event
          required:
            - id
            - displayName
          properties:
            id:
              type: string
              format: uuid
              description: The linked event's id
            displayName:
              type: string
              description: The linked event's display name
        linkedPolicy:
          type: object
          nullable: true
          description: >-
            The linked policy, display-ready, or `null`. An invoice links to an
            event or a policy, never both
          required:
            - id
            - displayName
          properties:
            id:
              type: string
              format: uuid
              description: The linked policy's id
            displayName:
              type: string
              description: The linked policy's display name
        linkedPayee:
          type: object
          nullable: true
          description: >-
            The invoice's payee, display-ready, or `null` when none is set.
            `entityType` is an open string (e.g. `Person`, `Organization`) — the
            payee vocabulary is the entity module's, not part of this contract
          required:
            - id
            - entityType
            - displayName
          properties:
            id:
              type: string
              format: uuid
              description: The payee entity's id
            entityType:
              type: string
              description: The payee's entity type (e.g. Person, Organization)
            displayName:
              type: string
              description: The payee's display name
        incurredDate:
          type: string
          format: date
          nullable: true
          description: The date the charges were incurred (ISO `YYYY-MM-DD`), or `null`
        dueDate:
          type: string
          format: date
          nullable: true
          description: When payment is owed (display metadata only), or `null`
        memo:
          type: string
          nullable: true
          description: Free-text memo, or `null`
        fieldData:
          type: object
          nullable: true
          additionalProperties: true
          description: >-
            Values for the tenant-defined custom invoice fields, keyed by field
            referenceId — an opaque JSON object, or `null`
        lineItems:
          type: array
          description: The invoice's line items
          items:
            $ref: '#/components/schemas/FinancialsV2InvoiceLineItem'
        totalAmountCents:
          type: integer
          description: >-
            Sum of the ORIENTED line-item amounts (a payable-direction line
            counts +, a receivable-direction line counts −), in integer cents —
            the net cash the document commits to move
        amountPaidCents:
          type: integer
          description: >-
            Sum of the ORIENTED live payment marks, in integer cents — the net
            cash moved so far
        balanceDueCents:
          type: integer
          description: >-
            `totalAmountCents - amountPaidCents`, in integer cents — the net
            cash remaining to move: positive = out, negative = in. May move
            non-monotonically as opposite-direction lines settle
        draftAmountPaidCents:
          type: integer
          nullable: true
          description: >-
            A DRAFT's annex-derived paid total: the oriented net-cash sum of the
            payment marks the draft was born carrying (the same frame as
            `amountPaidCents`, which counts LIVE posted marks only and reads 0
            while drafting) — what a draft's underlying payment picture will
            read once finalized. `null` on non-draft invoices (the annex is
            spent at finalize). Display-only — never a posted ledger amount
        paidDate:
          type: string
          format: date
          nullable: true
          description: >-
            Payment date of the mark that first made every line item settled;
            clears (`null`) whenever any line reopens
        lastPaymentDate:
          type: string
          format: date
          nullable: true
          description: Date of the most recent live payment, or `null`
        voidedAt:
          type: string
          format: date-time
          nullable: true
          description: When the invoice was voided, or `null`
        deletedAt:
          type: string
          format: date-time
          nullable: true
          description: When the invoice was deleted, or `null`
        approved:
          type: boolean
          description: >-
            The approval projection: `true` after `invoice.approved`, cleared
            only by `invoice.unapproved` — an edit never resets approval (the
            approval-relevant facts, line items and category, are immutable once
            posted). Only meaningful while the approvals feature is enabled;
            reads `false` otherwise
        approvedAt:
          type: string
          format: date-time
          nullable: true
          description: >-
            When the approval landed (`invoice.approved`), or `null` while
            unapproved — `approved` is exactly `approvedAt != null`. Like
            `approved`, only meaningful while the approvals feature is enabled;
            reads `null` otherwise
        createdAt:
          type: string
          format: date-time
          description: When the invoice was created
        updatedAt:
          type: string
          format: date-time
          description: When the invoice last changed
        headJournalId:
          type: string
          format: uuid
          description: >-
            The invoice's current journal head — send it as `If-Match` on the
            next write to this invoice; a mismatch is a `409` carrying the
            current row
    FinancialsV2IfMatchConflictError:
      type: object
      description: >-
        The conflict envelope of an `If-Match` invoice write.
        `IF_MATCH_CONFLICT`: the supplied watermark is stale —
        `error.currentInvoice` is the invoice's CURRENT row; re-read, reconcile,
        and retry with its `headJournalId`. `ACTION_ID_REUSED`: the `actionId`
        names a different recorded action (no `currentInvoice`).
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - IF_MATCH_CONFLICT
                - ACTION_ID_REUSED
              description: Which conflict occurred
            message:
              type: string
              description: Human-readable error message
            userMessages:
              type: array
              description: Clean, verbatim-displayable messages
              items:
                type: string
            currentInvoice:
              allOf:
                - $ref: '#/components/schemas/FinancialsV2Invoice'
              description: >-
                Present exactly when `code` is `IF_MATCH_CONFLICT` — the
                invoice's current row; its `headJournalId` is the fresh
                `If-Match` value for the retry
    FinancialsV2PreconditionError:
      type: object
      description: >-
        A failed write precondition (`422`): the request was well-formed but the
        books reject it. Preconditions fail closed — a rejected write leaves no
        journal record, no ledger change, and no read-state change. `error.code`
        is one of the STABLE financials error codes (single source:
        `FINANCIALS_V2_PRECONDITION_CODES` in the engine's error module,
        re-exported to this surface as `FINANCIALS_V2_API_ERROR_CODES`) —
        messages may be reworded, the codes will not.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - LIVE_PAYMENTS
                - AMOUNT_EXCEEDS_BALANCE_DUE
                - INVALID_AMOUNT
                - LINE_ITEMS_IMMUTABLE
                - INVOICE_DELETED
                - INVOICE_VOIDED
                - INVOICE_DRAFT
                - INVOICE_NOT_DRAFT
                - PAYMENT_NOT_FOUND
                - ERODE_RESERVES_MISMATCH
                - ERODE_REQUIRES_EVENT
                - DEPRECATED_CONFIG
                - NOT_RESERVED_CATEGORY
                - FUTURE_DATE
                - UNKNOWN_ID
                - LINK_TARGET_MISSING
                - UNAPPROVE_PAID
                - NOT_APPROVED
                - POLICY_INVOICE_BATCH_ONLY
              description: >-
                The stable precondition code — one per guard. `LIVE_PAYMENTS`
                (the action — re-link, payee change, or void — needs the
                invoice's live payments removed first);
                `AMOUNT_EXCEEDS_BALANCE_DUE` (a payment mark's magnitude
                overshoots its line item's remaining — per line, never a
                document scalar); `INVALID_AMOUNT` (a zero mark, or a mark whose
                sign opposes its line item's open remaining — a `payBalanceDue`
                against a document with nothing open included);
                `LINE_ITEMS_IMMUTABLE` (a posted invoice's line items — ids,
                types, amounts — and its category are fixed from the posting
                moment: any post-posting change is rejected, at any payment
                count; corrections are void-and-recreate, payments removed
                first; drafts edit freely until finalize); `INVOICE_DELETED` /
                `INVOICE_VOIDED` (lifecycle closures); `INVOICE_DRAFT` (drafts
                are unposted — money may not touch one: recording a payment on
                or voiding a draft is rejected; finalize it first);
                `INVOICE_NOT_DRAFT` (finalize targets exactly a draft);
                `PAYMENT_NOT_FOUND` (no live payment mark with that id on that
                invoice); `ERODE_RESERVES_MISMATCH` / `ERODE_REQUIRES_EVENT`
                (the erode flag missing on a reserved category or present on
                operating; eroding needs a linked event — there is no headroom
                bound: the remaining reserve is a signed balance and may cross
                zero); `DEPRECATED_CONFIG` / `NOT_RESERVED_CATEGORY` /
                `UNKNOWN_ID` (cited configuration guards); `FUTURE_DATE`;
                `LINK_TARGET_MISSING` (the named link target does not exist);
                `UNAPPROVE_PAID` (an approval cannot be revoked once payments
                have been recorded — remove them first); `NOT_APPROVED` (money
                cannot post against an unapproved invoice AND the calling key
                does not hold `company.payment:approve`, so it cannot approve it
                either — a key that holds the permission auto-approves instead
                of failing here); `POLICY_INVOICE_BATCH_ONLY` (the write touches
                a POLICY-LINKED invoice's document — creating one with
                `links.policy`, or updating, voiding, restoring, deleting, or
                re-linking one that is already policy-linked. A policy's
                invoices are issued as one set so that every price component of
                the policy's pricing contract stays netted to its own line item,
                so they are written only by the policy invoice batch: `POST
                /api/v1/companies/{companyId}/financials/policies/{policyId}/invoices/batch`.
                Change the policy or submit an explicit batch and the invoices
                are reissued. Payments, payee changes, and approvals on a policy
                invoice are unaffected)
            message:
              type: string
              description: Human-readable description of the failed precondition
            userMessages:
              type: array
              description: Clean, verbatim-displayable messages
              items:
                type: string
    FinancialsV2InvoiceLineItem:
      type: object
      description: A stored invoice line item as rendered on invoice reads.
      required:
        - lineItemId
        - lineItemTypeId
        - memo
        - amountCents
      properties:
        lineItemId:
          type: string
          format: uuid
          description: The line item's id
        lineItemTypeId:
          type: string
          format: uuid
          description: >-
            The configured line item type this item cites — discover ids with
            `GET /api/v1/companies/{companyId}/financials/config/categories`
        memo:
          type: string
          nullable: true
          description: Free-text memo, or `null`
        amountCents:
          type: integer
          description: >-
            The line item's amount in integer cents — the magnitude a user would
            type; a negative amount is a credit within the item's own frame — it
            posts opposite the line item type's expected direction (e.g. a
            reversal or refund); the posting rule always comes from the type,
            never the sign
  responses:
    Unauthorized:
      description: Unauthorized - Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            missingApiKey:
              summary: Missing API key
              value:
                error:
                  code: AuthenticationError
                  message: API key authentication required
                  userMessages:
                    - API key authentication required
            invalidApiKey:
              summary: >-
                Invalid API key (e.g. unknown key, or a Bearer token used
                instead of an API key)
              value:
                error:
                  code: AuthenticationError
                  message: Invalid API key
                  userMessages:
                    - Invalid API key
    Forbidden:
      description: Forbidden - Insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            insufficientPermissions:
              summary: Insufficient permissions
              value:
                error:
                  code: AuthorizationError
                  message: User is not authorized to perform the requested action
                  userMessages:
                    - User is not authorized to perform the requested action
            companyMismatch:
              summary: A valid API key naming another company in the URL
              value:
                error:
                  code: AuthorizationError
                  message: API key is not scoped to the requested company
                  userMessages:
                    - API key is not scoped to the requested company
    FinancialsV2IfMatchConflict:
      description: >-
        Conflict — a stale `If-Match` watermark (`IF_MATCH_CONFLICT`: the body
        carries the invoice's CURRENT row; re-read, reconcile, retry with its
        `headJournalId`), or an `actionId` reused by a different action
        (`ACTION_ID_REUSED`: supply a fresh uuid — an identical retry replays
        instead).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/FinancialsV2IfMatchConflictError'
          examples:
            ifMatchConflict:
              summary: Stale If-Match — the body carries the current invoice
              value:
                error:
                  code: IF_MATCH_CONFLICT
                  message: >-
                    If-Match 550e8400-e29b-41d4-a716-446655440901 does not match
                    the invoice's current head journal id
                  userMessages:
                    - >-
                      If-Match 550e8400-e29b-41d4-a716-446655440901 does not
                      match the invoice's current head journal id
                  currentInvoice:
                    id: 550e8400-e29b-41d4-a716-446655440700
                    invoiceNumber: INV-389538-455
                    categoryId: 550e8400-e29b-41d4-a716-446655440100
                    status: partially_paid
                    linkedEvent:
                      id: 550e8400-e29b-41d4-a716-446655440200
                      displayName: 'Claim #1042'
                    linkedPolicy: null
                    linkedPayee: null
                    incurredDate: '2026-06-01'
                    dueDate: null
                    memo: Legal defense
                    fieldData: null
                    lineItems:
                      - lineItemId: 550e8400-e29b-41d4-a716-446655440300
                        lineItemTypeId: 550e8400-e29b-41d4-a716-446655440110
                        memo: null
                        amountCents: 10000
                    totalAmountCents: 10000
                    amountPaidCents: 4000
                    balanceDueCents: 6000
                    paidDate: null
                    lastPaymentDate: '2026-06-10'
                    voidedAt: null
                    deletedAt: null
                    createdAt: '2026-06-01T10:30:00.000Z'
                    updatedAt: '2026-06-10T09:00:00.000Z'
                    headJournalId: 550e8400-e29b-41d4-a716-446655440900
            actionIdReused:
              summary: actionId reused by a different action
              value:
                error:
                  code: ACTION_ID_REUSED
                  message: >-
                    actionId 550e8400-e29b-41d4-a716-446655440000 was already
                    used by a different action; supply a fresh uuid (or retry
                    with the identical action to replay it)
                  userMessages:
                    - >-
                      actionId 550e8400-e29b-41d4-a716-446655440000 was already
                      used by a different action; supply a fresh uuid (or retry
                      with the identical action to replay it)
    FinancialsV2PreconditionFailed:
      description: >-
        Unprocessable — a write precondition failed. Fails closed: no journal
        record, no ledger change, no read-state change. `error.code` is one of
        the stable financials error codes; each operation's description names
        its signature codes.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/FinancialsV2PreconditionError'
          examples:
            livePayments:
              summary: The action requires the invoice's live payments removed first
              value:
                error:
                  code: LIVE_PAYMENTS
                  message: >-
                    The invoice has live payments; remove them before this
                    action
                  userMessages:
                    - >-
                      The invoice has live payments; remove them before this
                      action
            unknownId:
              summary: >-
                A cited configuration id is unknown (or outside the cited
                category)
              value:
                error:
                  code: UNKNOWN_ID
                  message: lineItemTypeId does not belong to the cited category
                  userMessages:
                    - lineItemTypeId does not belong to the cited category
            policyInvoiceBatchOnly:
              summary: >-
                Policy-linked invoice documents are changed only as one policy
                batch
              value:
                error:
                  code: POLICY_INVOICE_BATCH_ONLY
                  message: >-
                    Policy-linked invoice documents can only be changed through
                    POST
                    /api/v1/companies/{companyId}/financials/policies/{policyId}/invoices/batch
                  userMessages:
                    - >-
                      Policy-linked invoice documents can only be changed
                      through POST
                      /api/v1/companies/{companyId}/financials/policies/{policyId}/invoices/batch
    InternalServerError:
      description: Internal Server Error - Unexpected error occurred
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            internalError:
              summary: Unexpected server error
              value:
                error:
                  code: UncaughtActionError
                  message: Uncaught error occurred in <actionName>
                  userMessages:
                    - An unexpected error occurred. Please try again later.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: >-
        API key authentication. Send your raw API key as the `Authorization`
        header value with NO scheme prefix — `Authorization: YOUR-API-KEY`. Do
        NOT prefix it with `Bearer ` or `ApiKey `, and do not use an `X-API-Key`
        header; those are not accepted.

````