{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 withGET /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.Response envelope
Get and list responses use the generic entity envelope — identical for every entity type. All entity-specific field values live insidefieldModelV1Data
(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 fieldreferenceIds from the company’s field configuration — the same
shape for every entity type:
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 arePATCH 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.
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}/closecloses the submission and cancels its active quotes atomically.POST /api/v1/companies/{companyId}/submissions/{submissionId}/reopenrecomputes the submission status from its live quotes.POST /api/v1/companies/{companyId}/quotes/{quoteId}/cancelcancels an unbound active quote.
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 theid(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. Itsrequiredis 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.
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.
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 structured400 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 withownerUserId, 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 atpageNumber=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 whosequoteStatus 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.