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

# Concepts

> How transactions, deltas, segments, and versions work in the Policy API

This page explains the core data model behind the transaction-based Policy API. Understanding these concepts will help you predict how the API behaves when you create, endorse, cancel, and reinstate policies.

## Transaction Model

Every change to a policy is recorded as an immutable **transaction**. Transactions are the single source of truth — policy state is always derived from them, never authored directly.

<Steps>
  <Step title="Configure">
    Define your fields, option sets, and exposure types via the [Configuration API](/api-v1-reference/configuration/overview).
  </Step>

  <Step title="Create">
    `POST /transaction/new-business` creates a policy with initial state covering the full term.
  </Step>

  <Step title="Endorse">
    `POST /{policyId}/transaction/endorse` modifies the policy — add exposures, change field values, adjust coverage.
  </Step>

  <Step title="Cancel">
    `POST /{policyId}/transaction/cancel` cancels the policy from a specified date.
  </Step>

  <Step title="Reinstate">
    `POST /{policyId}/transaction/reinstate` reinstates a cancelled policy.
  </Step>

  <Step title="Renew">
    `POST /transaction/renew` starts a new policy term linked to the previous one.
  </Step>
</Steps>

### Transaction Types

<CardGroup cols={2}>
  <Card title="NEW_BUSINESS">
    Creates the policy. The effective date is the policy start date. Produces one segment covering the full term.
  </Card>

  <Card title="ENDORSE">
    Modifies policy state from a given effective date. Accepts an array of field-level deltas. May split existing segments or merge them if the change aligns state across periods.
  </Card>

  <Card title="CANCEL">
    Sets `policyStatus` to `"cancelled"` from the cancellation date through end of term. Also sets `fullTermPolicyInfo.policyEarlyTerminationDate` to the cancellation date. Optionally accepts billing-only deltas (e.g., short-rate penalties).
  </Card>

  <Card title="REINSTATE">
    Sets `policyStatus` back to `"active"` from the reinstatement date. Clears `fullTermPolicyInfo.policyEarlyTerminationDate`. Optionally accepts billing-only deltas (e.g., reinstatement fees).
  </Card>

  <Card title="RENEW">
    Creates a new policy term linked to the previous via `previousPolicyId`. Accepts a full `fieldModelV1Data` payload — the caller provides the complete initial state for the new term.
  </Card>
</CardGroup>

### Effective Date vs Transaction Timestamp

Each transaction has two timestamps:

* **`effectiveDate`** — when the policy state changes. For an endorsement, this is when the new coverage begins. For a cancellation, this is when coverage ends.
* **`transactionTimestamp`** — when the business decision was made. Defaults to the current time, but can be set explicitly for imports (e.g., aligning to a bordereau booking date).

## Deltas

A **delta** is a single field-level change within a transaction. Endorsements carry one or more deltas — in two parallel arrays — that describe exactly what changed, where, and (for per-segment fields) for what date range.

### Two Delta Arrays

* **`deltas`** — per-segment changes. Each carries its own `startDate` and `endDate` within the policy term.
* **`fullTermDeltas`** — changes to cross-segment invariant fields (`fullTermPolicyBilling`, `fullTermPolicyInfo`, `fullTermPolicyRatingResponse`, `fullTermExposureRatingResponse`). No dates — the server applies them across the whole policy term.

The server rejects `fullTerm*` paths sent via `deltas` and non-`fullTerm*` paths sent via `fullTermDeltas`. At least one of the two arrays must be non-empty.

### Delta Structure

**Per-segment delta (`deltas`):**

| Field       | Type   | Description                                     |
| ----------- | ------ | ----------------------------------------------- |
| `path`      | string | Dot-notation path to a non-`fullTerm*` field    |
| `action`    | string | `Overwrite`, `Add`, or `Remove`                 |
| `value`     | any    | The new value, value to add, or value to remove |
| `startDate` | string | Start of the date range this change applies to  |
| `endDate`   | string | End of the date range this change applies to    |

