Skip to main content
To list policies see the Policy Transactions Api. Policy is not one of the entity types this API serves — policy and policies are rejected here. Listing lives at List Policies (GET /api/v1/companies/{companyId}/policies/list).
Every top-level Field Model V1 entity is managed through one parametric CRUD surface, keyed on {entityType}: 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. Two entities add 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 endpoints. eventStatus is system managed, so a value sent for it on a generic create or PATCH is ignored — see Flow-written status fields. An exposure is screened against OFAC sanctions lists through the OFAC Screening endpoint. Creating or updating an exposure here stores its screening request values like any other field data and runs no screening — no provider call, no result.

Page layouts for external renderers

Read one configured page with GET /api/v1/companies/{companyId}/pages/{pageKey}/layout, using a fixed page key such as eventViewDetails, quoteViewDetails, or policyViewDetails. The call requires the page entity’s view permission (event.view, quote.view, or policy.view in these examples), without configuration.view. The response includes that page’s cards, placements, rendering arguments, and unevaluated display/required conditions, including reachable nested cards. It excludes unrelated pages and record values. Read field schemas through the entity configuration endpoint (or /policies/configuration) and fetch records through their own authorized endpoints. Layout access does not grant permission to create, edit, bind, or run any other action. Display conditions are presentation rules, not access controls. The configuration-authoring describe/cards and describe/card endpoints continue to require configuration.view.

The six entity types

There are exactly six CRUD entity types. Each links to its configuration schema:
Policy is not a CRUD entity. Policy has a dedicated read-only /policies/configuration schema, but policy is not valid on the parametric entity-configuration route and there is no generic policy create/update/delete endpoint. Policy writes go through the Policy Transaction endpoints. It is intentionally absent from the CRUD surface and from the /entity-types discovery response.
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.

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: 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 }.
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 referenceIds from the company’s field configuration — the same shape for every entity type:
Discover the valid referenceIds (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.
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.

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 parametric configuration endpoint accepts the six CRUD entity slugs; it does not accept policy. Use GET /policies/configuration for the read-only policy field schema. Policy has no generic create endpoint, so that schema’s required list reflects the fields your configuration marks required in the UI rather than a generic entity-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. Sending a readOnly field anyway is tolerated: the value is quietly recomputed or ignored — the write succeeds and the server-managed value wins. A value for a field the configuration does not declare at all still fails the request. Every calculated field also carries x-repeatable: whether its expression is repeatable — same stored data, same answer (true) — or may answer differently on each evaluation (false — a sequence mint, a clock read). A LIVE field is always repeatable. System ownership wins over every other mark. The slimmed schema surfaced by the MCP get_entity_schema tool carries the same facts as readOnly, calculated, systemOwned, variant, and repeatable.

Flow-written status fields

A status field can be published and still not be yours to move. Two mechanisms do that, and they differ in what happens when you try. eventStatus is a system managed field. A value you send for it is silently ignored on every generic channel — create and PATCH alike — leaving the stored value unchanged. You get a 200, not an error. 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 and Re-open Event — which do all of it in one transaction; see the Event Lifecycle section. To load a claim that was already closed, supply its eventOpenCloseHistory rather than its status: the platform derives the status from the log’s last entry. quoteStatus and submissionStatus are guarded instead: a generic update that moves them is refused with 409 (GuardedStatusFieldWrite), and the message names the flow that owns the transition. Sending the value the field already holds is always accepted, so an update that merely echoes a status is unaffected. Move those statuses through the dedicated lifecycle endpoints instead:
  • POST /api/v1/companies/{companyId}/submissions/{submissionId}/close closes the submission and cancels its active quotes atomically.
  • POST /api/v1/companies/{companyId}/submissions/{submissionId}/reopen recomputes the submission status from its live quotes.
  • POST /api/v1/companies/{companyId}/quotes/{quoteId}/cancel cancels an unbound active quote.
They enforce the same transition guards as the app: an invalid transition is a 409, while the generic status-field guard remains in place.

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 requires no permission — any API key of the Company may call it. 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 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 the caller-required Exposure create contract: the same fields a standalone exposure create requires after calculated values, server-seeded defaults, and joins are removed. Join fields (e.g. referencingPolicies, contacts) are never required.
If a create-mode item omits more than one required input, the API returns one InvalidEntityShape 400 whose userMessages list every missing field for that item. Each message includes the item’s path (for example exposures[1]), and the host transaction rolls back without persisting the host or any inline-created exposure. Both modes carry the same 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.

The three payload shapes

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]):

Permissions

Permissions follow the format <resource>.<verb> (verb ∈ view | create | edit | delete | export) — each entity type is its own catalog resource, so the key is a straight function of the type. Treat these as the source of truth (and confirm at runtime via the per-type permissions block in the /entity-types response): The per-type /configuration endpoint uses the same read permission as the entity itself (e.g. event.view for Event, exposure.view for Exposure) — see the Get Entity Configuration endpoint.

Export presets

An export preset is a saved, named column selection for one entity type’s export screen: which columns to export, in which order. The Create Export Preset For Member endpoint creates one on behalf of a company member you name with ownerUserId, and requires export-preset.create-for-member. The preset is private to that member, who finds it in their own export screen for that entity type. Every field key is validated on write against the columns the company’s export surface offers, so an unknown key is refused rather than saved. entityType: "bordereau" creates a preset for the Export Bordereau screen; its keys are fixed:<key> for a built-in bordereau column and field:<key> for a column configured on the bordereau export surface.

Listing, filtering & sorting

The list endpoint supports: That table is the whole list. Any other query parameter is rejected with 400 InvalidRequest, and the message names the ones the endpoint accepts. This matters most for paging, which has exactly one spelling here — pageNumber — so ?page=, ?offset=, ?skip= and ?cursor= are errors. They used to be ignored, which meant a book larger than one page could not be walked at all: the endpoint kept returning the first page with hasMore: true, and a page=N loop re-read it forever without ever failing.

Walking a whole book

Start at pageNumber=0 and increment until hasMore is false. Pass an explicit sortBy/sortDirection so the ordering is fixed for the duration of the walk — the default sort is updatedAt, which a concurrent write can move under you.

Updating a bound quote

A quote whose quoteStatus is bound no longer accepts updates. PATCH /entities/quote/{quoteId} on one is refused with 409 and code QuoteBoundImmutable, whatever the body holds, and the error carries referencingPolicy so you have the policy id without a second call. The reason is that there is nothing an edit could accomplish: a policy’s values live in its own segments, written once by the bind, and nothing re-reads the quote afterwards. An accepted edit would only move the quote away from the policy it is supposed to describe. To change what is in force, endorse the policy (POST /policies/{policyId}/transaction/endorse). To model a variation, create a new quote.