> ## 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.

# Cancel Policy Transaction

> Cancels a policy as of a given date via a CANCEL transaction. The policy must be
active at the cancellation date.

`cancellationDate` (= the transaction effective date) is sugar the server expands into
per-segment, segment-scoped deltas on framework-required fields. `policyStatus` flips to
`cancelled` from the cancellation date through end of term (splitting the segment at the
boundary), and a single `cancellationEffectiveOnDate` is recorded uniformly across the
whole term — the same value on both sides of the boundary. `policyStatus` alone marks
which side a segment is on. There is no `policyEarlyTerminationDate` and no status-enum
machinery.

## A cancel never reprices — the pricing contract is DERIVED

The server computes what the cancelled term's pricing contract must be, per charge. Every
pricing component the policy already carries stays present, keeps its `label`, `group`,
`kind` and `earningBasis`, and drops to **its own earned amount** through the cancellation
date. A charge modelled as fully earned at inception (`earningBasis:
"fully-earned-at-inception"` — a non-refundable policy fee) therefore keeps its whole
value and is untouched by the cancellation; a pro-rata charge keeps the part the term has
run through. Nothing is relabelled and nothing is merged: a charge's `<group, label>` pair
is its identity across policy versions, and the platform's revenue recognition and invoice
binding both attribute money by it.

**A bare `{ "cancellationDate": ... }` body is the normal request, and it always
succeeds.** Omit `fullTermPricingInfo` and the derived contract is used.

If you DO send `fullTermPricingInfo`, it is validated against that derived contract
**component by component, to the exact cent**:

- every component the policy already carries must be present, at exactly the derived value
  and with the derived `earningBasis`;
- an UNRECOGNIZED `<group, label>` pair is an **addition** — money the cancellation itself
  creates, such as a short-rate penalty or a cancellation fee — and it MUST carry
  `earningBasis: "fully-earned-at-inception"`, because it is recognized on the cancellation
  date rather than scheduled over coverage that is ending. An addition may be any `kind`
  (including `Premium` for a short-rate penalty) and may be negative (a clawback).

Re-valuing, re-basing or dropping an existing component is rejected with a `400` naming it.
To CHANGE what the policy is priced at, book an **endorsement at the same effective date**
and then cancel: transactions sharing an effective date are applied in booking order
(ascending `policyVersion`), so the endorsement's figures are what the cancellation floors
against. The same composition works after a reinstatement — reinstate, then endorse — to
reprice the restored term going forward.

Two optional **derived channels** are whole-object overwrites of a policy-root full-term
container — necessarily whole-term, so neither carries dates nor an element-level form:

- **`fullTermPricingInfo`** — the full-term pricing contract, validated as described above.
- **`fullTermPolicyRatingResult`** — the canonical policy-level rating result. Unvalidated
  and unconstrained: it is the rater's own output, not the money the platform books.

**Policy invoices.** Optionally send `invoicePlan`, a fully explicit
keep/void/create plan. Nothing is defaulted or inferred. If cancellation
moves a billed policy's pricing target, a conserving plan is required.

**Required permission:** `policy:update`




## OpenAPI

