Skip to main content

V1 API Changelog

Track changes, additions, and deprecations to the V1 API.

2026-06-16

Documentation: accuracy reset across the API reference

The API reference was audited end-to-end against the live route surface and corrected so every documented endpoint, schema, status code, and permission matches what the API does today. No runtime behaviour changed — these are documentation corrections only.
  • Removed phantom endpoint groups. Three capability groups that had never shipped as endpoints (resolve-address / Address Tools, Event Financials, and quote send) were removed from the spec; they were never callable.
  • Corrected every endpoint’s schema, examples, status codes, and permissions to match the implementation, including the bare (un-prefixed) permission strings that the authorization guard actually checks: insured:update / insured:delete for the Exposure update/delete, policy:update for the policy cancel/endorse/reinstate transactions, and policy:delete for transaction delete. Create/read operations keep their company.-prefixed permissions. The Event configuration endpoint requires company.event:export.
  • Raw Authorization header. External API authentication takes your API key as the raw Authorization header value with no scheme prefix — not Bearer and not ApiKey , and never X-API-Key. See Generating API Keys.
  • Narrative pages reconciled. The overview, roadmap, getting-started, object-primitives, and data-models pages were corrected: Submissions, Persons/Organizations, Notes, Tasks, and Company Files are documented as available today (not “planned”); the unified entity envelope (fieldModelV1Data with epoch-second createdAt/updatedAt, no top-level companyId) and its { items, hasMore, totalCount } / zero-based pageNumber list shape are documented accurately; and broken internal links were repaired.

2026-06-11

Added: per-placement organize — move/categorize a shared file under one entity

folderId and category are per-placement attributes, so the owner-less PATCH /files/{fileId} cannot address them once a file is shared (its 409 Conflict below). The new placement update lifts that limitation:
  • PATCH /api/v1/external/companies/{companyId}/files/{fileId}/placements/{placementId} — update ONE placement’s folderId (a folder of the placement’s owner, or null for the owner’s top level) and/or category (free-text ≤255 chars, or null to clear). Absent fields are untouched; at least one must be present. Returns 200 { placementId, fileId, entityType, entityId, folderId, category }; the file’s other placements are never affected. Placement ids come from GET /files/{fileId}/placements or the share response. Requires company.file:update.
  • The 409 Conflict body returned by PATCH /files/{fileId} for folderId/category on a shared file now points at the placement endpoint: File has multiple placements: {fileId}. Update one placement instead: PATCH /files/{fileId}/placements/{placementId} (enumerate them with GET /files/{fileId}/placements). Single-placement files are unaffected — either endpoint works there.

Added: file placements — share one file across entities

A file can now be placed on more than one entity at a time. Sharing adds a placement — never a copy: the bytes are stored once and every placement sees the same current version and history. Folder location and category are per placement; displayName stays on the file. Sharing is same-company only.
  • GET /api/v1/external/companies/{companyId}/files/{fileId}/placements — list everywhere a file appears ([{ placementId, entityType, entityId, entityDisplayName, folderId, category, createdAt }]). Requires company.file:read.
  • POST /api/v1/external/companies/{companyId}/files/{fileId}/placements — share the file to another owner (entityType, entityId?, optional folderId of the target owner). Returns 201 { placementId, fileId, entityType, entityId, folderId }; a duplicate share to the same owner is 409 Conflict. Requires company.file:create.
  • DELETE /api/v1/external/companies/{companyId}/files/{fileId}/placements/{placementId} — remove the file from ONE owner. While other placements remain, the file and its content are untouched; removing the last placement deletes the file and reclaims storage (fileDeleted: true). Requires company.file:delete.
  • The file responses now carry placement information: list items gained placementId and placementCount; GET /files/{fileId} gained placementCount, and its owner fields (entityType/entityId/folderId/category) now describe the file’s primary (oldest) placement.
  • PATCH /files/{fileId} on a shared file can only change displayName; an owner-less folderId/category update is ambiguous across placements and returns 409 Conflict. Single-placement files behave exactly as before.
  • DELETE /files/{fileId} removes the file everywhere (all placements) — unchanged for single-placement files; use the placements endpoint for per-entity removal.

