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, stored as free text. Each entity type has its own configured category list, which company admins manage in Company Settings → File Categories — Policy categories and Event categories are independent lists, and a company that has configured none simply has an empty list.
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. Writes are never validated against the configured list — setting acategoryaccepts any label up to 255 characters — so draw from the returned vocabulary to keep it consistent. See File categories below. - Folder — a named node in the owner’s folder tree (
parentFolderIdnull = top level). 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 bycompany.file:update (it changes an
existing file), not company.file:create.
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 the same
company.file:update):
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), stored as free text. Company admins configure a category list per entity type in Company Settings → File Categories; those lists give the vocabulary its membership and its order. 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: an import, an API
write, or a category an admin has since removed. They stay visible and
filterable rather than disappearing. 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.
Read the whole company’s in-use vocabulary. Omit entityType:
category via Update File or
Update File Placement is never validated against the configured list — any
non-blank string up to 255 characters is accepted (blank clears the category).
Read the vocabulary and reuse its values to keep it consistent; nothing rejects
a new label.
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.