**Full-term delta (`fullTermDeltas`):**

| Field    | Type   | Description                                     |
| -------- | ------ | ----------------------------------------------- |
| `path`   | string | Dot-notation path into a `fullTerm*` field      |
| `action` | string | `Overwrite`, `Add`, or `Remove`                 |
| `value`  | any    | The new value, value to add, or value to remove |

Dates are implicit on `fullTermDeltas` — the delta always spans `policyStartDate` → `policyEndDate`, because `fullTerm*` fields must be identical across every segment of a version.

### Delta Actions

#### Overwrite

Replace a scalar value or an entire object. This is the most common action.

```json theme={null}
{
  "path": "policy.exposures[exp-1].bedCount",
  "action": "Overwrite",
  "value": 110
}
```

#### Add

Append to a collection. Uses **set semantics** — objects are matched by `id`, primitives by equality. If the value already exists, the delta is a no-op.

```json theme={null}
{
  "path": "policy.exposures[exp-1].coveredSpecialties",
  "action": "Add",
  "value": "Neurology"
}
```

#### Remove

Remove from a collection. Same matching rules as Add. If the value is not present, the delta is a no-op.

```json theme={null}
{
  "path": "policy.exposures[exp-1].namedPhysicians",
  "action": "Remove",
  "value": "Dr. Nguyen"
}
```

### Path Notation

Paths use dot-notation to target fields at any depth. Bracket syntax targets specific items in an array by their `id` field.

| Path                               | Targets                                                                     |
| ---------------------------------- | --------------------------------------------------------------------------- |
| `policy.deductible`                | A scalar field on the policy                                                |
| `policy.exposures[exp-1].bedCount` | A field on a specific exposure                                              |
| `policy.exposures`                 | The exposures collection itself (for Add/Remove of entire exposure objects) |
| `policy.fullTermPolicyBilling`     | A cross-segment invariant (same value in every segment of the version)      |
| `policy.policyStatus`              | The policy status (used internally by Cancel/Reinstate)                     |

<Note>
  **Overwrite on an indexed path** (e.g., `policy.exposures[exp-1]`) replaces the entire exposure object. **Add on a collection path** (e.g., `policy.exposures`) appends an exposure. These are different operations targeting different levels.
</Note>

### Example: Endorsement with Deltas

An endorsement to `POST /v1/policies/{policyId}/transaction/endorse` effective April 1 that adds a new exposure and updates billing:

```json theme={null}
{
  "effectiveDate": "2025-04-01",
  "deltas": [
    {
      "startDate": "2025-04-01",
      "endDate": "2025-12-31",
      "path": "policy.exposures",
      "action": "Add",
      "value": {
        "id": "exp-2",
        "exposureType": "OutpatientClinic",
        "facilityName": "Greenfield West Clinic",
        "bedCount": 0,
        "coveredSpecialties": ["Dermatology", "Family Medicine"],
        "namedPhysicians": ["Dr. Kim"]
      }
    }
  ],
  "fullTermDeltas": [
    {
      "path": "policy.fullTermPolicyBilling",
      "action": "Overwrite",
      "value": {
        "policyPremium": 98000,
        "policyTaxes": 4900,
        "policyFees": 500,
        "policyGrandTotal": 103400
      }
    }
  ]
}
```

The exposure delta applies from April 1 through the end of the term and will split the existing segment at that boundary. The `fullTermPolicyBilling` delta lives under `fullTermDeltas` and is applied uniformly across the **full policy term** — no explicit dates because `fullTerm*` fields must be identical in every segment.

## Segments

A **segment** is a maximal contiguous date range where the policy state is identical.

<Warning>
  **Segments are NOT one-to-one with transactions.** A single endorsement may split one segment into many. Backdated corrections can merge segments back together. Six transactions can produce two segments — or one.
</Warning>

### Segment Properties

Every version's segments satisfy three invariants:

* **No overlaps** — segments never share a date
* **Full coverage** — segments span the entire policy term with no gaps
* **No adjacent duplicates** — adjacent segments with identical state are automatically merged

### How Segments Change

<Tabs>
  <Tab title="Create">
    A new policy starts with **one segment** covering the full term.

    | # | Date Range     | Exposures                |
    | - | -------------- | ------------------------ |
    | 1 | Jan 1 – Dec 31 | Main Hospital (120 beds) |
  </Tab>

  <Tab title="Endorse (split)">
    An endorsement effective April 1 adds a satellite clinic, splitting the segment:

    | # | Date Range     | Exposures                        |
    | - | -------------- | -------------------------------- |
    | 1 | Jan 1 – Mar 31 | Main Hospital                    |
    | 2 | Apr 1 – Dec 31 | Main Hospital + Satellite Clinic |

    Two segments — different exposure counts on each side of the boundary.
  </Tab>

  <Tab title="Correction (merge)">
    A backdated correction adds the clinic to Jan–Mar too, making it match Apr–Dec:

    | # | Date Range     | Exposures                        |
    | - | -------------- | -------------------------------- |
    | 1 | Jan 1 – Dec 31 | Main Hospital + Satellite Clinic |

    Three transactions, but only **one segment**. The correction converged per-segment state across the full term.
  </Tab>
</Tabs>

**Segments reflect the final state of the policy, not its change history.** The full audit trail is preserved in the transaction history.

## Versions

Each transaction produces a new policy **version**. A version is a complete snapshot — it contains the full set of segments representing the policy at that point in the transaction history.

| Property                | Description                                |
| ----------------------- | ------------------------------------------ |
| `policyVersion`         | Sequential integer (1, 2, 3, ...)          |
| `transactionId`         | The transaction that produced this version |
| `segments`              | Complete set of segments for this version  |
| `startDate` / `endDate` | Policy term boundaries                     |

You can query any historical version to see what the policy looked like after a specific transaction.

## How It Works

When you submit a transaction, the system:

1. **Loads the previous version** — gets the current segments
2. **Applies deltas** — applies each delta to all segments whose date range overlaps the delta's date range
3. **Normalizes** — produces deterministic JSON and computes a hash for each resulting segment
4. **Merges** — collapses adjacent segments with identical hashes into one
5. **Persists** — stores the new version with its segments

You don't need to understand the internal algorithm to use the API — just know that the system automatically handles segment splitting and merging based on the deltas you submit.

<Note>
  **No-op deltas are safe.** Adding a value that already exists or removing one that's already gone has no effect. This means a delta that spans a wide date range may change some segments and leave others untouched — the system handles it correctly.
</Note>

## Premiums and Rating

**The API does not calculate premiums.** When you submit a transaction, you supply field values and billing totals yourself. The system stores what you send — it does not rate, pro-rate, or re-aggregate.

Policy financial data lives at two levels:

### Full-Term Billing

`fullTermPolicyBilling` is a **cross-segment invariant** — identical across every segment in a version. It contains the billing summary for the entire policy term: `policyPremium`, `policyTaxes`, `policyFees`, `policyGrandTotal`. Every endorsement should include a `fullTermPolicyBilling` delta spanning the full policy term to keep billing current.

### Per-Segment Rating

Each segment can carry its own rating data via `PolicyRatingResponse` (on the policy) and `ExposureRatingResponse` (on each exposure). These typically include:

* **`annualPremium`** — the premium as if that segment's state applied for the full year
* **`dailyProratedPremium`** — the daily premium rate for that segment's risk profile

Both values are **time-independent** — they describe the risk characteristics, not the segment's duration. Per-segment rating provides visibility into which exposures contribute how much premium over which time periods.

<Note>
  **`fullTermPolicyBilling` is not necessarily derivable from per-segment rates.** Full-term billing can include flat premium minimums, surplus lines taxes, policy fees, or other adjustments that are independent of per-segment rating. The `FullTermPolicyRatingResponse` (a separate cross-segment field) captures these aggregate rating details.
