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

# Upload Policy File

> Upload a CSV or Excel file for asynchronous policy ingestion

Upload a policy data file for asynchronous processing. The file headers are validated synchronously — if validation passes, the file is uploaded to storage and a worker job is queued for ingestion. Returns an `uploadId` that you can poll via [Get Upload Status](/api-reference/v1/policies/get-upload-status).

## Workflow

<Steps>
  <Step title="Upload your file">
    Send a `POST /v1/policies/upload` request with your CSV or Excel file as `multipart/form-data`. The response includes an `uploadId` with status `QUEUED`.
  </Step>

  <Step title="Poll for status">
    Call `GET /v1/policies/uploads/{uploadId}` every **3-5 seconds**. The status will move from `QUEUED` to `PROCESSING` as a worker picks up the file.
  </Step>

  <Step title="Handle the result">
    Stop polling once the status reaches a terminal state: `COMPLETED`, `COMPLETED_WITH_FAILURES`, or `FAILED`. Check the `errors` array for any row-level failures. Once complete, retrieve your policies via [List Policies](/api-reference/v1/policies/list-policies) or [Get Policy](/api-reference/v1/policies/get-policy).
  </Step>
</Steps>

## File Requirements

| Constraint          | Value                         |
| ------------------- | ----------------------------- |
| **Formats**         | CSV (`.csv`), Excel (`.xlsx`) |
| **Max size**        | 10 MB                         |
| **Content-Type**    | `multipart/form-data`         |
| **Form field name** | `file`                        |

## Column Headers

Column headers in your file must **exactly match** the supported field names listed below. Unrecognized headers are rejected immediately with a `400` response.

### Required Columns

These columns **must** be present in every upload:

| Column Header           | Description                                                                              |
| ----------------------- | ---------------------------------------------------------------------------------------- |
| `policyNumber`          | Unique identifier that groups transactions into a single policy                          |
| `riskStateCode`         | US state code (e.g., `CA`, `TX`, `NY`)                                                   |
| `policyTransactionType` | Transaction lifecycle stage: `New`, `Renewal`, `Endorsement`, `Cancellation`, or `Audit` |
| `policyTransactionDate` | Date the transaction was processed (e.g., `2025-01-15` or `01/15/2025`)                  |

### Agency Identifier (at least one required)

At least one of these columns must be present for entity matching:

| Column Header | Description                                         |
| ------------- | --------------------------------------------------- |
| `agencyName`  | Name of the agency — used for AI-powered matching   |
| `agencyNpn`   | National Producer Number — most reliable identifier |
| `agencyFein`  | Federal Employer Identification Number              |

### Optional Columns

| Column Header             | Description                                                         |
| ------------------------- | ------------------------------------------------------------------- |
| `agencyLicenseNumber`     | State insurance license number of the agency                        |
| `agencyLicensedStateCode` | State where the agency license was issued                           |
| `agentName`               | Name of the individual agent                                        |
| `agentNpn`                | NPN of the individual agent                                         |
| `agentLicenseNumber`      | State license number of the agent                                   |
| `agentLicensedState`      | State where the agent license was issued                            |
| `insuredEntityName`       | Name of the insured party                                           |
| `carrierName`             | Insurance carrier name                                              |
| `lineOfBusiness`          | Line of business (e.g., `Commercial Lines`, `Property & Casualty`)  |
| `productName`             | Product name for product matching                                   |
| `productCode`             | Product code (alternative to product name)                          |
| `producerName`            | Producing agent name                                                |
| `producerCode`            | Producer identifier code                                            |
| `effectiveDate`           | Policy effective date                                               |
| `expiryDate`              | Policy expiration date                                              |
| `premiumAmountInUSD`      | Premium amount in USD                                               |
| `aggregateCoverageInUSD`  | Aggregate coverage limit                                            |
| `coveragePerClaimInUSD`   | Per-claim coverage limit                                            |
| `filingNumber`            | Filing number                                                       |
| `slaNumber`               | SLA number                                                          |
| `externalBillingId`       | External billing identifier from the upstream entity billing system |
| `licenseNumber`           | License number                                                      |
| `npn`                     | NPN                                                                 |
| `upstreamEntityName`      | Upstream entity name                                                |

