File Uploads

Upload files with the presigned GET /uploads flow, confirm them, and list uploaded files.


Files don’t go through the JSON /v1 endpoints - binary uploads use a presigned flow instead. You ask Blue for a one-time upload URL, PUT the file’s bytes straight to that URL, then confirm the upload. This keeps large binaries off the API and lets the upload go directly to object storage.

The flow is three steps:

  1. GET /uploads - authenticate with your Bearer token; Blue returns a presigned target.
  2. PUT to the returned url - send the raw file bytes with the returned Content-Type.
  3. PUT /uploads/{uid}/confirm - finalize the file so it appears in the workspace.
Not a /v1 endpoint

/uploads sits outside the /v1 JSON surface - it returns a presigned target rather than an operation result, and the binary PUT in step 2 goes to the storage url Blue hands back, not to a Blue API path at all.

Step 1: request a presigned upload

GET /uploads with the same Bearer token you use everywhere else, plus an x-bloo-project-id header naming the workspace the file belongs to (uploads are scoped to a workspace, so this header is required in addition to x-bloo-company-id). The file name and size go in the query string; Blue derives the content type from the file name’s extension.

curl "https://rest.blue.app/uploads?filename=report.pdf&size=248913" \
  -H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN" \
  -H "x-bloo-company-id: YOUR_COMPANY_ID" \
  -H "x-bloo-project-id: YOUR_WORKSPACE_ID"
Query paramRequiredDescription
filenameYesThe file name, with extension - Blue derives the content type from it.
sizeNoFile size in bytes. Validated against the workspace’s upload limit.
keyNoA custom file ID. Omit it and Blue generates one (returned as X-Key).

The response carries the upload target:

{
  "url": "https://STORAGE_HOST/PATH?X-Amz-Signature=...",
  "method": "PUT",
  "headers": {
    "Content-Type": "application/pdf"
  },
  "fields": {
    "Content-Type": "application/pdf",
    "Key": "acme/launch/abc123/report.pdf",
    "X-Key": "abc123"
  }
}
FieldDescription
urlThe presigned URL to PUT the file bytes to. Single-use and time-limited. Use it verbatim.
methodThe HTTP method for the upload - always PUT.
headersHeaders you must send on the upload request - send the Content-Type exactly as returned (the signature is bound to it).
fieldsStorage metadata: Content-Type, the storage Key, and X-Key - the file’s uid, which you pass to the confirm step.

Step 2: upload the file

Send the raw bytes to url with the Content-Type from headers. Do not send your Bearer token here - the URL is already signed, and the request goes to storage, not to Blue’s API:

curl "https://STORAGE_HOST/PATH?X-Amz-Signature=..." \
  -X PUT \
  -H "Content-Type: application/pdf" \
  --data-binary @report.pdf

A 2xx response means the bytes landed. The signed URL is single-use and expires, so request a fresh one for each file.

Step 3: confirm the upload

Finalize the file with its uid - the X-Key value from step 1 - so it’s marked complete and shows up in the workspace:

curl "https://rest.blue.app/uploads/abc123/confirm" \
  -X PUT \
  -H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"
Confirmation is also reconciled automatically

Blue schedules a background check just after the presign window closes that reconciles the file row against storage - so an upload eventually resolves even if you skip the confirm call. Calling confirm yourself just makes the file available immediately.

List uploaded files

Use the list-files operation to see the files in a workspace. It’s a standard RPC call and pages like any other list operation:

curl https://rest.blue.app/v1/list-files \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN" \
  -H "x-bloo-company-id: YOUR_COMPANY_ID" \
  -d '{
    "workspaceId": "WORKSPACE_ID",
    "skip": 0,
    "limit": 50
  }'

The response is an items array of file metadata plus a pageInfo object (see Pagination):

{
  "items": [
    {
      "id": "clm4n8qwx000008l0g4oxdqn7",
      "uid": "abc123",
      "name": "report.pdf",
      "size": 248913,
      "type": "application/pdf",
      "extension": "pdf",
      "status": "CONFIRMED",
      "shared": false,
      "createdAt": "2026-06-21T12:34:56.789Z",
      "updatedAt": "2026-06-21T12:34:56.789Z",
      "folder": null,
      "todo": null,
      "user": { "id": "clm...", "fullName": "Ada Lovelace", "email": "ada@example.com" }
    }
  ],
  "pageInfo": {
    "totalItems": 1,
    "hasNextPage": false
  }
}