</Note>

<Warning>
  Callers don't *have* to pass per-segment field changes. You could submit endorsements that only modify `fullTermPolicyBilling` and leave per-segment data untouched. However, this reduces the system to **importing bordereau** — you'd know the billing changed, but not what policy details produced the change. Per-segment deltas capture the actual changing characteristics of the policy over time.
</Warning>

<Warning>
  **Time-dependent per-segment values prevent merging.** If a per-segment field's value depends on segment duration (e.g. a total prorated premium for the time slice), segments with identical risk but different lengths will never merge. Use time-independent values like `annualPremium` and `dailyProratedPremium` instead. See [Merging and Time-Dependent Fields](#merging-and-time-dependent-fields) for details.
</Warning>

When using the application UI (not headless) with a rater configured, an aggregation rater automatically computes billing and rating totals across segments. Through the API, this is your responsibility.

## Worked Example

A medical facility policy (Greenfield Medical Center) for Jan 1 – Dec 31 with one exposure. This shows the core segment behaviors — splitting, maintaining, and merging — with the actual delta payloads. Each endorsement also includes a `fullTermPolicyBilling` update (omitted from the segment tables since it's the same in every segment).

<AccordionGroup>
  <Accordion title="1. NEW_BUSINESS — 1 segment">
    Create the policy with initial state spanning the full term. Grand total: 89,750.

    ```json theme={null}
    {
      "policyStartDate": "2025-01-01",
      "policyEndDate": "2025-12-31",
      "fieldModelV1Data": {
        "policy": {
          "fullTermPolicyInfo": {
            "policyStatus": "active",
            "policyStartDate": { "year": 2025, "month": 1, "day": 1, "timezone": "America/New_York" },
            "policyEndDate": { "year": 2025, "month": 12, "day": 31, "timezone": "America/New_York" },
            "primaryInsuredId": "exp-1"
          },
          "fullTermPolicyBilling": {
            "policyPremium": 85000, "policyTaxes": 4250,
            "policyFees": 500, "policyGrandTotal": 89750
          },
          "exposures": [{
            "id": "exp-1",
            "exposureType": "MedicalFacility",
            "facilityName": "Greenfield Main Campus",
            "bedCount": 120,
            "coveredSpecialties": ["Cardiology", "Orthopedics", "General Surgery"],
            "namedPhysicians": ["Dr. Patel", "Dr. Nguyen", "Dr. Hoffman"]
          }]
        }
      }
    }
    ```

    **Segments:**

    | # | Date Range     | Exposures                            |
    | - | -------------- | ------------------------------------ |
    | 1 | Jan 1 – Dec 31 | Main Campus (120 beds, 3 physicians) |
  </Accordion>

  <Accordion title="2. ENDORSE Apr 1: add satellite clinic — 2 segments">
    A satellite clinic opens. The `Add` action appends a new exposure to the collection. Grand total increases to 103,400 (+13,650) — the additional exposure adds risk for the remaining 9 months.

    ```json theme={null}
    {
      "effectiveDate": "2025-04-01",
      "deltas": [
        {
          "startDate": "2025-04-01", "endDate": "2025-12-31",
          "path": "policy.exposures",
          "action": "Add",
          "value": {
            "id": "exp-2", "exposureType": "OutpatientClinic",
            "facilityName": "Greenfield West Clinic", "bedCount": 0,
            "coveredSpecialties": ["Dermatology", "Family Medicine"],
            "namedPhysicians": ["Dr. Kim"]
          }
        }
      ],
      "fullTermDeltas": [
        {
          "path": "policy.fullTermPolicyBilling",
          "action": "Overwrite",
          "value": { "policyPremium": 98000, "policyTaxes": 4900,
                     "policyFees": 500, "policyGrandTotal": 103400 }
        }
      ]
    }
    ```

    **Segments:**

    | # | Date Range     | Exposures                 |
    | - | -------------- | ------------------------- |
    | 1 | Jan 1 – Mar 31 | Main Campus               |
    | 2 | Apr 1 – Dec 31 | Main Campus + West Clinic |

    The original segment split at April — different exposure count on each side.
  </Accordion>

  <Accordion title="3. ENDORSE Jun 1: add physician + specialty — 3 segments">
    A new surgeon joins the main campus. Neurology added as a covered specialty. Grand total increases to 111,800 (+8,400) — the additional physician and expanded specialty coverage increase risk.

    ```json theme={null}
    {
      "effectiveDate": "2025-06-01",
      "deltas": [
        {
          "startDate": "2025-06-01", "endDate": "2025-12-31",
          "path": "policy.exposures[exp-1].namedPhysicians",
          "action": "Add", "value": "Dr. Okafor"
        },
        {
          "startDate": "2025-06-01", "endDate": "2025-12-31",
          "path": "policy.exposures[exp-1].coveredSpecialties",
          "action": "Add", "value": "Neurology"
        }
      ],
      "fullTermDeltas": [
        {
          "path": "policy.fullTermPolicyBilling",
          "action": "Overwrite",
          "value": { "policyPremium": 106000, "policyTaxes": 5300,
                     "policyFees": 500, "policyGrandTotal": 111800 }
        }
      ]
    }
    ```

    **Segments:**

    | # | Date Range     | Physicians                         | Specialties                                     |
    | - | -------------- | ---------------------------------- | ----------------------------------------------- |
    | 1 | Jan 1 – Mar 31 | Patel, Nguyen, Hoffman             | Cardiology, Orthopedics, Surgery                |
    | 2 | Apr 1 – May 31 | Patel, Nguyen, Hoffman             | Cardiology, Orthopedics, Surgery                |
    | 3 | Jun 1 – Dec 31 | Patel, Nguyen, Hoffman, **Okafor** | Cardiology, Orthopedics, Surgery, **Neurology** |

    The Apr–Dec segment from version 2 split at the June boundary.
  </Accordion>

  <Accordion title="4. Backdated corrections — merge to 2 segments">
    Internal audit reveals the physician change, bed reduction, and Neurology addition should all have been effective April 1, not June 1. A single endorsement corrects everything retroactively. Grand total decreases to 106,550 (-5,250) — the physician departure and bed reduction reduce risk, partially offset by Neurology covering a longer period.

    ```json theme={null}
    {
      "effectiveDate": "2025-04-01",
      "deltas": [
        {
          "startDate": "2025-04-01", "endDate": "2025-12-31",
          "path": "policy.exposures[exp-1].bedCount",
          "action": "Overwrite", "value": 110
        },
        {
          "startDate": "2025-04-01", "endDate": "2025-12-31",
          "path": "policy.exposures[exp-1].namedPhysicians",
          "action": "Remove", "value": "Dr. Nguyen"
        },
        {
          "startDate": "2025-04-01", "endDate": "2025-12-31",
          "path": "policy.exposures[exp-1].namedPhysicians",
          "action": "Add", "value": "Dr. Okafor"
        },
        {
          "startDate": "2025-04-01", "endDate": "2025-12-31",
          "path": "policy.exposures[exp-1].coveredSpecialties",
          "action": "Add", "value": "Neurology"
        }
      ],
      "fullTermDeltas": [
        {
          "path": "policy.fullTermPolicyBilling",
          "action": "Overwrite",
          "value": { "policyPremium": 101000, "policyTaxes": 5050,
                     "policyFees": 500, "policyGrandTotal": 106550 }
        }
      ]
    }
    ```

    All four per-segment deltas span Apr 1 – Dec 31. The Jun–Dec segment **already had** beds=110, Nguyen removed, Okafor present, and Neurology — those deltas are no-ops there. Only Apr–May changes. After applying, Apr–May and Jun–Dec have converged to identical state. The system merges them:

    **Segments:**

    | # | Date Range     | Beds | Physicians             | Specialties                                 |
    | - | -------------- | ---- | ---------------------- | ------------------------------------------- |
    | 1 | Jan 1 – Mar 31 | 120  | Patel, Nguyen, Hoffman | Cardiology, Orthopedics, Surgery            |
    | 2 | Apr 1 – Dec 31 | 110  | Patel, Hoffman, Okafor | Cardiology, Orthopedics, Surgery, Neurology |

    <Info>
      **Four transactions, two segments.** The Apr–May / Jun–Dec boundary vanished — not because a transaction was reversed, but because the correction *converged the per-segment state* on both sides. `fullTermPolicyBilling` didn't affect the merge — it's the same in every segment.
    </Info>
  </Accordion>
</AccordionGroup>

For the complete 9-transaction walkthrough — including cancellation, reinstatement, transaction deletion, and both types of segment merging — see the [Lifecycle Walkthrough](/api-v1-reference/policies/lifecycle-walkthrough).

## Cancellation and Reinstatement

Cancel and reinstate are simpler than endorsements but follow the same segment mechanics. The system automatically generates the status and early termination date deltas.

* **Cancel** sets `policyStatus` to `"cancelled"` from the cancellation date through end of term and sets `fullTermPolicyInfo.policyEarlyTerminationDate` to the cancellation date. This typically splits the existing segment at the cancellation boundary.
* **Reinstate** sets `policyStatus` back to `"active"` from the reinstatement date and clears `fullTermPolicyInfo.policyEarlyTerminationDate`. If this makes the reinstated segment identical to its predecessor, they merge.
* Both optionally accept **billing-only deltas** (restricted to `fullTermPolicyBilling` paths) for short-rate penalties or reinstatement fees.

<Note>
  A cancel followed by a reinstate at the same date is **invisible in the final segments** — the policy returns to the same state it had before cancellation. Both transactions are preserved in the audit trail, but the derived segments are identical to the pre-cancellation version.
</Note>

## Transaction Deletion

Only the **most recent** transaction on a policy can be deleted. Deleting a transaction:

* Rolls back to the prior version — the deleted transaction's segments are removed, and the previous version becomes current (no new version is created)
* Preserves the audit trail — the deleted transaction is archived, not erased
* Is irreversible through the API once deleted (the transaction can be re-created manually)

<Warning>
  Transaction deletion undoes the most recent change. It does not allow arbitrary transaction removal from the middle of the history.
</Warning>

## Merging and Time-Dependent Fields

Segment merging compares the *entire* per-segment state, including rating. If any per-segment field's value depends on the segment's duration, two segments with identical risk profiles but different durations will never merge.

### The Problem

Suppose you store a `totalProratedPremium` that represents the premium for each segment's time slice. After a backdated correction converges two segments' structural data, they still can't merge:

| # | Date Range                | Risk Profile | `totalProratedPremium` | Merge?            |
| - | ------------------------- | ------------ | ---------------------- | ----------------- |
| A | Apr 1 – May 31 (61 days)  | identical    | 18,700                 | ✗ — values differ |
| B | Jun 1 – Dec 31 (214 days) | identical    | 65,500                 |                   |

This is a chicken-and-egg problem: you can't compute the merged segment's prorated premium without knowing the merge will happen, but the merge can't happen while the values differ.

### The Solution Today

Use **time-independent** per-segment values. `annualPremium` and `dailyProratedPremium` describe the risk profile, not the duration. Two segments with the same risk produce the same values regardless of how many days each covers, so merging works naturally.

If you need to know the total premium for a specific segment's time span, derive it from the daily rate and the segment's date range after reading the policy — don't store it as a per-segment field.

### Looking Ahead

We are working on support for **calculated fields** — per-segment fields whose values are automatically derived after segment computation. This will allow fields like `totalProratedPremium` to be stored on segments without blocking merges, because the system will exclude them from the merge comparison and recompute them based on each segment's final date range.
