Skip to main content
The Company Files API manages files and their folder organization. File bytes never travel through the API itself — uploads and downloads both go straight to cloud storage via short-lived signed URLs, while the API handles metadata, placement, and permissions as plain JSON.
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. an Exposure) or the whole company (entityType: "company", no entityId).
  • File — a display identity (displayName, renameable) over an immutable uploaded version (original fileName, contentType, byteSize). A file’s status is pending from upload intent until finalize, then ready — unless malware scanning holds it quarantined (scan verdict outstanding) or marks it infected (downloads permanently refused).
  • 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 category are per placement; displayName is per file. Removing a placement detaches the file from that owner; the file itself is deleted only when its last placement is removed.
  • Folder — a named node in the owner’s folder tree (parentFolderId null = top level). A folder only ever holds files and subfolders of its own owner.
  • Signed URLs — uploads PUT to a signed uploadUrl pinned to the declared content type and byte size; downloads GET a signed url minted 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

MethodEndpointDescription
POST/v1/filesCreate an upload intent (returns the signed uploadUrl)
POST/v1/files/{fileId}/finalizeFinalize an upload (makes the file ready)
GET/v1/filesList an owner’s files (paginated, optional folder filter)
GET/v1/files/{fileId}Get file metadata
GET/v1/files/{fileId}/download-urlMint a signed download URL
PATCH/v1/files/{fileId}Rename and/or move a file
DELETE/v1/files/{fileId}Soft-delete a file (every placement)
GET/v1/files/{fileId}/placementsList everywhere a file appears
POST/v1/files/{fileId}/placementsShare a file to another entity
PATCH/v1/files/{fileId}/placements/{placementId}Move/categorize a file under one entity
DELETE/v1/files/{fileId}/placements/{placementId}Remove a file from one entity

Folders

MethodEndpointDescription
POST/v1/foldersCreate a folder
GET/v1/foldersGet an owner’s folder tree (flat adjacency list)
GET/v1/folders/{folderId}List folder contents (subfolders + files)
PATCH/v1/folders/{folderId}Rename and/or move a folder
DELETE/v1/folders/{folderId}Soft-delete a folder subtree (recursive)

Permissions

OperationPermission
List files, Get file, List placements, Get folder tree, List folder contentscompany.file:read
Create upload intent, Finalize upload, Create folder, Add placementcompany.file:create
Rename/move file, Update placement, Rename/move foldercompany.file:update
Delete file, Remove placement, Delete foldercompany.file:delete
Get download URLcompany.file:download

Upload Workflow

Uploading is a three-step handshake; only steps 1 and 3 touch this API. 1. Declare the upload (intent)
curl -X POST https://app.aiinsurance.io/api/v1/external/companies/{companyId}/files \
  -H "Authorization: YOUR-API-KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "entityType": "company",
    "fileName": "master-agreement.pdf",
    "contentType": "application/pdf",
    "byteSize": 482133
  }'
# → {"fileId": "...", "versionId": "...", "uploadUrl": "https://storage.googleapis.com/..."}
2. PUT the bytes to the signed URL (direct to storage). Two headers are signed into the URL and must be sent exactly: the Content-Type you declared, and x-goog-content-length-range: <byteSize>,<byteSize>:
curl -X PUT "UPLOAD-URL-FROM-STEP-1" \
  -H "Content-Type: application/pdf" \
  -H "x-goog-content-length-range: 482133,482133" \
  --data-binary @master-agreement.pdf
3. Finalize (flips the file from pending to ready):
curl -X POST https://app.aiinsurance.io/api/v1/external/companies/{companyId}/files/{fileId}/finalize \
  -H "Authorization: YOUR-API-KEY" \
  -H "Content-Type: application/json" \
  -d '{"versionId": "VERSION-ID-FROM-STEP-1"}'
# → {"fileId": "...", "versionId": "..."}
If you abandon an upload after step 1, the pending file stays invisible and is reclaimed automatically — finalize is what publishes it.

Download Workflow

Mint a signed URL, then fetch the bytes from storage:
curl https://app.aiinsurance.io/api/v1/external/companies/{companyId}/files/{fileId}/download-url \
  -H "Authorization: YOUR-API-KEY"
# → {"url": "https://storage.googleapis.com/...", "expiresAt": "...", "fileName": "master-agreement.pdf", ...}

curl -o master-agreement.pdf "URL-FROM-ABOVE"

Organizing with Folders

# Create a folder for an exposure
curl -X POST https://app.aiinsurance.io/api/v1/external/companies/{companyId}/folders \
  -H "Authorization: YOUR-API-KEY" \
  -H "Content-Type: application/json" \
  -d '{"entityType": "Exposure", "entityId": "EXPOSURE-ID", "name": "Loss Runs"}'
# → {"id": "folder-uuid"}

# Read the exposure's folder tree
curl "https://app.aiinsurance.io/api/v1/external/companies/{companyId}/folders?entityType=Exposure&entityId=EXPOSURE-ID" \
  -H "Authorization: YOUR-API-KEY"
# → {"folders": [{"id": "folder-uuid", "parentFolderId": null, "name": "Loss Runs"}]}

# Move a file into the folder
curl -X PATCH https://app.aiinsurance.io/api/v1/external/companies/{companyId}/files/{fileId} \
  -H "Authorization: YOUR-API-KEY" \
  -H "Content-Type: application/json" \
  -d '{"folderId": "folder-uuid"}'
# → {"id": "file-uuid"}

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:
# Share an Event's file to a Submission
curl -X POST https://app.aiinsurance.io/api/v1/external/companies/{companyId}/files/{fileId}/placements \
  -H "Authorization: YOUR-API-KEY" \
  -H "Content-Type: application/json" \
  -d '{"entityType": "Submission", "entityId": "SUBMISSION-ID"}'
# → {"placementId": "...", "fileId": "...", "entityType": "Submission", ...}

# Where does this file appear?
curl https://app.aiinsurance.io/api/v1/external/companies/{companyId}/files/{fileId}/placements \
  -H "Authorization: YOUR-API-KEY"
# → {"placements": [{"placementId": "...", "entityType": "Event", "entityDisplayName": "Warehouse Fire", ...}, ...]}

# File it under one of the Submission's folders with its own category
# (the Event's placement is untouched)
curl -X PATCH https://app.aiinsurance.io/api/v1/external/companies/{companyId}/files/{fileId}/placements/{placementId} \
  -H "Authorization: YOUR-API-KEY" \
  -H "Content-Type: application/json" \
  -d '{"folderId": "SUBMISSION-FOLDER-ID", "category": "Loss Runs"}'
# → {"placementId": "...", "fileId": "...", "entityType": "Submission", "folderId": "...", "category": "Loss Runs"}

# Remove it from the Submission only (the Event keeps it)
curl -X DELETE https://app.aiinsurance.io/api/v1/external/companies/{companyId}/files/{fileId}/placements/{placementId} \
  -H "Authorization: YOUR-API-KEY"
# → {"id": "...", "fileId": "...", "fileDeleted": false}
Sharing is same-company only, and a duplicate share to an owner the file is already placed on returns 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.