2026-06-10

Added: file categories

Files can now carry a free-text category label (max 255 characters). There is no configured category list — any non-blank string is a valid category.
  • PATCH /api/v1/external/companies/{companyId}/files/{fileId} accepts an optional category field alongside displayName/folderId: a string sets the label, an explicit null clears it, and an absent field leaves it untouched.
  • The file responses (GET /files/{fileId} metadata and the GET /files list items) already include category (null when unset).

Breaking: Company Files API rebuilt on signed URLs

The Company Files API was rebuilt end-to-end. File bytes no longer travel through the API — uploads and downloads now go straight to cloud storage via short-lived signed URLs, and every request/response shape changed. There is no compatibility mode for the old contract. See the Company Files overview for the new workflow.
  • Upload is now a two-phase handshake. POST /api/v1/external/companies/{companyId}/files no longer accepts multipart/form-data; it takes a JSON upload intent (entityType, entityId?, folderId?, fileName, contentType, byteSize) and returns 201 { fileId, versionId, uploadUrl }. PUT the bytes to uploadUrl (pinned to the declared content type and byte size, 15-minute expiry), then POST /files/{fileId}/finalize with the versionId to make the file visible.
  • Files and folders are now owner-scoped. Every file/folder belongs to a configured entity (entityType + entityId) or the company level (entityType: "company"). The folder endpoints keep their five paths but now require the owner on create/tree and return new shapes; GET /folders returns the owner’s whole tree as { folders: [{ id, parentFolderId, name }] } (no pagination), and GET /folders/{folderId} returns { folder, folders, files, totalCount } instead of the mixed contentType-discriminated item list.
  • GET /files/{fileId} returns the new metadata shapedisplayName/fileName/contentType/byteSize/status/folderId/category/createdAt/updatedAt/uploadedAt/uploadedBy replace the legacy name/mimeType/entityName/userDate fields.
  • PATCH /files/{fileId} renames with displayName (was name) and/or moves with folderId; it returns { id } (was the full file). PATCH /folders/{folderId} keeps name/parentFolderId but returns { id }; reparenting is cycle-checked. DELETE /folders/{folderId} now reports the cascade: { id, deleted, deletedFolders, deletedFiles }.

Removed: binary download and multipart upload

  • GET /api/v1/external/companies/{companyId}/files/{fileId}/content (binary stream) is removed — use the new GET /files/{fileId}/download-url, which returns { url, expiresAt, fileName, contentType, byteSize } (requires the new company.file:download permission), and fetch the bytes from the signed url.
  • The multipart/form-data upload body is removed — see the upload handshake above.

Added: list files

  • GET /api/v1/external/companies/{companyId}/files — list an owner’s files (entityType, entityId?, optional folderId placement filter, 1-based page/pageSize). Previously files were only discoverable through folder contents. Requires company.file:read.
  • POST /api/v1/external/companies/{companyId}/files/{fileId}/finalize and GET /api/v1/external/companies/{companyId}/files/{fileId}/download-url — the new halves of the signed-URL upload/download workflow.

2026-06-07

Removed: QuoteBindError object primitive

The Object: QuoteBindError object primitive (and its Fmv1QuoteBindError OpenAPI schema) has been removed. It was never produced at runtime — no endpoint ever returned or accepted a quoteBindErrors value — so this removal is not expected to affect any integration. The four remaining object primitives (Address, CoverageLimit, Currency, Date) are unchanged. See the Object Primitives reference.

2026-06-01

Added: Notes endpoints

Notes can now be managed via the external API. Notes are simple text records attached to a top-level Field Model V1 entity (Event, Exposure, Quote, Submission, Person, Organization, Policy). The parent entity type travels as the entityType query parameter on every verb; permission is gated by the parent entity (read for GET, update for write).
  • GET /api/v1/external/companies/{companyId}/notes — List notes for a parent entity (1-based page/pageSize)
  • POST /api/v1/external/companies/{companyId}/notes — Create a note (returns { id }, HTTP 201)
  • GET /api/v1/external/companies/{companyId}/notes/{noteId} — Get a note
  • PATCH /api/v1/external/companies/{companyId}/notes/{noteId} — Update a note’s body
  • DELETE /api/v1/external/companies/{companyId}/notes/{noteId} — Soft-delete a note

