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

# Overview

> One parametric CRUD surface for all top-level Field Model V1 entities

Every top-level Field Model V1 entity is managed through **one parametric CRUD
surface**, keyed on `{entityType}`:

| Method   | Path                                                                |
| -------- | ------------------------------------------------------------------- |
| `GET`    | `/api/v1/companies/{companyId}/entities/{entityType}` (list)        |
| `POST`   | `/api/v1/companies/{companyId}/entities/{entityType}` (create)      |
| `GET`    | `/api/v1/companies/{companyId}/entities/{entityType}/{entityId}`    |
| `PATCH`  | `/api/v1/companies/{companyId}/entities/{entityType}/{entityId}`    |
| `DELETE` | `/api/v1/companies/{companyId}/entities/{entityType}/{entityId}`    |
| `GET`    | `/api/v1/companies/{companyId}/entities/{entityType}/configuration` |
| `GET`    | `/api/v1/companies/{companyId}/entity-types` (discovery)            |

The `{entityType}` path segment is the **lowercase kebab-case slug** (`exposure`,
not `Exposure`); the PascalCase form is rejected. The set of available fields for
each type is configured per company — discover it via the `/configuration`
endpoint below.

One entity adds two **action endpoints** alongside the CRUD surface, because
their work is more than setting a field: an event closes and re-opens through
the dedicated [Event Lifecycle](/api-reference/event-lifecycle/overview)
endpoints. A `PATCH` that changes `eventStatus` directly is rejected with `409`
(`GuardedStatusFieldWrite`) — see [Flow-written status
fields](#flow-written-status-fields).

***

## The six entity types

There are exactly six CRUD entity types. Each links to its configuration schema:

| Type         | Slug           | Configuration                                                                              |
| ------------ | -------------- | ------------------------------------------------------------------------------------------ |
| Event        | `event`        | [`/entities/event/configuration`](/api-reference/entities/get-entity-configuration)        |
| Exposure     | `exposure`     | [`/entities/exposure/configuration`](/api-reference/entities/get-entity-configuration)     |
| Quote        | `quote`        | [`/entities/quote/configuration`](/api-reference/entities/get-entity-configuration)        |
| Submission   | `submission`   | [`/entities/submission/configuration`](/api-reference/entities/get-entity-configuration)   |
| Person       | `person`       | [`/entities/person/configuration`](/api-reference/entities/get-entity-configuration)       |
| Organization | `organization` | [`/entities/organization/configuration`](/api-reference/entities/get-entity-configuration) |

<Note>
  **Policy is not a CRUD entity.** Policy has a read-only `/configuration` schema
  (its slug `policy` is valid on the configuration endpoint) but no generic
  create/update/delete — its writes go through the [Policy Transaction
  endpoints](/api-reference/policies/overview). It is intentionally absent from the
  CRUD surface and from the `/entity-types` discovery response.
</Note>

<Warning>
  **Custom objects are embedded-only.** A custom object is **never** a top-level
  CRUD entity and never appears here or in `/entity-types`. Custom objects surface
  **only** as `Object` / `Object List` field definitions *inside* an entity's
  `/configuration` response (for example, an embedded coverage list on a Quote).
  You read and write them as nested values on their owning entity — there is no
  `/entities/{customObject}` route.
</Warning>

***

## Response envelope

Get and list responses use the **generic entity envelope** — identical for every
entity type. All entity-specific field values live inside `fieldModelV1Data`
(keyed by field `referenceId`); the envelope adds only system metadata:

| Field              | Description                                                                                             |
| ------------------ | ------------------------------------------------------------------------------------------------------- |
| `id`               | Entity identifier (UUID).                                                                               |
| `fieldModelV1Data` | Object of field values keyed by `referenceId`, including calculated/auto-set values and join field IDs. |
| `createdAt`        | Creation time, epoch seconds.                                                                           |
| `createdBy`        | User ID of the creator, or `null`.                                                                      |
| `updatedAt`        | Last update time, epoch seconds (equals `createdAt` if never updated).                                  |
| `updatedBy`        | User ID of the last updater, or `null`.                                                                 |
| `createdByName`    | Display name resolved from `createdBy`, or `null`.                                                      |
| `updatedByName`    | Display name resolved from `updatedBy`, or `null`.                                                      |

There is no top-level `companyId` and no cross-entity enrichment — linked entities
are not embedded; query them separately. List responses wrap the envelopes as
`{ items: [...], hasMore, totalCount }`.

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440301",
  "fieldModelV1Data": {
    "exposureName": "Acme Corp HQ",
    "exposureType": "company"
  },
  "createdAt": 1736937000,
  "createdBy": "google-oauth2|123456789",
  "updatedAt": 1736937000,
  "updatedBy": null,
  "createdByName": "Jane Adjuster",
  "updatedByName": null
}
```

`createdAt`/`updatedAt` are epoch-second integers, not ISO strings.

***

## referenceId-keyed request bodies

The request body for **create** and **update** is a **flat JSON object** whose
keys are field `referenceId`s from the company's field configuration — the same
shape for every entity type:

```json theme={null}
{
  "exposureName": "Acme Corporation",
  "exposureType": "company",
  "numberOfEmployees": 150
}
```

Discover the valid `referenceId`s (and which are required) via the
`/configuration` endpoint. Date fields are objects, not plain strings:
`{ "date": "YYYY-MM-DD", "timezone": "America/New_York" }`.

Create returns `{ id }`; delete returns `{ id, deleted: true }`.

***

## PATCH merge semantics (never PUT)

Updates are **`PATCH` only**. The handler does a **partial merge** onto the
existing `fieldModelV1Data`:

* A field **provided** with a value → updated.
* A field set to **`null`** → cleared.
* A field **omitted** → left unchanged.

<Warning>
  **Never `PUT`.** `PUT` would imply full replacement, which these endpoints do not
  do. A `PUT` to a by-id entity route is rejected with **`405 Method Not Allowed`**
  and an `Allow: PATCH` header. Use `PATCH`.
</Warning>

***

## Configuration discovery

`GET /entities/{entityType}/configuration` returns a **JSON Schema** (draft
2020-12) describing the fields available for creating or updating that type;
option-set fields carry an `enum`/`oneOf` of valid values. Every field is
included — the schema is deliberately complete so you can read the whole shape —
but each field advertises its **write tier** so you know which ones are yours to
send. Embedded objects (custom objects) appear here as nested `Object`/`Object
List` properties under their join field.

The `required` array is your **create** contract: exactly the fields you must
supply, with everything the platform produces for you already subtracted (join
fields, calculated fields, server-seeded defaults). Supply all of them and a
create cannot be rejected for missing input; omit any and you get one
`InvalidEntityShape` 400 listing every field you missed, before anything is
written. It is not a `PATCH` contract — an update merges only the fields you send.

The configuration endpoint also accepts `policy` (read-only schema), in addition
to the six CRUD slugs. Policy has no generic create endpoint, so its `required`
reflects the fields your configuration marks required in the UI rather than a
create contract.

#### Field write tiers

Each field property carries the marks below. `readOnly` is the standard JSON
Schema flag a generic tool keys off; the `x-*` companions say *why* precisely.
A field may carry more than one mark (a value that is both calculated and
system-owned is marked as both). The rule of thumb: **if a field is `readOnly`,
do not send it; otherwise it is yours to set** — including a computed-default
field, which is calculated but caller-wins.

What happens if you send a `readOnly` field anyway depends on the tier, and one
tier is unforgiving: a calculated or system-owned value is quietly recomputed or
ignored, but sending a **generated** value the platform did not originate
**fails the whole request** with a `400` (`GeneratedFieldWrite`). Resending the
exact stored value is tolerated on an update, but the safe rule is to omit a
generated field entirely — inside an array (an embedded exposure) even an exact
resend is refused.

| Tier                               | Marks                                                                | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ---------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Settable**                       | *(no mark)*                                                          | A normal caller input. Present in `required` if a create must carry it, otherwise optional.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| **Calculated**                     | `readOnly: true` + `x-calculated: true`                              | The value is computed by a configured `calculatedValue` expression and the caller's value is not kept (e.g. a rating output like `fullTermPolicyRatingResult.policyPremium`). Do not send it.                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| **Computed default (caller-wins)** | `x-calculated: true` + `x-computed-default: true` *(NOT `readOnly`)* | Calculated by the self-referential keep-if-supplied idiom (`IS_PRESENT(<field>) ? <field> : <default>`), e.g. `quoteNumber`, `quoteStatus`. **Settable**: the server fills a default only when you omit the field, and a value you supply is kept. One caveat: a few of these are also **flow-written** (below) — settable on create, but not movable by an update.                                                                                                                                                                                                                                                                          |
| **System-owned**                   | `readOnly: true` + `x-system-owned: true`                            | The platform owns the value — the reverse-listing joins it resolves on read (`referencing*`). Not a contract input; do not send it.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| **Generated**                      | `readOnly: true` + `x-generated: true`                               | The platform **originates** the value at its trigger — a sequence number minted when a policy binds, or an identifier composed from other fields. On a **policy** the value then freezes: a later endorsement keeps the value the policy bound with, even if a composition input changes. On a **quote** it is not frozen — it recomputes from its inputs on each write until the quote binds. Sending a value the platform did not originate is **rejected**, not ignored: you get a `400 GeneratedFieldWrite`. Read the value back after the write. Often also `x-calculated`, because such a field is usually expressed as a calculation. |

System ownership wins over every other mark. A generated declaration wins over
the two calculated tiers: a generated field stays `readOnly` even when its
expression is the self-referential keep-if-supplied idiom — in that case the
idiom describes how the platform carries its **own** value forward, not an
invitation to supply one. The slimmed schema surfaced by the MCP
`get_entity_schema` tool carries the same tiers as `readOnly`, `calculated`,
`computedDefault`, `systemOwned`, and `generated` booleans.

#### Flow-written status fields

A **status** field can be settable and still not be yours to move. `eventStatus`
is published as a computed default — and on **create** it genuinely is settable,
which is how a historical import loads claims that were already closed. On an
**update** it is not: a `PATCH` that changes it returns `409`
(`GuardedStatusFieldWrite`), and the message names the action to use instead.

The reason is that the status does not travel alone. Closing an event also
stamps its close date and appends to the event's open/close-history log, and the
lifecycle dates a claim reports — opened on, previously closed on, re-opened on —
are read from that log. A status moved on its own would leave the log silently
disagreeing with the claim. So the transition has its own endpoints — [Close
Event](/api-reference/event-lifecycle/close-event) and [Re-open
Event](/api-reference/event-lifecycle/re-open-event) — which do all of it in one
transaction; see the [Event
Lifecycle](/api-reference/event-lifecycle/overview) section. Sending
`eventStatus` with the value it already holds is always accepted, so an update
that merely echoes it is unaffected.

### Entity-type discovery

`GET /entity-types` is the top-level discovery endpoint. It returns the fixed set
of six CRUD entity-type descriptors — each with display names, links to its
`/entities/{entityType}` collection and `/entities/{entityType}/configuration`
schema, and the per-action permission keys a client needs. It takes no inputs
beyond the path `companyId` and is gated on `company.configuration:export`. Use
it to bootstrap a client that doesn't already know the entity model.

***

## Embedded exposures

Some entity types embed **exposures** inline rather than only linking them by id.
A Quote, Policy, or Submission can keep its **own copy** of an exposure's data
(see [Exposure](/entities/exposure) for why), carried in an embedded-exposure
field — an **array of exposures** inside the host's payload. In the
`/configuration` schema such a field appears as an array whose items are a
**`oneOf` of two modes**:

* **Reference mode** — `required: ["id"]`. The item carries the `id` (uuid) of an
  existing exposure. Every other exposure field is present but optional; any you
  supply **override** that field on the embedded copy.
* **Create mode** — no `id`. A new exposure is created inline. Its `required` is
  derived from the **Exposure configuration** (the same fields a standalone
  exposure create requires); join fields (e.g. `referencingPolicies`, `contacts`)
  are never required.

Both modes carry the same [field write tiers](#field-write-tiers) as a standalone
exposure — the Exposure's system-owned fields (`referencing*`) are marked
`readOnly` + `x-system-owned` inside each mode, so an embedded item should not
send them either.

Rating and tax output containers (`exposureRatingResponse`,
`crossSegmentRatingOutputs`, `inscipherTaxPlan`) are **settable** on an embedded
exposure. Hosted rating does not persist its results, so writing them back is how
they are stored — see [Rating](/api-reference/rating/overview).

### The three payload shapes

| You send                           | What happens                                                                                                                                                                    |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `{ "id": "<uuid>" }`               | **Copy.** The referenced exposure's current data is copied into the host at embedding time (once).                                                                              |
| `{ "id": "<uuid>", "<field>": … }` | **Copy, then override.** The canonical data is copied, then the fields you sent overlay it (you win per field). Overrides are **never written back** to the canonical exposure. |
| `{ "<field>": … }` — no `id`       | **Create.** A new canonical exposure is created **in the same transaction** as the host, and its data is embedded; the minted `id` is stored on the host record.                |

Which mode applies is decided **purely by payload shape** — whether `id` is
present — never by who is calling. An already-embedded item keyed by an `id` the
host already holds re-uses the **stored** copy as its base (it is not re-copied
from the canonical record); any fields you send still overlay it.

### Membership is a restatement

Providing an embedded-exposure field **restates the complete membership** of that
field (a JSON-Merge-Patch convention):

* Items **present** are kept (referenced) or created.
* A stored item **omitted** from the array is **removed** from the host.
* **Omitting the field entirely** is a no-op — the stored membership is untouched.

A single-cardinality embedded field behaves the same way, restating the one item.

### As-if-inserted validation

After the copy/override merge, **every** embedded item is validated **as if it
were being inserted as a standalone exposure** against the **current** Exposure
configuration — field shapes, calculated-value resolution, tenant invariants,
type casting, and entity invariants, the same checks a standalone exposure create
runs. This applies in reference mode too: embedding pre-existing canonical data
that violates today's configuration fails the write.

A failure is a structured **`400`** whose message names the offending item by
path (e.g. `additionalExposures[1]`):

| Problem code                       | Meaning                                                            |
| ---------------------------------- | ------------------------------------------------------------------ |
| `InvalidEmbeddedExposureReference` | An `id` did not resolve to an existing exposure in this company.   |
| `InvalidEntityShape`               | The item's shape is invalid — wrong field types or unknown fields. |
| `InvalidFieldModelV1Data`          | A field value failed validation against the configuration.         |
| `TenantInvariantViolation`         | A tenant-configured invariant (JEXL) rejected the item.            |
| `EntityInvariantViolation`         | A platform entity invariant rejected the item.                     |

***

## Permissions

Permissions follow the format **`company.{entity}:{action}`** (`action` ∈
`read | create | update | delete`). However, the **exact key varies by type** —
the platform reuses existing permission families rather than minting a new one
per entity. Treat these as the source of truth (and confirm at runtime via the
per-type `permissions` block in the `/entity-types` response):

| Type         | Read                                                      | Create                              | Update                              | Delete                              |
| ------------ | --------------------------------------------------------- | ----------------------------------- | ----------------------------------- | ----------------------------------- |
| Exposure     | `company.insured:read`                                    | `company.insured:create`            | `insured:update`                    | `insured:delete`                    |
| Event        | `company.claim:read`                                      | `company.claim:create`              | `company.claim:create`              | `company.claim:delete`              |
| Quote        | `company.quote:read` (list) / `company.policy:read` (get) | `company.policy:create`             | `company.policy:create`             | `company.quote:delete`              |
| Submission   | `company.submission:read`                                 | `company.submission:create`         | `company.submission:update`         | `company.submission:delete`         |
| Person       | `company.fmv1_custom_object:read`                         | `company.fmv1_custom_object:create` | `company.fmv1_custom_object:update` | `company.fmv1_custom_object:delete` |
| Organization | `company.fmv1_custom_object:read`                         | `company.fmv1_custom_object:create` | `company.fmv1_custom_object:update` | `company.fmv1_custom_object:delete` |

The per-type `/configuration` endpoint has its own read permission (e.g.
`company.event:export` for Event, `company.insured:read` for Exposure) — see the
[Get Entity Configuration](/api-reference/entities/get-entity-configuration)
endpoint.

***

## Listing, filtering & sorting

The list endpoint supports:

| Parameter       | Description                                                                |
| --------------- | -------------------------------------------------------------------------- |
| `filterText`    | Free-text search against the type's configured display field.              |
| `sortBy`        | `createdAt` (default), `updatedAt`, or any configured field `referenceId`. |
| `sortDirection` | `asc` or `desc`.                                                           |
| `pageNumber`    | Zero-based page index (default `0`).                                       |
| `pageSize`      | Items per page (default `51`).                                             |
