> ## 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 Bordereau Export Run

> Starts a **bordereau export run**: a background job that produces the FULL
transaction-level premium bordereau as a **CSV file**, however large it is.
Returns immediately with a `runId` — poll
**`GET /policies/bordereau/export-runs/{runId}`** until the run succeeds,
then fetch the file from
**`GET /policies/bordereau/export-runs/{runId}/download`** (CSV or Excel),
or deliver it to Google Drive as a spreadsheet via
**`POST /policies/bordereau/export-runs/{runId}/drive`**.

This run family replaces the retired synchronous bordereau CSV download
and Google Sheets export endpoints. Unlike those, a run has **no
`limit`/`offset` and no row cap**: it streams the whole result set into a
stored file in the background.

### The request body is OPTIONAL

The body is the bordereau export request:

- **`periodStart`** / **`periodEnd`** — scope to transactions whose
  `transactionTimestamp` falls within the half-open interval
  `[periodStart, periodEnd)`.
- **`actions`** — transaction types to include (e.g. only `NEW_BUSINESS`
  and `ENDORSE`).
- **`columns`** — an ordered array that COMPLETELY describes the output
  columns (see `BordereauColumnSpec`): fixed columns become selectable,
  omittable, and reorderable, and field columns interleave anywhere. **Omit
  it (or send no body at all) for the default column set (the fixed 12)**:
  Policy Number, Insured Name, Transaction Action, Policy Version,
  Effective Date, Transaction Timestamp, Policy Start Date, Policy End
  Date, Created At, Created By, Policy Premium, Policy Premium Change.
- **`filters`** — policy filters selecting which policies contribute
  transactions.
- **`sortBy`** / **`sortDirection`** — row order.

A body that arrives and cannot be read as JSON is a `400`
(`UNREADABLE_REQUEST_BODY`) — it is never treated as an omitted body,
because that would export different columns and rows than the ones asked
for.

The run snapshots the request AND the company's current configuration
version when it starts, so what a queued export produces cannot drift: a
configuration import landing mid-run does not change the file.

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

If a run is already in flight (`queued` or `running`) producing this **exact
export** — same request, same configuration version — this endpoint does not
start a second one: it returns `200` with that run's id and
`outcome: "duplicate"`. That is a normal, successful answer, not an error.
Pass **`?force=true`** to start a new run anyway. A run that already
**finished** is never deduplicated onto — re-asking for the same export is
how you refresh the file.

### At most 3 runs in flight per company

A company may have **3** export runs in flight at once — bordereau and
entity export runs share the limit. A 4th distinct request is refused with
a `429` whose body carries `inFlightRunIds` — poll those instead of
retrying blind. `?force=true` does **not** lift this limit.

**Required permission:** `company.policy:read`




## OpenAPI