## Success Response

<Note>
  All successful responses are wrapped in the standard response envelope. See [Request/Response Conventions](/guides/request-response).
</Note>

```json theme={null}
{
  "statusCode": 201,
  "data": {
    "uploadId": "6650a1b2c3d4e5f6a7b8c9d0",
    "status": "QUEUED"
  },
  "timestamp": "2025-01-15T10:30:00.000Z"
}
```

| Field      | Type   | Description                                                                                           |
| ---------- | ------ | ----------------------------------------------------------------------------------------------------- |
| `uploadId` | string | MongoDB ObjectId — use this to poll [Get Upload Status](/api-reference/v1/policies/get-upload-status) |
| `status`   | string | Always `QUEUED` on successful upload                                                                  |

## Error Scenarios

### Unsupported File Format (400)

Returned when the file is not CSV or XLSX.

```json theme={null}
{
  "statusCode": 400,
  "errorType": "validation_error",
  "errorMessage": ["Only CSV and XLSX files are allowed"],
  "requestId": "dev-abc123",
  "timestamp": "2025-01-15T10:30:00.000Z"
}
```

### Unsupported Column Headers (400)

Returned when any column header doesn't match a supported field name.

```json theme={null}
{
  "statusCode": 400,
  "errorType": "validation_error",
  "errorMessage": [
    "Unsupported column headers. Column headers must exactly match the supported field names."
  ],
  "details": {
    "supportedFields": [
      "policyNumber", "riskStateCode", "policyTransactionType", "policyTransactionDate",
      "insuredEntityName", "carrierName", "effectiveDate", "expiryDate",
      "premiumAmountInUSD", "aggregateCoverageInUSD", "coveragePerClaimInUSD",
      "lineOfBusiness", "licenseNumber", "npn", "producerName", "producerCode",
      "productName", "productCode", "filingNumber", "slaNumber", "externalBillingId", "upstreamEntityName",
      "agencyName", "agencyNpn", "agencyFein", "agencyLicenseNumber",
      "agencyLicensedStateCode", "agentName", "agentNpn", "agentLicenseNumber",
      "agentLicensedState"
    ]
  },
  "requestId": "dev-abc123",
  "timestamp": "2025-01-15T10:30:00.000Z"
}
```

### Missing Required Columns (400)

Returned when one or more required columns are absent.

```json theme={null}
{
  "statusCode": 400,
  "errorType": "validation_error",
  "errorMessage": ["Missing required columns: riskStateCode, policyTransactionDate"],
  "details": {
    "supportedFields": [
      "policyNumber", "riskStateCode", "policyTransactionType", "policyTransactionDate",
      "insuredEntityName", "carrierName", "effectiveDate", "expiryDate",
      "premiumAmountInUSD", "aggregateCoverageInUSD", "coveragePerClaimInUSD",
      "lineOfBusiness", "licenseNumber", "npn", "producerName", "producerCode",
      "productName", "productCode", "filingNumber", "slaNumber", "externalBillingId", "upstreamEntityName",
      "agencyName", "agencyNpn", "agencyFein", "agencyLicenseNumber",
      "agencyLicensedStateCode", "agentName", "agentNpn", "agentLicenseNumber",
      "agentLicensedState"
    ]
  },
  "requestId": "dev-abc123",
  "timestamp": "2025-01-15T10:30:00.000Z"
}
```

### Missing Agency Identifier (400)

Returned when none of `agencyName`, `agencyNpn`, or `agencyFein` columns are present.

```json theme={null}
{
  "statusCode": 400,
  "errorType": "validation_error",
  "errorMessage": [
    "At least one agency identifier column is required: agencyName, agencyNpn, agencyFein"
  ],
  "details": {
    "supportedFields": [
      "policyNumber", "riskStateCode", "policyTransactionType", "policyTransactionDate",
      "insuredEntityName", "carrierName", "effectiveDate", "expiryDate",
      "premiumAmountInUSD", "aggregateCoverageInUSD", "coveragePerClaimInUSD",
      "lineOfBusiness", "licenseNumber", "npn", "producerName", "producerCode",
      "productName", "productCode", "filingNumber", "slaNumber", "externalBillingId", "upstreamEntityName",
      "agencyName", "agencyNpn", "agencyFein", "agencyLicenseNumber",
      "agencyLicensedStateCode", "agentName", "agentNpn", "agentLicenseNumber",
      "agentLicensedState"
    ]
  },
  "requestId": "dev-abc123",
  "timestamp": "2025-01-15T10:30:00.000Z"
}
```

