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

# Start Data Validation Run

> Starts a **data validation run**: a background scan that reads **every stored
record** of every entity type for this company and classifies each one against
a configuration. Returns immediately with a `runId` — poll
**`GET /configuration/data-validation-runs/{runId}`** for progress and results.

This is the data-aware companion to **validate**. **Validate** checks whether a
configuration is *well-formed* and reads no records at all; a run checks whether
the records you have already stored still *fit* a configuration.

### The request body is OPTIONAL — that is how you choose what to scan

- **Send no body at all** → the run scans the company's **live
  configuration**. This answers "do my current records still conform to the
  config I am running right now?" Nothing is read from the request; the live
  configuration is snapshotted onto the run. An explicit JSON `null` body, and
  an empty JSON object, are accepted the same way and also mean a live scan.
- **Send a COMPLETE configuration body** (the same shape **import** and
  **validate** accept) → the run scans that **candidate** configuration.
  Nothing is persisted as the company's configuration — the body is only
  snapshotted onto the run — so this is a safe way to ask "which of my records
  would break if I imported this?" before importing it.

Either way the configuration is snapshotted onto the run when it starts, so a
run's verdict cannot drift: a configuration import that lands mid-scan does not
change what the scan is measuring against.

A delta/patch body is rejected with a `400`. Typed changes go to the **patch**
endpoint (`POST .../configuration/patch`); this endpoint, like **import** and
**validate**, takes a complete body only.

### Send the body as `application/json`, or send nothing

A body that arrives and cannot be read as JSON is a `400`
(`UNREADABLE_REQUEST_BODY`) — it is **never** treated as an omitted body. This
covers a body sent with a `Content-Type` other than `application/json`, a body
sent with no `Content-Type` at all, and a body that is not valid JSON (a
truncated or corrupted payload). Failing loudly is the point: silently falling
back to a live scan would return a clean result for a candidate configuration
nobody ever measured.

`force` is a **query parameter only**. A `force` key inside the request body is
part of the configuration document, not a control flag, and is ignored — use
`?force=true`.

### Duplicate starts collapse onto the run already in flight

If a run is already in flight (`queued` or `running`) for this company and it is
scanning the **exact same configuration**, this endpoint does **not** start a
second scan. It returns `200` with that run's id and `outcome: "duplicate"`.

`duplicate` is a normal, successful answer and **not** an error — a client
looping starts, or retrying after a timeout, should treat it as "the scan you
asked for is already happening, poll this id". Configurations are matched by
content, so a body that differs anywhere is a different scan and starts a new
run. Pass **`?force=true`** to start a new run even when a matching one is in
flight.

### At most 3 runs in flight per company

A company may have **3** runs in flight (`queued` or `running`) at once. A 4th
**distinct** configuration is refused with a `429` whose body carries
`inFlightRunIds` — the ids of the runs already in flight — so you can poll those
instead of retrying blind. Wait for one to reach a terminal status, then start
again.

`?force=true` does **not** lift this limit; it only overrides the duplicate
check described above. The limit exists because scans share one background work
queue with everything else the platform runs (parsing, extraction, invoicing),
so an unbounded number of scans for one company would starve unrelated work.

**Required permission:** `company.configuration:export`

<Note>
This endpoint requires an API key created with the **FMV1_CONFIGURATION_MANAGER** role.
See [Authentication](/api-reference/authentication) for how to create API keys with specific roles.
</Note>




## OpenAPI

