Uploads

Stage files for a turn to work on and get the references to attach.

Sometimes the answer isn't in the warehouse — it's in a CSV the user just dropped into your UI. Uploads is how that file reaches the agent.

It is a two-step flow, and the second step is the one people miss: uploading a file does not hand it to the agent. Upload returns references; you attach those references to the next turn.

graph LR
  A["Your UI<br/>user picks a file"] -->|"multipart/form-data"| B["POST /projects/{projectId}/uploads"]
  B -->|"returns filePath references"| C["Your backend"]
  C -->|"files: [ ... ] on the request"| D["POST /stream/agent_ask"]
  D --> E["Agent reads the file<br/>in the thread workspace"]

Endpoints Overview

EndpointWhat it does
POST /v2/projects/{projectId}/uploadsStage one or more files and return their references.

Basic Usage

Step 1 — stage the bytes

Send multipart/form-data with one or more file parts:

curl --request POST \
  --url https://cloud.getwren.ai/api/v2/projects/1/uploads \
  --header 'Authorization: Bearer <API_KEY>' \
  --form '[email protected]' \
  --form '[email protected]'
{
  "uploadSessionId": "6f1c8a2e-77b3-4c1d-9a55-1e0b7c3d2f10",
  "files": [
    {
      "fileName": "q3-targets.csv",
      "filePath": "staging/1/6f1c8a2e-77b3-4c1d-9a55-1e0b7c3d2f10/q3-targets.csv",
      "fileSize": 20481,
      "fileType": "text/csv"
    },
    {
      "fileName": "notes.md",
      "filePath": "staging/1/6f1c8a2e-77b3-4c1d-9a55-1e0b7c3d2f10/notes.md",
      "fileSize": 812,
      "fileType": "text/markdown"
    }
  ]
}

Step 2 — attach them to a turn

Pass the entries back as files, with filePath verbatim:

{
  "projectId": 1,
  "question": "Compare the Q3 targets in this CSV against actual revenue.",
  "files": [
    {
      "fileName": "q3-targets.csv",
      "filePath": "staging/1/6f1c8a2e-77b3-4c1d-9a55-1e0b7c3d2f10/q3-targets.csv",
      "fileType": "text/csv"
    }
  ]
}

The turn moves the file into the thread's workspace, and the agent can open it:

event: tool_call
data: {"block_id":2,"id":"toolu_01C","name":"Read","input":{"file_path":"q3-targets.csv"}}

Send a turn's attachments in one request

Every file in one upload request shares a single uploadSessionId. A turn moves one upload session into the thread — so a files array spanning two sessions is rejected with 400 rather than silently dropping the extras.

// ✅ Correct — one request, one session, all files reach the agent.
const { files } = await uploadFiles(projectId, [csv, notes]);
await streamAgentAsk({ projectId, question, files });

// ❌ Wrong — two sessions; the second file would be dropped, so this is rejected.
const a = await uploadFiles(projectId, [csv]);
const b = await uploadFiles(projectId, [notes]);
await streamAgentAsk({ projectId, question, files: [...a.files, ...b.files] });

Limits

Extensions.csv, .doc, .docx, .pdf, .xls, .xlsx, .sql, .yaml, .yml, .md, .json, .txt, .zip
Files per request10
Size per fileSet by AGENT_FILE_UPLOAD_MAX_SIZE_MB, default 10 MB. Exceeding it returns 413.

Lifecycle

Uploaded bytes are staging only. The turn moves them into the thread workspace, after which the agent copies what it needs — so they survive a sandbox restart and remain available to later turns in the same thread.

An upload never referenced by a turn stays in staging. How long it is kept before cleanup is set by your deployment's staging lifecycle policy.

Uploads are for giving the agent a file to work on now. To manage the project's durable knowledge and modeling files, use the Files API instead.

🔗

Learn more