Actana

Uploads

How files get into the workspace — drag-drop, API, and sandbox-generated assets.

There are three paths a file takes to land in the workspace file store. Each writes a row into workspace_files with a different context.

Drag-drop and the upload picker

The Files module accepts drag-drop into the gallery and a "Browse" picker. Both go through the same upload route:

  • The client requests a presigned upload URL from /api/files/upload.
  • The browser PUTs the file directly to object storage.
  • On success, the client confirms the upload back to the API, which inserts a workspace_files row with context = 'workspace'.

The collision rule kicks in here: re-uploading a file with an existing originalName (in the workspace context) fails until the prior row is soft-deleted or renamed.

The same flow drives chat attachments — the Assistant composer's drag-drop calls a chat-scoped variant that writes the row with context = 'chat' and additionally inserts into assistantChatBagFiles so the conversation can project the bag into the Sandbox.

The Files API

For programmatic uploads, the same presigned-URL flow is exposed through the REST API:

# 1. Request a presigned PUT URL.
curl -X POST \
  "https://your-actana.example.com/api/files/upload" \
  -H "Authorization: Bearer ${API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "originalName": "report.pdf",
    "contentType": "application/pdf",
    "size": 102934,
    "context": "workspace"
  }'

# Response: { key, uploadUrl, headers }

# 2. PUT the bytes directly to object storage with the returned URL.
curl -X PUT "${uploadUrl}" \
  -H "${headers}" \
  --data-binary @report.pdf

# 3. Confirm — server inserts the workspace_files row.
curl -X POST \
  "https://your-actana.example.com/api/files/upload/confirm" \
  -H "Authorization: Bearer ${API_KEY}" \
  -d '{ "key": "${key}" }'

This is the right path for batch imports, sync jobs, or any system that produces files server-side.

Sandbox-generated assets

Files produced inside a Sandbox run land in the file store via writeback rather than an upload. The sandbox holds an upload OTP for the run; at the end of the run it POSTs assets to:

  • ${agentUrl}/results — for Assistant chat sessions. The asset row goes into assistantChatBagFiles with source: 'sandbox', scoped to the conversation.
  • ${agentUrl}/assets/writeback — for Agents block workflow runs. Anything in the sandbox's /root/workspace/assets/ directory is collected and surfaced on the block's files output. Pipe that output into a Save File block to persist the assets into the workspace Files module (context = 'workspace').

Without a Save File downstream, the writeback assets stay in the chat bag or workflow bag — they are not durable workspace files.

The upload OTP has a longer TTL (60 minutes) than the boot OTPs (10 minutes) so the sandbox can still write back assets after a long-running model call.

File size + type policy

There is no built-in MIME-type allow-list — the upload route accepts any contentType. Per-deployment size caps live on the storage backend (object-storage policy + reverse proxy client_max_body_size). For very large files, prefer the presigned-URL flow over routing bytes through the application.

Source

  • apps/actana/app/api/files/upload/route.ts — presigned URL + confirm
  • apps/actana/app/api/files/serve/[key]/route.ts — authenticated read
  • apps/actana/lib/uploads/ — utilities + storage adapters
  • apps/actana/lib/assistant-sandbox/file-bag/ — chat-bag projection

On this page