### No File Uploaded (400)

```json theme={null}
{
  "statusCode": 400,
  "errorType": "validation_error",
  "errorMessage": ["No file uploaded"],
  "requestId": "dev-abc123",
  "timestamp": "2025-01-15T10:30:00.000Z"
}
```

### File Too Large (413)

Files exceeding 10 MB are rejected by the server before reaching the handler.

### Unauthorized (401)

Missing or invalid authentication token. See [Authentication](/authentication).

## Idempotency

This endpoint supports idempotency. If you include an `idempotency-key` header, duplicate requests within **1 hour** return the original response without reprocessing. See [Idempotency](/guides/idempotency).

## Multi-Transaction Policies

Multiple rows sharing the same `policyNumber` are grouped into a single policy with multiple transactions. For example, a CSV with:

| policyNumber | policyTransactionType | policyTransactionDate |
| ------------ | --------------------- | --------------------- |
| POL-001      | New                   | 2025-01-15            |
| POL-001      | Endorsement           | 2025-03-20            |
| POL-001      | Renewal               | 2025-06-01            |

Creates **1 policy** with **3 transactions**. The policy's `currentStatus` is automatically resolved from its transaction history.


## OpenAPI

````yaml openapi/v1.json POST /v1/policies/upload
openapi: 3.0.0
info:
  title: Turris Public API
  description: API for managing insurance compliance data
  version: 1.0.0
  contact: {}
servers:
  - url: https://public.api.live.turrisfi.com
    description: Production
  - url: https://public.api.sandbox.turrisfi.com
    description: Sandbox
security: []
tags: []
paths:
  /v1/policies/upload:
    post:
      tags:
        - policies
      operationId: PoliciesController_uploadPolicyFile_v1
      parameters:
        - name: idempotency-key
          in: header
          description: UUID to ensure idempotent request processing
          required: false
          schema:
            type: string
            example: 550e8400-e29b-41d4-a716-446655440000
        - name: x-idempotency-key
          in: header
          description: Alternative UUID header for idempotent request processing
          required: false
          schema:
            type: string
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
              required:
                - file
      responses:
        '201':
          description: File upload initiated — returns uploadId for polling status
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PolicyUploadInitiateResponse'
                  requestId:
                    type: string
                    description: Unique request identifier
                    example: dev-2c5e7cf2-9acf-4c8c-ab2f-b81f39d775a8
                  timestamp:
                    type: string
                    description: Response timestamp
                    example: '2025-11-12T20:49:03.293Z'
                required:
                  - data
                  - requestId
                  - timestamp
        '400':
          description: Invalid file or unsupported format
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '401':
          description: Invalid or missing auth token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
components:
  schemas:
    PolicyUploadInitiateResponse:
      type: object
      properties:
        uploadId:
          type: string
          description: >-
            ID of the upload request — use this to poll GET
            /policies/uploads/{uploadId}
          example: 6650a1b2c3d4e5f6a7b8c9d0
        status:
          type: string
          description: Initial status of the upload
          example: QUEUED
          enum:
            - QUEUED
            - PROCESSING
            - COMPLETED
            - COMPLETED_WITH_FAILURES
            - NEEDS_CLARIFICATION
            - CANCELLED
            - FAILED
      required:
        - uploadId
        - status
    ErrorResponseDto:
      type: object
      properties:
        statusCode:
          type: number
          description: HTTP status code
        requestId:
          type: string
          description: Unique request identifier for debugging
        errorType:
          type: string
          description: Error type classification
        errorMessage:
          description: Array of error messages
          type: array
          items:
            type: string
        timestamp:
          type: string
          description: ISO timestamp when the error occurred
        details:
          type: object
          description: Additional error context
      required:
        - statusCode
        - requestId
        - errorType
        - errorMessage
        - timestamp

````