````yaml /openapi/generated-external-api.yaml post /api/v1/companies/{companyId}/policies/{policyId}/transaction/cancel
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}/policies/{policyId}/transaction/cancel:
    post:
      tags:
        - Field Model Policy Transactions
      summary: Cancel Policy Transaction
      description: >
        Cancels a policy as of a given date via a CANCEL transaction. The policy
        must be

        active at the cancellation date.


        `cancellationDate` (= the transaction effective date) is sugar the
        server expands into

        per-segment, segment-scoped deltas on framework-required fields.
        `policyStatus` flips to

        `cancelled` from the cancellation date through end of term (splitting
        the segment at the

        boundary), and a single `cancellationEffectiveOnDate` is recorded
        uniformly across the

        whole term — the same value on both sides of the boundary.
        `policyStatus` alone marks

        which side a segment is on. There is no `policyEarlyTerminationDate` and
        no status-enum

        machinery.


        ## A cancel never reprices — the pricing contract is DERIVED


        The server computes what the cancelled term's pricing contract must be,
        per charge. Every

        pricing component the policy already carries stays present, keeps its
        `label`, `group`,

        `kind` and `earningBasis`, and drops to **its own earned amount**
        through the cancellation

        date. A charge modelled as fully earned at inception (`earningBasis:

        "fully-earned-at-inception"` — a non-refundable policy fee) therefore
        keeps its whole

        value and is untouched by the cancellation; a pro-rata charge keeps the
        part the term has

        run through. Nothing is relabelled and nothing is merged: a charge's
        `<group, label>` pair

        is its identity across policy versions, and the platform's revenue
        recognition and invoice

        binding both attribute money by it.


        **A bare `{ "cancellationDate": ... }` body is the normal request, and
        it always

        succeeds.** Omit `fullTermPricingInfo` and the derived contract is used.


        If you DO send `fullTermPricingInfo`, it is validated against that
        derived contract

        **component by component, to the exact cent**:


        - every component the policy already carries must be present, at exactly
        the derived value
          and with the derived `earningBasis`;
        - an UNRECOGNIZED `<group, label>` pair is an **addition** — money the
        cancellation itself
          creates, such as a short-rate penalty or a cancellation fee — and it MUST carry
          `earningBasis: "fully-earned-at-inception"`, because it is recognized on the cancellation
          date rather than scheduled over coverage that is ending. An addition may be any `kind`
          (including `Premium` for a short-rate penalty) and may be negative (a clawback).

        Re-valuing, re-basing or dropping an existing component is rejected with
        a `400` naming it.

        To CHANGE what the policy is priced at, book an **endorsement at the
        same effective date**

        and then cancel: transactions sharing an effective date are applied in
        booking order

        (ascending `policyVersion`), so the endorsement's figures are what the
        cancellation floors

        against. The same composition works after a reinstatement — reinstate,
        then endorse — to

        reprice the restored term going forward.


        Two optional **derived channels** are whole-object overwrites of a
        policy-root full-term

        container — necessarily whole-term, so neither carries dates nor an
        element-level form:


        - **`fullTermPricingInfo`** — the full-term pricing contract, validated
        as described above.

        - **`fullTermPolicyRatingResult`** — the canonical policy-level rating
        result. Unvalidated
          and unconstrained: it is the rater's own output, not the money the platform books.

        **Policy invoices.** Optionally send `invoicePlan`, a fully explicit

        keep/void/create plan. Nothing is defaulted or inferred. If cancellation

        moves a billed policy's pricing target, a conserving plan is required.


        **Required permission:** `policy:update`
      operationId: cancelPolicyTransaction
      parameters:
        - $ref: '#/components/parameters/companyId'
        - $ref: '#/components/parameters/policyIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - cancellationDate
              properties:
                cancellationDate:
                  type: string
                  format: date
                  description: >
                    The date the cancellation takes effect in ISO 8601 format.
                    Must fall

                    within the policy term, and the policy must be active at
                    this date.
                transactionTimestamp:
                  type: string
                  format: date-time
                  description: >
                    When the business decision was made. Defaults to the current
                    time if omitted.

                    Set explicitly for imports (e.g., aligning to a bordereau
                    booking date).
                fullTermPricingInfo:
                  type: object
                  description: >
                    OPTIONAL — omit it and the server derives the whole contract
                    (recommended).


                    When supplied, it must be the derived contract plus
                    additions: every component

                    the policy already carries, present at exactly its earned
                    amount through the

                    cancellation date and with its existing `earningBasis`, plus
                    any NEW

                    `<group, label>` pairs (short-rate penalties, cancellation
                    fees) carrying

                    `earningBasis: "fully-earned-at-inception"`. Each component
                    is

                    `{label, group, kind, value[, earningBasis]}`; the five
                    rollups are computed by

                    the platform and any you send are ignored. Compared to the
                    exact cent — there

                    is no tolerance.
                  additionalProperties: true
                fullTermPolicyRatingResult:
                  type: object
                  description: >
                    Optional whole-object overwrite of the policy-root canonical
                    rating result.
                  additionalProperties: true
                invoicePlan:
                  $ref: '#/components/schemas/PolicyInvoiceTransactionPlan'
            examples:
              simpleCancellation:
                summary: >-
                  Cancel policy mid-term (bare body — the server derives the
                  whole contract; this is the normal request)
                value:
                  cancellationDate: '2025-06-15'
              cancellationWithFee:
                summary: >-
                  Cancel and charge a cancellation fee (an addition — new key,
                  immediate basis)
                description: >
                  The existing `Policy Premium` component is stated at its
                  earned amount through

                  Jun 15 (the server's figure — omit the whole container if you
                  don't want to

                  compute it), and `Cancellation Fee` is a NEW key, so it must
                  declare

                  `earningBasis: "fully-earned-at-inception"`.
                value:
                  cancellationDate: '2025-06-15'
                  fullTermPricingInfo:
                    pricingComponents:
                      - label: Policy Premium
                        group: Policy Invoice
                        kind: Premium
                        value: 38493.15
                      - label: Cancellation Fee
                        group: Policy Invoice
                        kind: Fees
                        earningBasis: fully-earned-at-inception
                        value: 500
              cancellationWithTimestamp:
                summary: Cancel with explicit transaction timestamp
                value:
                  cancellationDate: '2025-06-15'
                  transactionTimestamp: '2025-06-14T16:00:00Z'
      responses:
        '201':
          description: Policy cancelled successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyTransactionVersionResponse'
              examples:
                success:
                  summary: Cancellation transaction response
                  value:
                    policyId: 550e8400-e29b-41d4-a716-446655440001
                    policyVersion: 2
                    transactionId: 550e8400-e29b-41d4-a716-446655440040
                    startDate: '2025-01-01'
                    endDate: '2025-12-31'
                    createdAt: '2025-06-15T10:30:00.000Z'
                    policyStartDate:
                      year: 2025
                      month: 1
                      day: 1
                      timezone: America/New_York
                    policyEndDate:
                      year: 2025
                      month: 12
                      day: 31
                      timezone: America/New_York
                    fullTermPricingInfo:
                      premium: 42500
                      taxes: 0
                      fees: 500
                      brokerCommission: 0
                      programCommission: 0
                      pricingComponents:
                        - label: Policy Premium
                          group: Policy Invoice
                          kind: Premium
                          earningBasis: null
                          value: 42500
                        - label: Cancellation Fee
                          group: Policy Invoice
                          kind: Fees
                          earningBasis: fully-earned-at-inception
                          value: 500
                    fullTermPolicyRatingResult: null
                    segments:
                      - startDate: '2025-01-01'
                        endDate: '2025-06-14'
                        data:
                          policyStatus: active
                          cancellationEffectiveOnDate:
                            year: 2025
                            month: 6
                            day: 15
                            timezone: null
                          annualPremium: 85000
                          fullTermPricingInfo:
                            premium: 42500
                            taxes: 0
                            fees: 500
                            brokerCommission: 0
                            programCommission: 0
                            pricingComponents:
                              - label: Policy Premium
                                group: Policy Invoice
                                kind: Premium
                                earningBasis: null
                                value: 42500
                              - label: Cancellation Fee
                                group: Policy Invoice
                                kind: Fees
                                earningBasis: fully-earned-at-inception
                                value: 500
                      - startDate: '2025-06-15'
                        endDate: '2025-12-31'
                        data:
                          policyStatus: cancelled
                          cancellationEffectiveOnDate:
                            year: 2025
                            month: 6
                            day: 15
                            timezone: null
                          annualPremium: 85000
                          fullTermPricingInfo:
                            premium: 42500
                            taxes: 0
                            fees: 500
                            brokerCommission: 0
                            programCommission: 0
                            pricingComponents:
                              - label: Policy Premium
                                group: Policy Invoice
                                kind: Premium
                                earningBasis: null
                                value: 42500
                              - label: Cancellation Fee
                                group: Policy Invoice
                                kind: Fees
                                earningBasis: fully-earned-at-inception
                                value: 500
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                noExistingVersion:
                  summary: Policy has no NEW_BUSINESS transaction
                  value:
                    error:
                      code: InvalidRequest
                      message: >-
                        Policy has no existing version — NEW_BUSINESS must be
                        created first
                dateOutsidePolicyPeriod:
                  summary: Cancellation date outside policy term
                  value:
                    error:
                      code: InvalidRequest
                      message: >-
                        Cancellation date 2026-03-01 falls outside policy period
                        [2025-01-01, 2025-12-31]
                policyNotActive:
                  summary: Policy is not active at cancellation date
                  value:
                    error:
                      code: InvalidRequest
                      message: >-
                        Policy must be active at cancellation date 2025-09-01 —
                        current status is "cancelled"
                timestampNotMonotonic:
                  summary: >-
                    transactionTimestamp earlier than the latest existing
                    transaction
                  value:
                    error:
                      code: InvalidRequest
                      message: >-
                        transactionTimestamp (2025-05-01T10:00:00Z) is earlier
                        than the latest existing transaction on this policy
                        (2025-06-14T16:00:00Z)
                componentValueMismatch:
                  summary: A supplied component re-values an existing charge
                  value:
                    error:
                      code: InvalidRequest
                      message: >-
                        Cancel transaction states 50000.00 for the pricing
                        component <kind 'Premium', group 'Policy Invoice', label
                        'Policy Premium'>, but the server derives 38493.15 — the
                        cancel floors each existing charge at what it has
                        earned. To change an existing charge, book an ENDORSE at
                        the SAME effective date: transactions sharing an
                        effective date are ordered by policy version (booking
                        order), so endorse-then-cancel floors off the corrected
                        figures and reinstate-then-endorse reprices the restored
                        term prospectively.
                componentMissing:
                  summary: A supplied contract drops an existing charge
                  value:
                    error:
                      code: InvalidRequest
                      message: >-
                        Cancel transaction is missing the existing pricing
                        component <kind 'Taxes', group 'Policy Invoice', label
                        'Surplus Lines Tax'>. Every key the policy already
                        carries must stay present at its server-derived value —
                        the cancel floors each existing charge at what it has
                        earned. Removing a charge is an ENDORSE, not a lifecycle
                        transaction.
                additionNotImmediate:
                  summary: An added charge omits the immediate earning basis
                  value:
                    error:
                      code: InvalidRequest
                      message: >-
                        Cancel transaction adds the pricing component <kind
                        'Fees', group 'Policy Invoice', label 'Cancellation
                        Fee'> without earningBasis 'fully-earned-at-inception'.
                        A charge a lifecycle transaction creates is recognized
                        on its effective date; a pro-rata addition would
                        schedule coverage the transaction is not selling.
                        Absent, null and 'pro-rata' all mean pro-rata, so an
                        addition must state the immediate basis explicitly.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Policy or an invoice referenced by invoicePlan not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                policyNotFound:
                  summary: Policy does not exist
                  value:
                    error:
                      code: NOT_FOUND
                      message: Policy 550e8400-e29b-41d4-a716-446655440099 not found
        '409':
          description: >-
            A void watermark in invoicePlan is stale; no policy version or
            invoice action was committed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: >-
            invoicePlan failed policy-invoice binding, conservation,
            payment-lock, or restating preconditions; no policy version or
            invoice action was committed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  parameters:
    companyId:
      name: companyId
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Company identifier
    policyIdPath:
      name: policyId
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Policy identifier
  schemas:
    PolicyInvoiceTransactionPlan:
      type: object
      additionalProperties: false
      required:
        - incurredDate
        - voidInvoices
        - creates
      properties:
        incurredDate:
          type: string
          format: date
          description: >-
            Explicit recognition date for non-scheduled creates. It is never
            inferred from a transaction effective date or the server clock.
        voidInvoices:
          type: array
          items:
            $ref: '#/components/schemas/PolicyInvoiceBatchVoid'
        creates:
          type: array
          items:
            $ref: '#/components/schemas/PolicyInvoiceBatchCreate'
      description: >-
        A fully explicit, detached policy-invoice batch. Existing invoices not
        named in voidInvoices are kept. The complete kept-plus-created set must
        conserve every bound component of the policy's current pricing contract.
    PolicyTransactionVersionResponse:
      type: object
      description: >
        Response returned by policy transaction endpoints. Contains the policy
        version

        produced by the transaction, including all derived segments.
      required:
        - policyId
        - policyVersion
        - transactionId
        - startDate
        - endDate
        - createdAt
        - segments
      properties:
        policyId:
          type: string
          format: uuid
          description: Policy identifier
        policyVersion:
          type: integer
          description: Sequential version number produced by this transaction
        transactionId:
          type: string
          format: uuid
          description: Identifier of the transaction that produced this version
        startDate:
          type: string
          format: date
          description: Policy term start date (ISO 8601)
        endDate:
          type: string
          format: date
          description: Policy term end date (ISO 8601)
        createdAt:
          type: string
          format: date-time
          description: When the transaction was created (ISO 8601)
        primaryInsuredName:
          type: string
          nullable: true
          description: >
            Plain-text primary-insured name, read from the policy's own

            `primaryInsuredName` field — the source of truth for the primary
            insured.

            Reported as of the END of

            the term, so a policy whose insured changed mid-term returns the
            later name;

            `segments[]` carries the per-segment history. Null only when the
            version

            carries no policy data.
        primaryInsuredId:
          type: string
          nullable: true
          description: |
            Id of the entity behind `primaryInsuredName`. Null when the policy's
            configuration does not populate it.
        policyNumber:
          type: string
          nullable: true
          description: >
            The policy number, read from the policy's own `policyNumber` field —
            the

            source of truth, invariant across the whole term. Null only when the
            version

            carries no policy data.
        policyStartDate:
          type: object
          nullable: true
          description: >
            Policy term start date as the structured date object, read from the
            policy's

            own `policyStartDate` field — the source of truth, invariant across
            the whole

            term. This is **not** `startDate` above: that is the ISO

            span this version covers, which a cancellation makes shorter than
            the term.

            Null only when the version carries no policy data.
          allOf:
            - $ref: '#/components/schemas/Fmv1Date'
        policyEndDate:
          type: object
          nullable: true
          description: >
            Policy term end date as the structured date object, read from the
            policy's own

            `policyEndDate` field — the source of truth, invariant across the
            whole term.

            This is **not** `endDate` above: that is the ISO

            span this version covers. Null only when the version carries no
            policy data.
          allOf:
            - $ref: '#/components/schemas/Fmv1Date'
        fullTermPricingInfo:
          type: object
          nullable: true
          description: >
            The policy's full-term pricing contract, hoisted as a read-once
            convenience

            (also duplicated in every segment): `pricingComponents` (each

            `{label, group, kind, value[, earningBasis]}`) plus the five
            server-computed,

            read-only rollups (`premium`, `taxes`, `fees`, `brokerCommission`,

            `programCommission` — each the sum of its kind's components).
          additionalProperties: true
        fullTermPolicyRatingResult:
          type: object
          nullable: true
          description: >
            Derived canonical policy-level rating result for the full term,
            hoisted as a

            read-once convenience (also duplicated in every segment).
            Element-level rating

            output (`crossSegmentRatingOutputs`) stays inline at its host and is
            not hoisted.
          additionalProperties: true
        segments:
          type: array
          description: >
            Derived segments for this policy version. Each segment represents a
            maximal contiguous

            date range where policy state is identical. Adjacent segments with
            identical data are

            automatically merged.
          items:
            $ref: '#/components/schemas/PolicyTransactionSegmentResponse'
    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
    PolicyInvoiceBatchVoid:
      type: object
      additionalProperties: false
      required:
        - invoiceId
        - headJournalId
      properties:
        invoiceId:
          type: string
          format: uuid
        headJournalId:
          type: string
          format: uuid
          description: >-
            The invoice watermark read while composing the plan. A stale
            watermark rejects the whole batch with 409.
    PolicyInvoiceBatchCreate:
      type: object
      additionalProperties: false
      required:
        - group
        - lineItems
      properties:
        group:
          type: string
          minLength: 1
          description: Policy invoice type name, matching a pricing component group.
        dueDate:
          type: string
          format: date
        scheduledDate:
          type: string
          format: date
          description: >-
            Installment recognition anchor. When present, incurredDate for this
            invoice is this date and the invoice remains scheduled until it
            arrives.
        payeeId:
          type: string
          format: uuid
          description: Canonical Person, Organization, or Exposure entity billed or paid.
        memo:
          type: string
        lineItems:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/PolicyInvoiceBatchCreateLine'
    Fmv1Date:
      type: object
      description: >
        Field Model V1 object primitive representing a calendar date with an

        explicit timezone.


        `day`, `month`, and `year` are required when this object is provided in
        a

        write payload; `timezone` is optional. Partial date components are

        rejected with `400`.


        **Two accepted input shapes** — the API canonicalizes both into the

        internal `{ day, month, year, timezone }` form before storage:

        * `{ date: "YYYY-MM-DD", timezone: "America/New_York" }` — ISO form

        * `{ day, month, year, timezone }` — DMY form (returned in responses)


        Day, month, and year are 1-based integers. `timezone` is an IANA tz

        database identifier (e.g. "America/New_York", "UTC").
      required:
        - day
        - month
        - year
      properties:
        day:
          type: integer
          minimum: 1
          maximum: 31
          description: Day of month, 1-based (1-31)
        month:
          type: integer
          minimum: 1
          maximum: 12
          description: Month, 1-based (1=January, 12=December)
        year:
          type: integer
          description: Four-digit calendar year (e.g. 2026)
        timezone:
          type: string
          description: IANA timezone identifier (e.g. "America/New_York", "UTC")
    PolicyTransactionSegmentResponse:
      type: object
      description: >
        A derived policy segment representing a date range where policy state is
        identical.

        Contains the full policy data (policy-level fields and nested exposures)
        for this period.
      required:
        - startDate
        - endDate
        - data
      properties:
        startDate:
          type: string
          format: date
          description: Segment start date (ISO 8601)
        endDate:
          type: string
          format: date
          description: Segment end date (ISO 8601)
        data:
          type: object
          description: >
            Policy state for this segment: the policy-level fields and their
            nested

            exposure containers, at the top level of this object.


            Before 2026-08-05 this member was named `fieldModelV1Data` and
            wrapped the

            same fields in an extra `policy` key. Read `data.policyNumber` where
            you

            read `fieldModelV1Data.policy.policyNumber`.
          additionalProperties: true
    PolicyInvoiceBatchCreateLine:
      type: object
      additionalProperties: false
      required:
        - label
        - amountCents
      properties:
        label:
          type: string
          minLength: 1
          description: Line-item label from the pricing contract and bound invoice type.
        amountCents:
          type: integer
          description: Signed integer cents in the configured line-item type's own frame.
        memo:
          type: string
  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
    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.

````