This API was rebuilt on 2026-06-10 with breaking changes: the multipart
upload and the binary
/content download were replaced by the signed-URL
workflow below, and every response shape changed. See the
changelog for the full diff.Key Concepts
- Owner — every file and folder belongs to an owner: a configured Field Model V1 entity (
entityType+entityId, e.g.entityType: "exposure") or the whole company (entityType: "company", noentityId).entityTypeis the lowercase kebab-case wire slug — PascalCase likeExposureis rejected with a400. - File — a display identity (
displayName, renameable) over an immutable uploaded version (originalfileName,contentType,byteSize). A file’sstatusispendingfrom upload intent until finalize, thenready— unless malware scanning holds itquarantined(scan verdict outstanding) or marks itinfected(downloads permanently refused). - Version — every upload is an immutable version; a file keeps its full version history. Re-uploading (a new-version intent → PUT → the same finalize) adds a version and repoints the file at it, so the file keeps its id, placements, categories, and history — the download URL always serves the current version. List a file’s versions to see its history, with the current one flagged.
- Placement — one appearance of a file under an owner. A file can be placed on several entities at once: sharing adds a placement, never a copy — every placement sees the same current version and history. Folder location and
categoryare per placement;displayNameis per file. Removing a placement detaches the file from that owner; the file itself is deleted only when its last placement is removed. - Category — a label on a placement or on a folder, drawn from a configured list. Each entity type has its own list, which company admins manage in Company Settings → File Categories — Policy categories and Event categories are independent lists.
GET /v1/files/categories?entityType=<slug>returns that type’s configured categories in their configured order plus the values in use on its placements that the list does not cover, each flagged so you can tell them apart; without the param it returns the distinct categories across the company’s live placements. Setting a category that is not on the list is rejected with a400— draw from the entries flaggedconfigured: true. The same list serves both: a Policy folder and a Policy file draw their category from the Policy list. A folder’s category applies to the folder itself only, never to the files inside it. See File categories below. - Folder — a named node in the owner’s folder tree (
parentFolderIdnull = top level), optionally carrying its owncategory. A folder only ever holds files and subfolders of its own owner. - Signed URLs — uploads
PUTto a signeduploadUrlpinned to the declared content type and byte size; downloadsGETa signedurlminted per request. Both expire after 15 minutes — request fresh ones, never cache them. - Soft delete — file and folder deletes are soft; folder deletes cascade recursively through the subtree and its files.
API Endpoints
Files
Folders
Permissions
Upload Workflow
Uploading is a three-step handshake; only steps 1 and 3 touch this API. 1. Declare the upload (intent)Content-Type you declared,
and x-goog-content-length-range: <byteSize>,<byteSize>:
pending to ready):
Download Workflow
Mint a signed URL, then fetch the bytes from storage:id), then mint every URL in a single POST instead of
one round trip per file. Send up to 100 fileIds; add "disposition": "inline"
to preview rather than download. The response pairs each url with its fileId,
using the same per-file fields as the single-file endpoint:
ready, the whole request is rejected
(404 or 409) and no URLs are returned — a mixed batch can never partially
leak. An empty fileIds array or more than 100 ids returns 400.
Re-upload Workflow (new version)
To replace a file’s contents without losing its id, placements, categories, or history, upload a new version. It is the same two-phase handshake as a first upload, but the intent targets the existing file — and it reuses the same finalize endpoint. Re-upload is gated byfile.edit (it changes an
existing file), not file.upload.
1. Declare the new version (re-upload intent)
Content-Type and x-goog-content-length-range are
required).
3. Finalize with the SAME endpoint — there is no separate re-upload
finalize. Pass the new versionId; finalize flips the version ready and
repoints the file at it, so the new bytes silently become current:
ready version to make it current again — pure metadata, so no bytes
move, nothing is deleted, and the version you were on stays in history. Take a
versionId from the version listing and restore it (gated by its own
file.restore-version permission, separate from file.edit):
ready
version can be restored; restoring a pending version or the one that is
already current returns 409, and an unknown version returns 404.
Files on entities
Files rarely live at the company level — most belong on an entity. Name the owner withentityType + entityId, where entityType is the entity’s
lowercase kebab-case wire slug (event, exposure, quote, submission,
person, organization, or policy). PascalCase like Quote is rejected
with a 400 — always send the slug.
Upload a file onto a quote. The handshake is the same three steps as the
Upload Workflow above; only the intent changes — it names the owning
entity instead of "company":
entityType +
entityId query pair (only ready files are returned):
File categories
A category is a label on a placement (so a shared file can be categorized differently under each owner). Company admins configure a category list per entity type in Company Settings → File Categories; those lists give the vocabulary its membership and its order, and a category has to be on the list before it can be applied to a file. A new company starts with a standard starter list per entity type (for example Correspondence, Court Filings, Billing, Medical Records, Photos, First Notice of Loss, and Other on events), which admins are free to rename, reorder, extend, or delete. Read one entity type’s vocabulary. PassentityType — the same lowercase
kebab-case owner slug the rest of this API uses (company, quote, policy,
exposure, event, submission, person, organization). PascalCase like
Quote is rejected with a 400:
entityTypeCategories lists the configured categories first, in the order
the admin arranged them, followed by every label actually in use on that entity
type’s live placements that no configured category covers —
"configured": false. Those unconfigured values are real: a label written
before the list was enforced, or a category an admin has since removed. They
stay visible and filterable rather than disappearing, and a file already
carrying one can be sent that same value again — but they cannot be applied to
any other file. categories repeats the same names in the same order without
the flags, for consumers that only need the list. Each list is scoped to one
entity type, so a Policy category never appears under Event.
Writing a category. Send a name flagged
configured: true for that owner
type. Matching is case-insensitive and the configured spelling is what gets
stored, so loss runs is saved as Loss Runs. category: null clears it and
is always accepted. Anything else is a 400 whose message lists the configured
categories — including every write while an entity type’s list is still empty.entityType:
category via
Update File or Update File Placement must name a category configured
for that file owner’s type (matched case-insensitively; the configured spelling
is what is stored). A label no definition covers is rejected with a 400
listing the configured categories — unless the placement already carries that
exact label, which may always be re-sent (so echoing back what you just read
never starts failing). category: null (or blank) clears and is always
accepted.
Organizing with Folders
Sharing a File Across Entities
The same document often belongs on more than one entity — a loss run on both an Event and its Submission. Add a placement instead of uploading twice; the bytes are stored once and every placement stays in sync:409 Conflict. Removing the file’s last
placement deletes the file itself and reclaims its stored content
("fileDeleted": true).
Folder location and category are per placement, so once a file is
shared the owner-less PATCH /v1/files/{fileId} can no longer address them —
it returns 409 Conflict pointing at the per-placement update; displayName
stays on the file (one name everywhere) and remains renameable there.