New: Tasks API

Added 5 endpoints for managing company tasks at /api/v1/external/companies/{companyId}/tasks:
  • GET /tasks — List tasks (paginated, filter by status, assigneeId, entityType, entityId). Requires company.task:read.
  • POST /tasks — Create a task. Returns { id }. Requires company.task:create.
  • GET /tasks/{taskId} — Get a single task. Requires company.task:read.
  • PATCH /tasks/{taskId} — Partial update (only changed fields; unknown fields rejected). Requires company.task:update.
  • DELETE /tasks/{taskId} — Soft delete. Returns { id, deleted: true }. Requires company.task:delete.
A task has a name, description, status (Not Complete / Complete), ISO 8601 deadline, optional linked entity snapshot ({ type, id, name }), and assignees (company user IDs — non-members are rejected with 400). See the Tasks overview.

Breaking: Full-term policy transaction reshape

The segmented Policy Transaction API moved to the full-term (“Model-B”) design. Affects new-business, endorse, cancel, reinstate, and renew.
  • Term bounds come solely from fullTermPolicyInfo. policyStartDate / policyEndDate are read from fieldModelV1Data.policy.fullTermPolicyInfo on NEW_BUSINESS / RENEW — the top-level policyStartDate / policyEndDate (and RENEW’s top-level previousPolicyId / newPolicyStartDate / newPolicyEndDate) parameters are removed. previousPolicyId now lives in fullTermPolicyInfo.
  • fullTermPolicyBilling renamed to fullTermPolicyBillingInfo in every request, response, and bordereau column path.
  • policyStatus is a segment-scoped policy field with lowercase values "active" / "cancelled" — no longer inside fullTermPolicyInfo.
  • ENDORSE now has five channels: deltas XOR fullTermDeltas (the latter restricted to policy.fullTermPolicyInfo, no dates), plus additive fullTermPolicyBillingInfo, fullTermPolicyRatingResult, and crossSegmentRatingOutputs. List elements are addressed by predicate — exposures[id = '…'].
  • CANCEL / REINSTATE take the date plus optional whole-object fullTermPolicyBillingInfo / fullTermPolicyRatingResult. CANCEL flips per-segment policyStatus to "cancelled" and records a single cancellationEffectiveOnDate (uniform across the term); REINSTATE flips policyStatus back to "active" and clears cancellationEffectiveOnDate (no reinstatement date field). A reinstate that would leave a coverage gap is rejected — that scenario is a new policy term. policyEarlyTerminationDate is removed.
  • Rating output split into fullTermPolicyRatingResult (policy-root, hoisted) and crossSegmentRatingOutputs (element-level, inline). Responses hoist fullTermPolicyInfo, fullTermPolicyBillingInfo, and fullTermPolicyRatingResult.

2026-05-15

New: Company Files API (folders + files)

Added 10 endpoints for managing company-level folders and files: Folders: POST, GET, GET /{folderId}, PATCH /{folderId}, DELETE /{folderId} under /api/v1/external/companies/{companyId}/folders Files: POST (multipart upload), GET /{fileId}, GET /{fileId}/content (binary download), PATCH /{fileId}, DELETE /{fileId} under /api/v1/external/companies/{companyId}/files Key capabilities:
  • Create nested folder hierarchies with parentFolderId
  • Upload files via multipart/form-data, optionally placing them in a folder
  • Stream binary file content with correct Content-Type and Content-Disposition headers
  • Rename and move files/folders (including moving to root by setting parent to null)
  • Recursive soft-delete of folders (deletes all contents)
All endpoints use company.file:{action} permissions. See the Company Files overview for details.
These are company-level file endpoints only. Entity-scoped file endpoints (attached to exposures, policies, events) are planned for a future release.

2026-05-01

Event→Policy relationship migrated to eventPolicy Join field (additive)