````yaml /openapi/generated-external-api.yaml post /api/v1/companies/{companyId}/policies/bordereau/export-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}/policies/bordereau/export-runs:
    post:
      tags:
        - Field Model Bordereau
      summary: Start Bordereau Export Run
      description: >
        Starts a **bordereau export run**: a background job that produces the
        FULL

        transaction-level premium bordereau as a **CSV file**, however large it
        is.

        Returns immediately with a `runId` — poll

        **`GET /policies/bordereau/export-runs/{runId}`** until the run
        succeeds,

        then fetch the file from

        **`GET /policies/bordereau/export-runs/{runId}/download`** (CSV or
        Excel),

        or deliver it to Google Drive as a spreadsheet via

        **`POST /policies/bordereau/export-runs/{runId}/drive`**.


        This run family replaces the retired synchronous bordereau CSV download

        and Google Sheets export endpoints. Unlike those, a run has **no

        `limit`/`offset` and no row cap**: it streams the whole result set into
        a

        stored file in the background.


        ### The request body is OPTIONAL


        The body is the bordereau export request:


        - **`periodStart`** / **`periodEnd`** — scope to transactions whose
          `transactionTimestamp` falls within the half-open interval
          `[periodStart, periodEnd)`.
        - **`actions`** — transaction types to include (e.g. only `NEW_BUSINESS`
          and `ENDORSE`).
        - **`columns`** — an ordered array that COMPLETELY describes the output
          columns (see `BordereauColumnSpec`): fixed columns become selectable,
          omittable, and reorderable, and field columns interleave anywhere. **Omit
          it (or send no body at all) for the default column set (the fixed 12)**:
          Policy Number, Insured Name, Transaction Action, Policy Version,
          Effective Date, Transaction Timestamp, Policy Start Date, Policy End
          Date, Created At, Created By, Policy Premium, Policy Premium Change.
        - **`filters`** — policy filters selecting which policies contribute
          transactions.
        - **`sortBy`** / **`sortDirection`** — row order.


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

        (`UNREADABLE_REQUEST_BODY`) — it is never treated as an omitted body,

        because that would export different columns and rows than the ones asked

        for.


        The run snapshots the request AND the company's current configuration

        version when it starts, so what a queued export produces cannot drift: a

        configuration import landing mid-run does not change the file.


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


        If a run is already in flight (`queued` or `running`) producing this
        **exact

        export** — same request, same configuration version — this endpoint does
        not

        start a second one: it returns `200` with that run's id and

        `outcome: "duplicate"`. That is a normal, successful answer, not an
        error.

        Pass **`?force=true`** to start a new run anyway. A run that already

        **finished** is never deduplicated onto — re-asking for the same export
        is

        how you refresh the file.


        ### At most 3 runs in flight per company


        A company may have **3** export runs in flight at once — bordereau and

        entity export runs share the limit. A 4th distinct request is refused
        with

        a `429` whose body carries `inFlightRunIds` — poll those instead of

        retrying blind. `?force=true` does **not** lift this limit.


        **Required permission:** `company.policy:read`
      operationId: startBordereauExportRun
      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
            producing

            this exact export — i.e. skip the duplicate check and always mint a
            fresh

            run. Any value other than the literal `true` (including omission) is

            treated as `false`. This does **not** raise the limit of 3 runs in
            flight

            per company.
      requestBody:
        required: false
        description: >
          OPTIONAL. The bordereau export request; omit it entirely (or send
          `{}`)

          to export the default column set (the fixed 12) for the whole book. A

          body that cannot be read as JSON is a `400`, never a silent default

          export.
        content:
          application/json:
            schema:
              type: object
              properties:
                periodStart:
                  type: string
                  format: date-time
                  description: |
                    Inclusive lower bound on `transactionTimestamp`. Only
                    transactions at or after this timestamp are included.
                periodEnd:
                  type: string
                  format: date-time
                  description: |
                    Exclusive upper bound on `transactionTimestamp`. Only
                    transactions before this timestamp are included.
                actions:
                  type: array
                  items:
                    type: string
                  description: >
                    Transaction actions to include. Valid values:
                    `NEW_BUSINESS`,

                    `ENDORSE`, `CANCEL`, `REINSTATE`, `RENEW`. Omit to include
                    all

                    actions.


                    **Note:** Unrecognized action values are not rejected — they

                    silently match zero rows. Double-check spelling if results
                    are

                    unexpectedly empty.
                columns:
                  type: array
                  items:
                    $ref: '#/components/schemas/BordereauColumnSpec'
                  description: >
                    Ordered column specs that COMPLETELY describe the output

                    columns: fixed columns become selectable, omittable, and

                    reorderable, and field columns (`{"kind":"field"}` entries —

                    a dot-path into the policy's field data plus a display
                    header,

                    resolved per row as of the transaction's effective date)

                    interleave anywhere. A path that does not exist in the
                    policy

                    data yields an empty cell. Omit for the default column set

                    (the fixed 12).
                filters:
                  type: array
                  items:
                    $ref: '#/components/schemas/FieldModelV1ListFilter'
                  description: >
                    Policy filters restricting the export to transactions of the

                    policies whose CURRENT segment matches every filter — the
                    filter

                    selects the policies, then all of each matching policy's

                    transactions in the period are included. Tenant filters must
                    use

                    `fieldType: "declared"` and a policy-surface `listFilters`
                    key.

                    Fixed system filters remain explicit; implicit field-derived

                    forms fail at admission with `400`
                    `implicit-filters-disabled`

                    (no run is persisted or enqueued). Omit to include every
                    policy.
                sortBy:
                  type: string
                  enum:
                    - policyNumber
                    - primaryInsuredName
                    - effectiveDate
                    - transactionTimestamp
                    - createdAt
                  description: Field to sort results by. Default `transactionTimestamp`.
                sortDirection:
                  type: string
                  enum:
                    - asc
                    - desc
                  default: desc
                  description: Sort direction (default `desc`).
            examples:
              defaultExport:
                summary: >-
                  Export the default column set for the whole book (an omitted
                  body does the same)
                value: {}
              periodScoped:
                summary: New business and endorsements booked in H1 2025
                value:
                  periodStart: '2025-01-01T00:00:00.000Z'
                  periodEnd: '2025-07-01T00:00:00.000Z'
                  actions:
                    - NEW_BUSINESS
                    - ENDORSE
              customColumns:
                summary: >-
                  Fully custom ordered column set (fixed keys interleaved with
                  field paths)
                value:
                  columns:
                    - kind: fixed
                      key: policyNumber
                    - kind: field
                      path: policyStatus
                      header: Status
                    - kind: fixed
                      key: effectiveDate
                    - kind: field
                      path: fullTermPricingInfo.taxes
                      header: Taxes
      responses:
        '200':
          description: >
            The run to poll. `outcome: "enqueued"` means a new export was
            started;

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

            this exact export and no second one was started.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExportRunStartResponse'
              examples:
                enqueued:
                  summary: A new export run was started
                  value:
                    runId: 7c9e6679-7425-40de-944b-e07fc1f90ae7
                    outcome: enqueued
                duplicate:
                  summary: A run producing this exact export was already in flight
                  value:
                    runId: 7c9e6679-7425-40de-944b-e07fc1f90ae7
                    outcome: duplicate
        '400':
          description: Bad Request - invalid request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalidRequest:
                  summary: The export request failed validation
                  value:
                    error:
                      code: INVALID_REQUEST
                      message: >-
                        Invalid bordereau export request: columns.0.path: path
                        must be a dot-separated field path (no empty, leading,
                        trailing, or doubled segments)
                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
                        export request as an `application/json` body, or send NO
                        body at all to export the full default column set. A
                        body that cannot be read is never treated as an omitted
                        one, because that would export different columns and
                        rows than the ones asked for.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          description: >
            Conflict — this company has no stored configuration version to
            export

            against yet. Import a configuration first.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                noConfigVersion:
                  summary: The company has never imported a configuration
                  value:
                    error:
                      code: NoCompanyConfigVersion
                      message: >-
                        This company has no stored configuration version to
                        export against. Import a configuration first.
        '429':
          description: >
            Too Many Requests — this company already has 3 export 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/ExportRunsTooManyInFlightResponse'
              examples:
                tooManyInFlight:
                  summary: The per-company in-flight limit was reached
                  value:
                    error:
                      code: TooManyActiveExportRuns
                      message: >-
                        This company already has 3 export 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.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                admissionBusy:
                  summary: A concurrent start held the per-company admission lock
                  value:
                    error:
                      code: ExportRunAdmissionBusy
                      message: >-
                        Could not start an export: another export 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:
    BordereauColumnSpec:
      description: >
        One column of a fully caller-ordered bordereau — the single
        column-selection

        mechanism on every bordereau endpoint. On the export-run surface (`POST

        .../policies/bordereau/export-runs`, whose finished file downloads as
        CSV or

        Excel and can be delivered to Google Drive) an ordered `columns` array
        is the

        COMPLETE column set: the fixed columns become selectable, omittable, and

        reorderable, and configured field columns (resolved per row as of the

        transaction's effective date) interleave anywhere. On the typed-JSON
        list

        endpoint only

        `field` entries are accepted (they select into each row's `fieldColumns`

        map); a `fixed` entry there is rejected with HTTP 400, since the JSON
        rows

        always carry every fixed property.


        Two kinds:

        - `fixed` — one of the 12 built-in transaction-metadata columns,
        addressed by
          key; the column header comes from the built-in registry (e.g.
          `policyNumber` → "Policy Number").
        - `field` — a configured bordereau column, addressed by its declared
        column
          `key`, plus the display `header` for the column. A column the company has
          not declared on its bordereau surface is ignored; a column whose value
          cannot be produced yields an empty cell.
      oneOf:
        - type: object
          required:
            - kind
            - key
          properties:
            kind:
              type: string
              enum:
                - fixed
            key:
              type: string
              enum:
                - policyNumber
                - primaryInsuredName
                - transactionAction
                - policyVersion
                - effectiveDate
                - transactionTimestamp
                - policyStartDate
                - policyEndDate
                - createdAt
                - createdBy
                - policyPremium
                - policyPremiumChange
              description: The fixed column to include, by `BordereauRow` property name.
        - type: object
          required:
            - kind
            - path
            - header
          properties:
            kind:
              type: string
              enum:
                - field
            path:
              type: string
              description: >
                The `key` of a column the company has declared on its bordereau

                export surface (e.g. `policyStatus`). Its value is computed per
                row

                as of the transaction's effective date, from the expression the

                declared column carries — a field reference or a formula. Keys
                the

                surface does not declare are ignored.
            header:
              type: string
              description: Display name for this column in the output.
      discriminator:
        propertyName: kind
    FieldModelV1ListFilter:
      description: >
        A filter accepted by FMV1 external list, version, and export endpoints.


        Tenant-field filters must use `fieldType: declared` and the `key` of a

        `listFilters` definition on the surface being queried. The definition
        supplies

        the SQL expression, result type, and permitted operators; the request
        supplies

        only its key, an exposed operator, and an operand of that operator's
        shape.


        The fixed `systemUser`, `systemDate`, and `systemId` forms remain
        explicit.

        Implicit field-derived forms such as `text`, `number`, `optionSet`, or
        `join`

        are rejected with `400` and code `implicit-filters-disabled`.
      oneOf:
        - type: object
          additionalProperties: false
          description: Apply a tenant-declared list filter.
          required:
            - fieldType
            - key
            - operator
            - value
          properties:
            fieldType:
              type: string
              enum:
                - declared
            key:
              type: string
              description: Key of the declared list filter on the queried surface.
            operator:
              type: string
              enum:
                - equals
                - notEquals
                - contains
                - doesNotContain
                - gt
                - lt
                - gtOrEqual
                - ltOrEqual
                - between
                - before
                - after
                - in
                - notIn
                - includesAny
                - includesAll
                - excludes
                - jsonContains
                - hasKey
            value:
              description: Operand validated against the resolved definition and operator.
        - type: object
          additionalProperties: false
          description: Filter by the user who created or updated the row.
          required:
            - fieldReferenceId
            - fieldType
            - operator
            - value
            - systemColumn
          properties:
            fieldReferenceId:
              type: string
            fieldType:
              type: string
              enum:
                - systemUser
            operator:
              type: string
              enum:
                - in
            value:
              type: array
              items:
                type: string
            systemColumn:
              type: string
              enum:
                - createdBy
                - updatedBy
        - type: object
          additionalProperties: false
          description: Compare the row's created or updated date.
          required:
            - fieldReferenceId
            - fieldType
            - operator
            - value
            - systemColumn
          properties:
            fieldReferenceId:
              type: string
            fieldType:
              type: string
              enum:
                - systemDate
            operator:
              type: string
              enum:
                - 'on'
                - before
                - after
            value:
              type: object
              additionalProperties: false
              required:
                - year
                - month
                - day
              properties:
                year:
                  type: integer
                month:
                  type: integer
                  minimum: 1
                  maximum: 12
                day:
                  type: integer
                  minimum: 1
                  maximum: 31
            systemColumn:
              type: string
              enum:
                - createdAt
                - updatedAt
        - type: object
          additionalProperties: false
          description: Inclusive range on the row's created or updated date.
          required:
            - fieldReferenceId
            - fieldType
            - operator
            - value
            - valueTo
            - systemColumn
          properties:
            fieldReferenceId:
              type: string
            fieldType:
              type: string
              enum:
                - systemDate
            operator:
              type: string
              enum:
                - between
            value:
              type: object
              additionalProperties: false
              required:
                - year
                - month
                - day
              properties:
                year:
                  type: integer
                month:
                  type: integer
                  minimum: 1
                  maximum: 12
                day:
                  type: integer
                  minimum: 1
                  maximum: 31
            valueTo:
              type: object
              additionalProperties: false
              required:
                - year
                - month
                - day
              properties:
                year:
                  type: integer
                month:
                  type: integer
                  minimum: 1
                  maximum: 12
                day:
                  type: integer
                  minimum: 1
                  maximum: 31
            systemColumn:
              type: string
              enum:
                - createdAt
                - updatedAt
        - type: object
          additionalProperties: false
          description: Filter by one or more entity row IDs.
          required:
            - fieldReferenceId
            - fieldType
            - operator
            - value
          properties:
            fieldReferenceId:
              type: string
            fieldType:
              type: string
              enum:
                - systemId
            operator:
              type: string
              enum:
                - in
            value:
              type: array
              items:
                type: string
    ExportRunStartResponse:
      type: object
      description: >
        The run to poll after starting an export run. `outcome` is a policy
        verdict

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

        producing this exact export (same request, same configuration version)
        was

        already in flight, so `runId` is that run and no second export was
        started.
      required:
        - runId
        - outcome
      properties:
        runId:
          type: string
          format: uuid
          description: >-
            The run to poll via the start endpoint's GET .../export-runs/{runId}
            sibling
        outcome:
          type: string
          enum:
            - enqueued
            - duplicate
          description: >-
            `enqueued` when a new export was started; `duplicate` when a run
            already in flight was producing this exact export (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
    ExportRunsTooManyInFlightResponse:
      type: object
      description: >
        The `429` body when a company already has the maximum number of export
        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: TooManyActiveExportRuns
            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
  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`.

````