````yaml /openapi/generated-external-api.yaml post /api/v1/companies/{companyId}/configuration/data-validation-runs
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}/configuration/data-validation-runs:
    post:
      tags:
        - FMV1 Configuration
      summary: Start Data Validation Run
      description: >
        Starts a **data validation run**: a background scan that reads **every
        stored

        record** of every entity type for this company and classifies each one
        against

        a configuration. Returns immediately with a `runId` — poll

        **`GET /configuration/data-validation-runs/{runId}`** for progress and
        results.


        This is the data-aware companion to **validate**. **Validate** checks
        whether a

        configuration is *well-formed* and reads no records at all; a run checks
        whether

        the records you have already stored still *fit* a configuration.


        ### The request body is OPTIONAL — that is how you choose what to scan


        - **Send no body at all** → the run scans the company's **live
          configuration**. This answers "do my current records still conform to the
          config I am running right now?" Nothing is read from the request; the live
          configuration is snapshotted onto the run. An explicit JSON `null` body, and
          an empty JSON object, are accepted the same way and also mean a live scan.
        - **Send a COMPLETE configuration body** (the same shape **import** and
          **validate** accept) → the run scans that **candidate** configuration.
          Nothing is persisted as the company's configuration — the body is only
          snapshotted onto the run — so this is a safe way to ask "which of my records
          would break if I imported this?" before importing it.

        Either way the configuration is snapshotted onto the run when it starts,
        so a

        run's verdict cannot drift: a configuration import that lands mid-scan
        does not

        change what the scan is measuring against.


        A delta/patch body is rejected with a `400`. Typed changes go to the
        **patch**

        endpoint (`POST .../configuration/patch`); this endpoint, like
        **import** and

        **validate**, takes a complete body only.


        ### Send the body as `application/json`, or send nothing


        A body that arrives and cannot be read as JSON is a `400`

        (`UNREADABLE_REQUEST_BODY`) — it is **never** treated as an omitted
        body. This

        covers a body sent with a `Content-Type` other than `application/json`,
        a body

        sent with no `Content-Type` at all, and a body that is not valid JSON (a

        truncated or corrupted payload). Failing loudly is the point: silently
        falling

        back to a live scan would return a clean result for a candidate
        configuration

        nobody ever measured.


        `force` is a **query parameter only**. A `force` key inside the request
        body is

        part of the configuration document, not a control flag, and is ignored —
        use

        `?force=true`.


        ### Duplicate starts collapse onto the run already in flight


        If a run is already in flight (`queued` or `running`) for this company
        and it is

        scanning the **exact same configuration**, this endpoint does **not**
        start a

        second scan. It returns `200` with that run's id and `outcome:
        "duplicate"`.


        `duplicate` is a normal, successful answer and **not** an error — a
        client

        looping starts, or retrying after a timeout, should treat it as "the
        scan you

        asked for is already happening, poll this id". Configurations are
        matched by

        content, so a body that differs anywhere is a different scan and starts
        a new

        run. Pass **`?force=true`** to start a new run even when a matching one
        is in

        flight.


        ### At most 3 runs in flight per company


        A company may have **3** runs in flight (`queued` or `running`) at once.
        A 4th

        **distinct** configuration is refused with a `429` whose body carries

        `inFlightRunIds` — the ids of the runs already in flight — so you can
        poll those

        instead of retrying blind. Wait for one to reach a terminal status, then
        start

        again.


        `?force=true` does **not** lift this limit; it only overrides the
        duplicate

        check described above. The limit exists because scans share one
        background work

        queue with everything else the platform runs (parsing, extraction,
        invoicing),

        so an unbounded number of scans for one company would starve unrelated
        work.


        **Required permission:** `company.configuration:export`


        <Note>

        This endpoint requires an API key created with the
        **FMV1_CONFIGURATION_MANAGER** role.

        See [Authentication](/api-reference/authentication) for how to create
        API keys with specific roles.

        </Note>
      operationId: startFmv1DataValidationRun
      parameters:
        - $ref: '#/components/parameters/companyId'
        - name: force
          in: query
          required: false
          schema:
            type: boolean
            default: false
          description: >
            When `true`, start a new run even if a run is already in flight for
            this

            company scanning the same configuration — i.e. skip the duplicate
            check and

            always mint a fresh run. Any value other than `true` (including
            omission)

            is treated as `false`.


            This does **not** raise the limit of 3 runs in flight per company: a

            `?force=true` request past that limit still gets a `429`.


            Unlike `force` on **import**, this needs no additional permission
            and

            destroys nothing — the only cost of forcing is a redundant scan.
      requestBody:
        required: false
        description: >
          OPTIONAL. **Omit the body entirely** to scan the company's **live**

          configuration — that is the documented way to ask for a live scan, and
          no

          query flag is needed for it.


          When a body IS sent it must be a COMPLETE configuration document
          matching the

          schema below, and the run scans that **candidate** configuration
          instead;

          nothing is persisted. A delta/patch body is rejected with a `400`, and
          so is a

          body that cannot be read as JSON.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfigurationJsonImportRequest'
            examples:
              candidateConfig:
                summary: >-
                  Scan a candidate configuration without importing it (omit the
                  body entirely to scan the live configuration instead)
                value:
                  fields: []
                  pages: []
                  cards: []
                  cardPageRelationships: []
                  optionSetTypes: []
                  objectTypes: []
                  optionSets: []
                  objects: []
                  objectPrimitives: []
                  fieldLocations: []
                  ratingWorkflows: []
                  entityInvariants: []
      responses:
        '200':
          description: >
            The run to poll. `outcome: "enqueued"` means a new scan was started;

            `outcome: "duplicate"` means `runId` is a run already in flight
            scanning

            this same configuration and no second scan was started.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataValidationRunStartResponse'
              examples:
                enqueued:
                  summary: A new run was started
                  value:
                    runId: 7c9e6679-7425-40de-944b-e07fc1f90ae7
                    outcome: enqueued
                duplicate:
                  summary: A run scanning this same configuration was already in flight
                  value:
                    runId: 7c9e6679-7425-40de-944b-e07fc1f90ae7
                    outcome: duplicate
        '400':
          description: Bad Request - Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                patchGrammarRemoved:
                  summary: A delta/patch body was sent instead of a complete config
                  value:
                    error:
                      code: ConfigPatchGrammarRemoved
                      message: >-
                        This endpoint accepts a complete configuration body
                        only. The delta/patch grammar was removed — send typed
                        changes to POST
                        /api/v1/companies/{companyId}/configuration/patch
                        instead.
                invalidProperties:
                  summary: Request body shape validation failure
                  value:
                    error:
                      code: InvalidProperties
                      message: 'Invalid input: expected array, received string'
                      details:
                        - field: fields
                          message: 'Invalid input: expected array, received string'
                unreadableRequestBody:
                  summary: >-
                    A body was sent that could not be read as JSON (wrong or
                    missing Content-Type, or invalid JSON)
                  value:
                    error:
                      code: UNREADABLE_REQUEST_BODY
                      message: >-
                        A request body was sent with Content-Type "text/plain",
                        and this endpoint reads only application/json. Send the
                        COMPLETE configuration to scan as an `application/json`
                        body, or send NO body at all to scan this company's live
                        configuration. A body that cannot be read is never
                        treated as an omitted one, because that would scan the
                        live configuration and report a clean result for a
                        candidate nobody measured.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          description: >
            Too Many Requests — this company already has 3 data-validation runs
            in

            flight, so no run was started. The body carries `inFlightRunIds`:
            poll those

            runs and start again once one reaches a terminal status.
            `?force=true` does

            not bypass this.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataValidationRunsTooManyInFlightResponse'
              examples:
                tooManyInFlight:
                  summary: The per-company in-flight limit was reached
                  value:
                    error:
                      code: TooManyActiveDataValidationRuns
                      message: >-
                        This company already has 3 data-validation runs in
                        flight (the limit is 3). Poll the runs already in
                        flight, or wait for one to finish before starting
                        another.
                      inFlightRunIds:
                        - 7c9e6679-7425-40de-944b-e07fc1f90ae7
                        - 1f0c9a52-2f8d-4a3e-9b7a-2c4f6d8e0a11
                        - 3b8d4f61-5a2c-4e7b-8d1f-9c0e2a4b6d83
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: >
            Service Unavailable — another start for this company was holding the

            admission lock, so this request gave up waiting for it. **Nothing
            was

            started** and no run was created. Retry in a few seconds. Starting
            many runs

            for one company concurrently is what provokes this; start them one
            at a

            time.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                admissionBusy:
                  summary: A concurrent start held the per-company admission lock
                  value:
                    error:
                      code: DataValidationAdmissionBusy
                      message: >-
                        Could not start a data-validation run: another start for
                        this company is holding the admission lock. Nothing was
                        started. Retry in a few seconds.
      security:
        - BearerAuth: []
        - ApiKeyAuth: []
components:
  parameters:
    companyId:
      name: companyId
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Company identifier
  schemas:
    ConfigurationJsonImportRequest:
      type: object
      description: >
        Structured FMV1 configuration body. This is both the body the **import**

        endpoint accepts and the body the **export** endpoint returns, so

        `export` → (edit) → `import` is a lossless round-trip. Each property is
        an

        array of config rows; the import runs them through the full validate →

        compare → apply pipeline.


        The per-element schemas below are the canonical FMV1 configuration model
        and

        the single source of truth for this payload.
      required:
        - fields
        - pages
        - cards
        - cardPageRelationships
        - optionSetTypes
        - objectTypes
        - optionSets
        - objects
        - objectPrimitives
        - fieldLocations
        - ratingWorkflows
        - entityInvariants
      properties:
        fields:
          type: array
          description: Field definitions, keyed by entity + reference id.
          items:
            $ref: '#/components/schemas/Fmv1ConfigField'
        pages:
          type: array
          description: Page definitions.
          items:
            $ref: '#/components/schemas/Fmv1ConfigPage'
        cards:
          type: array
          description: Card definitions.
          items:
            $ref: '#/components/schemas/Fmv1ConfigCard'
        cardPageRelationships:
          type: array
          description: Placements of cards onto pages.
          items:
            $ref: '#/components/schemas/Fmv1ConfigCardPageRelationship'
        optionSetTypes:
          type: array
          description: Option-set type declarations.
          items:
            $ref: '#/components/schemas/Fmv1ConfigOptionSetType'
        objectTypes:
          type: array
          description: Custom-object type declarations.
          items:
            $ref: '#/components/schemas/Fmv1ConfigObjectType'
        optionSets:
          type: array
          description: Option sets and their options.
          items:
            $ref: '#/components/schemas/Fmv1ConfigOptionSet'
        objects:
          type: array
          description: Custom-object sub-field definitions, joined to object types.
          items:
            $ref: '#/components/schemas/Fmv1ConfigObjectDefinition'
        objectPrimitives:
          type: array
          description: Object-primitive sub-field definitions (Address / Date / Currency).
          items:
            $ref: '#/components/schemas/Fmv1ConfigObjectDefinition'
        fieldLocations:
          type: array
          description: Field placements (the layout) onto cards.
          items:
            $ref: '#/components/schemas/Fmv1ConfigFieldLocation'
        ratingWorkflows:
          type: array
          description: Rating workflow definitions.
          items:
            $ref: '#/components/schemas/Fmv1ConfigRatingWorkflow'
        entityInvariants:
          type: array
          description: Per-entity invariant conditions enforced on every write.
          items:
            $ref: '#/components/schemas/Fmv1ConfigEntityInvariant'
        formLogicRules:
          type: array
          description: |
            Forms-logic rules (quote-flow auto-add rules). Optional — existing
            payloads predate the "Forms" tab; absent ⇒ no rules.
          items:
            $ref: '#/components/schemas/Fmv1ConfigFormLogicRule'
        smartTags:
          type: array
          description: >
            Smart tags — the named values resolved into generated documents.
            Optional:

            existing payloads predate the slice, and absent ⇒ no smart tags,
            which is

            also how a company that has not yet moved to this format is
            recognised.


            Omitted from an export when the company has none, rather than
            emitted as an

            empty array.
          items:
            $ref: '#/components/schemas/Fmv1ConfigSmartTag'
        exportSurfaces:
          type: array
          description: >
            Declared export columns — the tenant-facing column set of each
            export

            surface (the seven entity exports plus the bordereau). Optional:
            existing

            payloads predate the slice, and absent ⇒ no declared columns, which
            is also

            how a surface that still offers every configured field is
            recognised.


            Activation is per surface: a surface with at least one row here
            resolves its

            whole tenant column set through those rows, in the order they
            appear; a

            surface with none behaves exactly as it did before this section
            existed.


            Omitted from an export when the company has declared none, rather
            than

            emitted as an empty array.
          items:
            $ref: '#/components/schemas/Fmv1ConfigExportSurfaceRow'
    DataValidationRunStartResponse:
      type: object
      description: >
        The run to poll after starting a data-validation run. `outcome` is a
        policy

        verdict delivered as a successful `200`, not an error: `duplicate` means
        a run

        scanning this same configuration was already in flight, so `runId` is
        that run

        and no second scan was started.
      required:
        - runId
        - outcome
      properties:
        runId:
          type: string
          format: uuid
          description: >-
            The run to poll via GET
            /api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}
        outcome:
          type: string
          enum:
            - enqueued
            - duplicate
          description: >-
            `enqueued` when a new scan was started; `duplicate` when a run
            already in flight was scanning this same configuration (pass
            ?force=true to start a new run anyway)
    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
    DataValidationRunsTooManyInFlightResponse:
      type: object
      description: >
        The `429` body when a company already has the maximum number of
        data-validation

        runs in flight. It is the standard error envelope plus `inFlightRunIds`,
        so a

        client can poll the runs that are already running instead of retrying
        blind.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - inFlightRunIds
          properties:
            code:
              type: string
              description: Machine-readable error code
              example: TooManyActiveDataValidationRuns
            message:
              type: string
              description: Human-readable error message
            userMessages:
              type: array
              description: Clean, verbatim-displayable messages
              items:
                type: string
            inFlightRunIds:
              type: array
              description: >-
                The ids of the runs already in flight (`queued` or `running`)
                for this company. Poll these instead of retrying; start again
                once one reaches a terminal status.
              items:
                type: string
                format: uuid
    Fmv1ConfigField:
      type: object
      description: >
        One top-level field definition (a parsed *Fields* sheet row), keyed by
        entity

        + reference id. Mirrors the `FieldDefinition` shape.
      required:
        - entitySelector
        - referenceId
        - type
        - typeArgs
        - typeInfo
        - cardinality
      properties:
        entitySelector:
          type: string
          description: The entity the field belongs to (e.g. "Exposure").
        referenceId:
          type: string
          description: Stable per-entity reference id for the field.
        definition:
          type: string
          description: Optional human description of the field.
        type:
          type: string
          description: The field-type kind discriminant.
        typeArgs:
          type: string
          description: >-
            The type-specific argument string (JSON of the kind's `args`, or
            empty for argless kinds).
        typeInfo:
          $ref: '#/components/schemas/Fmv1ConfigFieldTypeInfo'
        cardinality:
          type: string
          description: >-
            Single value or list. Authoritative for the field's Single/List
            axis.
          enum:
            - Single
            - List
        calculatedValue:
          type: string
          description: Optional calculated-value expression.
        exportLabel:
          type: string
          minLength: 1
          maxLength: 200
          description: >
            Optional column-header override for exports (entity export +
            bordereau).

            Must be unique per entity (case-insensitive) and must not collide
            with a

            system export column or a fixed bordereau header.
        generated:
          type: string
          enum:
            - allocated
            - composed
          description: >
            Declares the field server-generated. Client-supplied values are

            rejected on every ordinary write surface ("echo, never originate")
            and

            the value freezes once computed on a Policy. `allocated` = issued by
            an

            allocator (e.g. a sequence mint) and carried forward across a
            renewal

            chain; `composed` = deterministically recomputed from other fields

            (requires a calculated value).
        termInvariant:
          type: boolean
          description: >
            Declares the field term-invariant: it holds one value across every

            segment of a policy version, so a write to it applies to the whole
            term.

            Accepted on `Policy` field rows only — no other entity is segmented
            —

            and `true` is rejected together with a calculated value, because the

            calculated-value resolver runs per segment. Absent means `false`.
        systemManaged:
          type: boolean
          description: >
            Declares the field system-managed: the platform owns its canonical
            value.

            Client-supplied values are silently dropped on every generic write
            channel;

            the write continues and the platform-maintained value wins. Omit the

            property for caller-owned fields.
    Fmv1ConfigPage:
      type: object
      description: >-
        One page definition (a parsed *Onboarding* sheet page row). Mirrors the
        `Page` shape.
      required:
        - name
        - pageType
      properties:
        name:
          type: string
          description: Human-readable page name.
        pageType:
          type: string
          description: How the page renders.
          enum:
            - input
            - viewEdit
            - readonly
        pageKey:
          type: string
          description: >-
            Optional stable key for the page (referenced by card-page
            relationships).
        entity:
          type: string
          description: Optional entity-in-context for the page.
    Fmv1ConfigCard:
      type: object
      description: >-
        One card definition (a parsed *Cards* sheet band-B row). Mirrors the
        `Card` shape.
      required:
        - entity
        - name
      properties:
        entity:
          type: string
          description: Entity-in-context for the card.
        name:
          type: string
          description: Human-readable card name.
        cardKey:
          type: string
          description: >-
            Optional stable key for the card (referenced by card-page
            relationships and field locations).
        cardType:
          type: string
          description: Optional card render type (e.g. "input", "viewEdit", "readonly").
        columnCount:
          type: number
          description: Optional number of layout columns on the card.
        displayCondition:
          type: string
          description: >
            Optional JEXL condition gating whether the card is shown — the

            expression string of the card's `{ expression }` display condition
            (the

            same shape carried by a field location's `displayCondition`).
            Omitted

            when the card has no display condition.
        cardTitleExpression:
          type: string
          description: Optional expression computing the card's title at render time.
    Fmv1ConfigCardPageRelationship:
      type: object
      description: |
        A static placement of a card onto a page (band C of the *Cards* sheet).
        Many-to-many; `displayPosition` orders cards within a page. Mirrors the
        `CardPageRelationship` shape.
      required:
        - cardKey
        - pageKey
        - displayPosition
      properties:
        cardKey:
          type: string
          description: The placed card's `cardKey`.
        pageKey:
          type: string
          description: The hosting page's `pageKey`.
        displayPosition:
          type: number
          description: Ordering of this card within the page.
    Fmv1ConfigOptionSetType:
      type: object
      description: An option-set type declaration. Mirrors the `OptionSetType` shape.
      required:
        - name
      properties:
        name:
          type: string
          description: The option-set type name.
    Fmv1ConfigObjectType:
      type: object
      description: A custom-object type declaration. Mirrors the `ObjectType` shape.
      required:
        - key
        - name
        - pluralizedName
      properties:
        key:
          type: string
          description: Stable key for the custom-object type.
        name:
          type: string
          description: Singular display name.
        pluralizedName:
          type: string
          description: Plural display name.
        displayNameExpression:
          type: string
          description: Optional expression computing an instance's display name.
    Fmv1ConfigOptionSet:
      type: object
      description: An option set and its options. Mirrors the `OptionSet` shape.
      required:
        - name
        - options
      properties:
        name:
          type: string
          description: The option-set name.
        options:
          type: array
          description: The options in the set.
          items:
            type: object
            required:
              - label
              - key
              - groupLabel
            properties:
              label:
                type: string
                description: Human-readable option label.
              key:
                type: string
                description: Stable option key (the stored value).
              groupLabel:
                type: string
                description: Group label for visually grouping options (may be empty).
    Fmv1ConfigObjectDefinition:
      type: object
      description: >
        A custom-object (or object-primitive) definition: a named object joined
        to

        its sub-field definitions. Mirrors the `ObjectDefinition` shape — the
        element

        type of both the `objects` and `objectPrimitives` arrays.
      required:
        - name
        - subFields
      properties:
        name:
          type: string
          description: >-
            The object's name (the object-type key for `objects`, or the
            primitive name for `objectPrimitives`).
        subFields:
          type: array
          description: The object's sub-field definitions.
          items:
            $ref: '#/components/schemas/Fmv1ConfigSubField'
    Fmv1ConfigFieldLocation:
      type: object
      description: >
        One field placement (the layout) onto a card — a parsed *Field
        Locations*

        sheet row. Mirrors the `FieldLocationDefinition` shape. The nullable

        presentation fields (`inputModality`, `inputModalityArgs`,
        `displayFormat`,

        `displayFormatArgs`) carry an explicit `null` on the wire (the parser
        emits

        `null`, not omission).
      required:
        - cardKey
        - entity
        - fieldReferenceId
        - label
        - sectionPosition
        - inputModality
        - inputModalityArgs
        - displayFormat
        - displayFormatArgs
      properties:
        cardKey:
          type: string
          description: The card this field is placed on (its `cardKey`).
        entity:
          type: string
          description: The entity-in-context for the placement.
        fieldReferenceId:
          type: string
          description: The placed field's `referenceId`.
        subFieldReferenceId:
          type: string
          description: >-
            Optional sub-field reference id (dot-notation placement into a
            custom-object field).
        label:
          type: string
          description: The label rendered for the field at this location.
        sectionPosition:
          type: object
          description: The 1-based grid coordinates of the placement on the card.
          required:
            - row
            - column
            - rowSpan
            - columnSpan
            - zAxis
          properties:
            row:
              type: integer
              minimum: 1
              description: 1-based grid row.
            column:
              type: integer
              minimum: 1
              description: 1-based grid column.
            rowSpan:
              type: integer
              minimum: 1
              description: Number of grid rows spanned.
            columnSpan:
              type: integer
              minimum: 1
              description: Number of grid columns spanned.
            zAxis:
              type: integer
              minimum: 1
              description: Stacking order.
        inputModality:
          type: string
          nullable: true
          description: >
            The edit-mode input control, or `null` when the placement is
            read-only /

            has no input. One of the framework input modalities.
          enum:
            - Text Box
            - Text Area
            - Comma Separated List
            - Phone Number (US/Canada)
            - Phone Number with Country Code
            - Exposure Name Picker
            - Number Box
            - Percentage Whole Number
            - Percentage Decimal
            - Comma Separated Numbers
            - Dollar Number
            - Switch
            - Checkbox
            - Labeled Dropdown
            - Single Select Dropdown
            - Multi Select Dropdown
            - Single Select Radio
            - Single Select Checkbox
            - Multi Select Checkbox
            - Address Typeahead
            - Currency Input
            - Custom Object Card List
            - Custom Object Table Input
            - Date Picker
            - String Or Number Input
            - ThirdPartyTypeaheadSingle
            - Relation Key
            - Relation Key Inline
            - Relation Keys
            - Relation Keys Inline
            - Embedded Exposure Single Select Dropdown
            - Embedded Exposure Table
            - Embedded Exposure Table Input
        inputModalityArgs:
          type: object
          nullable: true
          additionalProperties: true
          description: Modality-specific arguments, or `null` when none apply.
        displayFormat:
          type: string
          nullable: true
          description: >
            The read-mode display format, or `null` when none applies. One of
            the

            framework display formats.
          enum:
            - Plain Text
            - Comma Separated List
            - Phone Number
            - Hyperlink
            - Plain Number
            - Percentage
            - Dollar Number - Cents
            - Dollar Number - Nearest Dollar
            - Dollar Number - Optional Cents
            - Yes/No
            - True/False
            - Switch
            - Checkbox
            - Labeled
            - Date Format - mm/dd/yyyy
            - Date Format - yyyy-mm-dd
            - Address Format
            - Currency Format
            - Custom Object Card List
            - Custom Object Table Display
            - String Or Number Display
            - Pricing Table
            - Relation Link
            - Relation Links
            - Relation Card List
            - Relation Table
            - Embedded Exposure Card List
            - Embedded Exposure Table Display
        displayFormatArgs:
          type: object
          nullable: true
          additionalProperties: true
          description: Display-format-specific arguments, or `null` when none apply.
        displayCondition:
          type: string
          description: >-
            Optional JEXL condition gating whether the field is shown at this
            location.
        requiredCondition:
          type: string
          description: Optional JEXL condition making the field required at this location.
        autoSetCalculation:
          type: string
          description: Optional expression whose result is auto-written to the field.
        autoSetTrigger:
          type: string
          description: When the auto-set calculation writes its value to the target field.
          enum:
            - UNSET
            - UNSET_OR_DEPENDENCY_CHANGE
            - VISIBLE_OR_DEPENDENCY_CHANGE
        autoSetConfirmation:
          type: string
          description: Optional confirmation message shown before an auto-set overwrite.
        autoSetScope:
          type: string
          description: >
            Where the auto-set rule stays live in the client. `page` (the
            default when

            omitted) evaluates the rule only while its card's page is the active

            surface; `entity-global` evaluates it on every surface that edits
            the

            entity, for the whole editing session. Client-side only: server-side

            rating always evaluates all configured quote-level rules regardless
            of

            scope. `entity-global` cannot be combined with `autoSetConfirmation`
            or

            the `VISIBLE_OR_DEPENDENCY_CHANGE` trigger. On item cards (cards
            rendered

            per list item), `entity-global` installs the card's per-item rules
            for

            the whole session and is supported only when every list rendering
            the

            card is a top-level List field on the entity — nested and relation
            lists

            are rejected at validation.
          enum:
            - page
            - entity-global
    Fmv1ConfigRatingWorkflow:
      type: object
      description: >
        A single named rating workflow: a name plus an ordered list of stages. A

        company has many of these (no versions); the quote flow selects one by
        name

        at rating time. Mirrors the `RatingWorkflow` shape.
      required:
        - name
        - stages
      properties:
        name:
          type: string
          description: >-
            The workflow name (non-empty; selected by the quote flow at rating
            time).
        stages:
          type: array
          description: The ordered rating stages.
          items:
            type: object
            required:
              - stageType
              - raterSpec
              - callOncePerPath
              - outputPath
            properties:
              stageType:
                type: string
                description: >-
                  The stage the rater runs in. Must agree with the rater's own
                  stage.
                enum:
                  - segment
                  - fullTerm
              raterSpec:
                type: object
                description: The rater to run and its opaque, rater-specific arguments.
                required:
                  - raterType
                  - args
                  - raterDebugName
                properties:
                  raterType:
                    type: string
                    description: Canonical, stage-qualified rater identifier.
                    enum:
                      - segment-exposure-inscipher
                      - segment-policy-inscipher
                      - full-term-inscipher
                      - segment-inscipher-tax-plan-builder
                      - segment-policy-inscipher-tax-plan-builder
                      - full-term-inscipher-tax-plan-builder
                      - segment-google-sheets
                      - full-term-google-sheets
                      - segment-exposure-aufort
                      - segment-policy-aufort
                      - full-term-aufort
                      - segment-hyperformula
                      - full-term-hyperformula
                      - segment-stub
                      - full-term-stub
                  args:
                    type: object
                    additionalProperties: true
                    description: >-
                      Opaque, rater-specific configuration. Each rater reads the
                      keys it needs.
                  raterDebugName:
                    type: string
                    description: Human-readable name for the rater step (non-empty).
              callOncePerPath:
                type: string
                description: >
                  The `quote`-rooted dot-path naming the container to iterate
                  (e.g.

                  `quote` to run once for the whole quote, `quote.exposures` to
                  run

                  once per exposure). No brackets and no empty segments.
              outputPath:
                type: string
                description: Where the stage writes its computed output.
    Fmv1ConfigEntityInvariant:
      type: object
      description: >
        One per-entity invariant condition enforced on every write (a parsed
        *Entity

        Invariants* sheet row). Mirrors the `EntityInvariantDefinition` shape.
      required:
        - entity
        - condition
        - errorMessage
      properties:
        entity:
          type: string
          description: Top-level entity the invariant applies to (e.g. "Exposure").
        condition:
          type: string
          description: JEXL condition that must evaluate `true` on every write.
        errorMessage:
          type: string
          description: Error message returned when the condition fails.
    Fmv1ConfigFormLogicRule:
      type: object
      description: |
        One forms-logic rule — an auto-add rule for the quote flow. Mirrors the
        `FormLogicRuleDefinition` shape.
      required:
        - formNumber
        - rank
      properties:
        formNumber:
          type: string
          description: >
            The referenced form template's `FM-XXXX` number. Must match an
            existing,

            non-deleted Quote or Policy form template for the company.
        rank:
          type: integer
          minimum: 1
          description: |
            Evaluation / ordering rank — a positive integer, unique within the
            company. Lower ranks are evaluated (and added) first.
        addCondition:
          type: string
          description: >
            JEXL condition gating whether the form is auto-added to the quote
            flow.

            Empty or absent ⇒ the form is NOT auto-added (it stays available for

            manual selection); "always add" is an explicit `true`.
        transactionTypes:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            type: string
            enum:
              - newBusiness
              - endorsement
              - renewal
              - cancellation
              - reinstatement
          description: >
            The quote transaction types this rule applies to. Absent ⇒ new
            business

            only — what every rule authored before this field existed meant.
            When

            present the list must be non-empty and must not repeat a value.


            The scope is a filter, not a replacement for `addCondition`: a form
            that

            behaves differently across transaction types stays ONE rule (a
            rule's

            identity is its `formNumber`) whose condition branches on the
            `quoteType`

            field — for example

            `quoteType == "newBusiness" || (quoteType == "endorsement" &&
            someFieldChanged)`.
    Fmv1ConfigSmartTag:
      type: object
      description: >
        One smart tag — a named value destined to be resolved into a generated
        document

        at generation time. Mirrors the `SmartTagDefinition` shape.


        This section is the ONLY place a smart tag can be declared: a document

        generation resolves its anchors from these rows.


        A tag's identity is the (`formType`, `key`) PAIR: there is one row per
        form

        type, so a tag offered on several form types is several rows sharing a
        `key`

        and a `name`. Names are therefore unique within a form type, not
        company-wide.
      required:
        - key
        - name
        - formType
        - expression
        - valueType
        - cardinality
      properties:
        key:
          type: string
          minLength: 1
          description: >
            The tag's identifier within its form type, and the only input to the

            anchor written into a document template. Changing it changes the
            anchor,

            so an existing template stops resolving the tag.
        name:
          type: string
          minLength: 1
          maxLength: 200
          description: >
            Display name shown in the tag catalog and the insertion drawer. Must
            be

            unique within a form type (sibling rows on other form types share
            it).

            Must not be whitespace-only.
        formType:
          type: string
          enum:
            - event
            - quote-flow
            - quote-bind-flow
            - insured
          description: >
            The form type this tag belongs to. It determines which entity
            contexts

            `expression` may read: `event` reads `event`, `quote-flow` and

            `quote-bind-flow` both read `policy` (the quote on a quote form, the

            policy segment on a policy form), and `insured` reads `exposure`.


            `unknown` is not accepted — a form with no category cannot be
            generated,

            so a tag scoped to it could never resolve.
        expression:
          type: string
          minLength: 1
          description: >
            JEXL evaluated when a document is generated, against the form type's

            contexts, each addressed by name — for example `policy.quoteNumber`
            or

            `exposure.glClaimLimit ? exposure.glClaimLimit :
            policy.glClaimLimit`.


            Usually just a pointer at a field. Arithmetic belongs in a
            calculated

            field with the tag pointing at it; a computing expression here earns
            its

            keep only when the value must come from more than one context, or on
            an

            `insured` or `quote-flow` form, where field formulas are not
            recomputed at

            generation.


            A tag's expression may only READ. Functions that allocate a number
            from a

            counter, return a different value per call, read the wall clock, or
            need an

            entity being saved are rejected — a tag's expression also runs for
            the

            sidebar preview and the out-of-date check, so those would burn real

            numbers or mark every document permanently out of sync. Cross-record

            `LINKED_*` reads stay available.
        valueType:
          $ref: '#/components/schemas/Fmv1ConfigFieldTypeInfo'
          description: >
            The tag's own type, driving formatting (dates, currency, option
            labels,

            table columns). It no longer comes from a field, so it must carry

            everything the formatter needs.


            `Join`, `Pointer` and `SubHeader` are accepted but reported as a
            warning:

            they are not values, so the tag lists as unsupported and resolves to

            nothing on every document. They are not rejected outright because a
            tag

            migrated from a field of one of those types is existing
            configuration, and

            refusing it would fail the whole import rather than the one dead
            tag. An

            option set or custom object the configuration does not define IS
            rejected —

            that name resolves to nothing anywhere, so nothing can describe what
            the

            tag was meant to show.
        cardinality:
          type: string
          enum:
            - Single
            - List
          description: >
            Required, and not inferred from `valueType`: the formatter branches
            on it

            independently, so a `List` of objects renders as a table where a
            `Single`

            renders as text.
        emptyText:
          type: string
          nullable: true
          minLength: 1
          maxLength: 200
          description: >
            Text substituted when the tag resolves to nothing — for example

            "Generated when bound". Absent ⇒ the default behaviour: the anchor
            is left

            unresolved and a warning is reported. An explicit `null` is accepted
            and

            means the same as absent — it is what the get-configuration surface

            serves for a tag with no empty text. Must not be whitespace-only,
            which

            would substitute invisibly and report success.
    Fmv1ConfigExportSurfaceRow:
      type: object
      description: >
        One declared export column — a tenant-facing column on one export
        surface.

        Mirrors the `ExportSurfaceRowDefinition` shape.


        A surface is one column set: each of the seven entity exports, plus the

        bordereau. `bordereau` reads Policy fields like the `policy` surface
        does, but

        it is a separate report with its own fixed columns, so it is its own
        surface.


        A column's identity is the (`surface`, `key`) PAIR: there is one row per

        surface, so the same `key` on two surfaces is two independent columns
        that may

        disagree about everything else. Labels are therefore unique within a
        surface,

        not company-wide.


        ACTIVATION IS DATA-DRIVEN, and per surface. A surface with NO rows here
        offers

        and exports every configured field, exactly as it did before this
        section

        existed. A surface with at least one row resolves its whole tenant
        column set

        through these rows and offers nothing else. Declaring the first column
        for a

        surface is therefore a behaviour change for that surface, not an
        addition to

        it.


        COLUMN ORDER IS CANONICAL, not authored: the stored configuration sorts
        this

        section by (`surface`, `key`), so the order rows appear in a request
        body does

        not survive the round trip. Read a column's position off its key, never
        off its

        position here.
      required:
        - surface
        - key
        - label
        - expression
        - valueType
        - cardinality
      properties:
        surface:
          type: string
          enum:
            - event
            - exposure
            - quote
            - submission
            - person
            - organization
            - policy
            - bordereau
          description: >
            The surface this column belongs to. The seven entity-export values
            are the

            same slugs the export endpoints take in their paths. `expression`
            must

            reference a field on the surface's entity — `Policy` for both
            `policy` and

            `bordereau`.
        key:
          type: string
          minLength: 1
          description: >
            The column's identifier within its surface — what an export request
            names

            and what a saved column selection carries (on the bordereau, as

            `field:<key>`). Changing it changes which saved selections resolve.
        label:
          type: string
          minLength: 1
          maxLength: 1000
          description: >
            The column header the export renders. Must not be empty or

            whitespace-only, and is capped at 1000 characters — a sanity bound,
            not a

            style rule, because today's headers include prose-length labels
            resolved

            from form layouts.


            Two further rules are checked but do NOT reject the configuration;
            each

            returns a WARNING, because these headers are what the exports
            already

            print:

              - a label should be unique within its surface (case-insensitively);
              - a label should not name a column that surface already emits — a system
                export column (`id`, `ID`, `Created At`, `Updated At`) on any surface,
                plus the 12 fixed bordereau headers (`Policy Number`, `Insured Name`,
                …) on the `bordereau` surface only.

            Either produces two columns under one header, which is worth telling
            an

            author about but is not grounds for refusing a configuration that
            merely

            describes today's export.
        expression:
          type: string
          minLength: 1
          description: >
            What the column reads. In this version it must be exactly the
            `referenceId`

            of a field declared on the surface's entity — a bare reference, with
            no

            operators, paths, or function calls. Anything else is rejected at
            import.


            The narrowness is deliberate, not a gap: the export path projects a
            whole

            result page straight out of stored configuration data in one
            request, so

            evaluating an expression per row per column is not something it can
            do

            yet. Asynchronous exports are the prerequisite for a richer grammar;

            widening it later is additive, so declaring the column vocabulary
            now costs

            nothing.
        valueType:
          $ref: '#/components/schemas/Fmv1ConfigFieldTypeInfo'
          description: >
            The column's own type, driving cell formatting (dates, currency,
            option

            labels). It does not come from the referenced field, so it must
            carry

            everything the formatter needs.
        cardinality:
          type: string
          enum:
            - Single
            - List
          description: >
            Required, and not inferred from `valueType`: cell formatting
            branches on it

            independently, so a `List` renders differently from a `Single`.
    Fmv1ConfigFieldTypeInfo:
      type: object
      description: >
        Structured field-type model — a `kind` discriminant plus kind-specific

        `args`. The typed form of the legacy "Field Type" string. The field's

        Single/List axis is NOT here — it lives on the separate `cardinality`
        field,

        which is authoritative.


        Argless kinds (`Text`, `Number`, `Boolean`, `SubHeader`, `Date`,
        `Address`,

        `AddressV2`, `Currency`, `StringOrNumber`, `Percentage`,
        `EmbeddedExposure`)

        carry only `kind` (no `args`). Arg-bearing kinds carry a kind-specific
        `args`

        object:

        - `OptionSet` → `{ optionSetName }`

        - `Object` → `{ objectKey }`

        - `Join` → join args (`{ sourceEntity, targetEntity, sourceCardinality,
        targetCardinality, qualifier?, whoAmI }`)

        - `Pointer` → `{ targetEntity }`


        `EmbeddedExposure` embeds Exposure value(s) inline and takes no
        arguments —

        the embedded target is not configurable.
      required:
        - kind
      properties:
        kind:
          type: string
          description: The per-field type discriminant.
          enum:
            - Text
            - Number
            - Boolean
            - SubHeader
            - Date
            - Address
            - AddressV2
            - Currency
            - StringOrNumber
            - Percentage
            - OptionSet
            - Object
            - EmbeddedExposure
            - Join
            - Pointer
        args:
          type: object
          additionalProperties: true
          description: >
            Kind-specific argument payload. Omitted for argless kinds. For
            arg-bearing

            kinds the shape depends on `kind`:


            - `OptionSet`: `{ optionSetName: string }`

            - `Object`: `{ objectKey: string }`

            - `Join`: `{ sourceEntity: string, targetEntity: string,
            sourceCardinality: "1"|"N", targetCardinality: "1"|"N", qualifier?:
            string, whoAmI: "source"|"target" }`

            - `Pointer`: `{ targetEntity: string }`
    Fmv1ConfigSubField:
      type: object
      description: >
        A custom-object sub-field definition — pure data, structurally a subset
        of a

        top-level *Fields*-sheet field. Presentation (label / input modality /

        display format) and per-placement conditional logic are owned by the
        layout

        (cards + field locations), not by the sub-field definition. Mirrors the

        `SubField` shape.
      required:
        - referenceId
        - typeInfo
      properties:
        referenceId:
          type: string
          description: Stable reference id for the sub-field within its object.
        definition:
          type: string
          description: Optional human description of the sub-field.
        typeInfo:
          $ref: '#/components/schemas/Fmv1ConfigFieldTypeInfo'
        cardinality:
          type: string
          description: Optional Single value or list axis for the sub-field.
          enum:
            - Single
            - List
        generated:
          type: string
          enum:
            - allocated
            - composed
          description: >
            Declares the sub-field server-generated. Client-supplied values are

            rejected on every ordinary write surface ("echo, never originate")
            and

            the value freezes once computed on a Policy. `allocated` = issued by
            an

            allocator (e.g. a sequence mint); `composed` = deterministically

            recomputed from other fields (requires a calculated value).
        systemManaged:
          type: boolean
          description: >
            Declares the sub-field system-managed: the platform owns its
            canonical

            value. Client-supplied values are silently dropped on every generic
            write

            channel; the write continues and the platform-maintained value wins.
            Omit

            the property for caller-owned sub-fields.
  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.
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        User-principal OAuth 2.0 Bearer authentication. Send a user-scoped Auth0
        access token (audience = the app API audience) as `Authorization: Bearer
        <jwt>`. The request resolves to the user's identity and is authorized by
        their Role on the `{companyId}` in the path — the same role-based
        permissions the web app enforces. This is the path the MCP connector
        uses to act on a user's behalf; endpoints that accept it list both
        `BearerAuth` and `ApiKeyAuth`.

````