Event responses now surface the associated policy in two places: the existing top-level policyId and the new eventPolicy key inside fieldModelV1Data. Both reflect the same value — policyId stays at the top level for backwards compatibility, while eventPolicy is the underlying Join: Policy field where the value is stored. What changed:
  • GET /api/v1/external/companies/{companyId}/events and /events/{eventId} responses include eventPolicy inside fieldModelV1Data alongside the pre-existing top-level policyId. No fields were removed.
  • POST and PUT event endpoints continue to accept policyId as a top-level request param. The server maps it to fieldModelV1Data.eventPolicy internally; clients that already submit policyId need no changes.
  • policyId is no longer stored on a dedicated events.policy_id column on the FMV1 read/write paths — it now lives in fieldModelV1Data.eventPolicy, consistent with how eventInsureds was migrated previously. The external API contract is unchanged for existing integrators.
This is a non-breaking change. Integrators can ignore eventPolicy and continue using policyId, or migrate to reading the value from fieldModelV1Data.eventPolicy to align with the rest of the field model.

Address.zipCode must be a JSON string (breaking)

Address object-primitive writes that send zipCode as a JSON number are now rejected with 400 and problemCode: "InvalidAddressZipCode". Previously, numeric ZIPs were accepted by the API but silently lost their leading zeros — 02140 parses as 2140, corrupting the stored address. Affected endpoints: every FMV1 create/update endpoint that can carry an Address value (top-level field or nested inside a custom object), including all exposure, event, quote, policy-transaction, and custom-object writes. What to send: quote ZIP codes in your payload — "zipCode": "02140", never "zipCode": 02140. The Address sub-field reference and the Fmv1Address OpenAPI schema both call this out explicitly. Why this is most likely to bite:
  • Spreadsheets (Excel / Google Sheets) auto-coerce ZIP-shaped cells to numbers — export as text or wrap in =TEXT(...) before serializing.
  • OpenAPI clients / codegen that infer the zipCode JSON type from a sample value rather than the schema (which has always been type: string).
  • LLM-generated requests that “helpfully” unquote numeric-looking strings.

2026-04-29

Documentation: Object Primitives

  • New Object Primitives reference page documenting the five FMV1 built-in object shapes (Address, CoverageLimit, Currency, Date, QuoteBindError) — sub-field tables, JSON examples, the strict-completeness rule, and List cardinality.
  • OpenAPI spec now exports reusable Fmv1Address, Fmv1CoverageLimit, Fmv1Currency, Fmv1Date, and Fmv1QuoteBindError schemas under components.schemas for client-codegen consumers. Schemas mark every sub-field as required.

2026-04-28

Strict object-primitive sub-field validation (breaking)

When a request body for an FMV1 create/update endpoint includes an object-primitive value (Address, CoverageLimit, Currency, Date, QuoteBindError), every declared sub-field must now be present and non-empty. Affected endpoints:
  • POST/PATCH /api/v1/external/companies/{companyId}/exposures and /exposures/{id}
  • POST/PATCH /api/v1/external/companies/{companyId}/events and /events/{id}
  • POST/PATCH /api/v1/external/companies/{companyId}/quotes and /quotes/{id}
  • All segmented policy transaction endpoints (new-business, endorse, renew)
  • POST/PATCH /api/v1/external/companies/{companyId}/custom-objects/{objectType} and /{objectId}
What changed:
  • A null, undefined, or empty-string ("") sub-field on a present object-primitive value now returns 400 with Field '<parentField>' is missing required sub-field '<refId>'. Previously, partial object primitives were silently accepted and surfaced as confusing rating errors downstream. The most common case is Address without county — ensure upstream data includes a county before submitting.
  • Numeric 0 (e.g. Currency.value: 0) and Boolean false are still valid — the rule only treats null / undefined / "" as missing.
  • Whitespace-only strings (e.g. " ") are NOT treated as missing by this validator. A consumer sending county: " " will pass this check; downstream rating may still reject it. Trim/normalize sub-field strings client-side before submitting.
  • Custom-object sub-fields keep their existing requiredCondition rules unchanged.
  • Omitting the parent object-primitive field entirely is unchanged — the strict rule only fires when the parent value is provided.