Confluye
Features

Workspace Files

Immutable scanned blobs with a three-step upload, byte-range download, and deferred deletion.

Workspace Files are workspace-owned blobs. Metadata is created first (pending), bytes are written once, completion verifies size and SHA-256, then a scanner must mark the object ready before download or attachment. Content is not rewritten in place.

The browser uses /api/files with a signed-in session and workspaceId. API clients use /api/v1/files with a workspace API key. Both path prefixes share the same handlers.

In Command Center, open Files in the composer's workspace context strip to upload and select files. The panel expands below the context strip, above the message field, and includes Artifacts and Activity. Select Files again to close it; closing the panel keeps the current attachments selected.

Choose Load more files to continue beyond the first page. Refresh files reloads the pages you have already opened, preserving selections that are still available. If loading fails, the panel shows the error and you can refresh or retry loading more files.

Upload lifecycle

statusMeaning
pendingMetadata exists. Only this state accepts PUT .../content.
pending_scanBytes persisted and checksums match. A scan lease is required before ready.
readyScanner returned clean. Download, Command attachments, and presentation inputs require this.
quarantinedScanner returned infected. Content cannot be opened.
deletion_requestedSoft-delete. Blob remains until GC. New references are rejected.
deletedGC removed the blob and marked the row.

Create (POST) with name, mimeType, sizeBytes (0–100 MiB), optional sha256 (64 hex). The response includes uploadUrl and completeUrl derived from the request path (session URLs keep ?workspaceId=).

Then PUT the raw body to .../{id}/content. Content-Length above 100 MiB is 413 before the body is stored. Missing body: 400. Only pending files accept content.

Then POST .../{id}/complete with optional sizeBytes / sha256. The handler completes the upload and, while status is pending_scan, runs scanFile in the same request. HTTP status is 202 with the serialized file (often ready after a clean scan). Replay of the same Idempotency-Key returns the stored 202 without a second scan.

The UI client polls while any listed file is pending_scan, for up to two minutes per polling session. If scanning is still pending, it shows a message; choose Refresh files to check again and restart polling. This limit stops browser polling, not the server scanner. Only ready files are selectable as chat attachments.

Authorization

Files metadata and bytes require workspace.restricted.read; existing stricter session controls still apply. Organization visibility alone does not grant this access. Each request checks current authority.

An image column in Tables retains a ready raster file by record ID. The file cannot be requested for deletion while any record uses it. Clear those cells first. Raster images, including server-validated images uploaded as application/octet-stream, also stay available while an active workflow, saved run or pinned output retains them. Generic binary files are not classified as images. Clearing the last table cell does not release those owners; remove the owning run or pin before requesting deletion. Expired temporary references do not block deletion. GET .../{id}/content?preview=true serves verified PNG, JPEG, WebP or GIF inline through the private authenticated proxy; the default remains an attachment download. Responses are private and not cached.

SurfacePrincipalWorkspace id
/api/filesBrowser sessionQuery workspaceId, else the session workspace
/api/v1/filesWorkspace API key (Bearer)Always the key's workspace

Session access is membership-checked. A workspaceId the user does not belong to is rejected. v1 missing/invalid bearer: 401 authentication_required. Personal or copilot key: 403 authorization_failed (A workspace API key is required.). A file id in another workspace is 404 (Workspace file {id} was not found.).

Preview capabilities: files.list, files.get, files.read (bounded chunk, default 32 KiB, max 64 KiB), files.delete (confirmation and idempotency required). files.delete also asserts write readiness.

Storage and scanning

Default blob store is S3 (WORKSPACE_FILE_BLOB_STORE unset or s3). postgres is allowed only outside NODE_ENV=production. Production requires the private S3 store.

S3 profiles: minio (local), railway (non-local Confluye), aws (legacy, rejected on Confluye deployments). Minio/railway require endpoint and credentials. Railway rejects WORKSPACE_FILES_S3_SSE. Object key is the file id (optional WORKSPACE_FILES_S3_PREFIX).

Scanner: WORKSPACE_FILE_SCANNER clamav (default in production) or noop (non-production only). ClamAV uses CLAMAV_HOST (default 127.0.0.1), CLAMAV_PORT (3310), CLAMAV_TIMEOUT_MS (30s). Scan lease default is 5 minutes; a stale running scan is marked Scanner lease expired. before a new claim. Infected → quarantined. Scanner exception → scan row error and the HTTP complete fails.

encryptionKeyId is stored as null on create. Serialized file objects omit storage keys.

Download

GET .../{id}/content returns the raw stream (not the JSON envelope): 200 or 206 with accept-ranges: bytes, content-type from metadata, content-disposition attachment (quotes and newlines in the name become _), etag "sha256-{hex}". Range: bytes={start}-{end} only (one explicit range). An end beyond the file length is limited to the last byte; an out-of-bounds start returns 416. Non-ready files: 423 conflict.

Deletion and GC

DELETE .../{id} is idempotent at the service layer: already deletion_requested or deleted returns the current row. HTTP 202. Blobs are not removed in that request.

GC (runWorkspaceFileGc): orphans older than 7 days with no live reference (lease null or unexpired) in pending / pending_scan / ready / quarantined are marked deletion_requested. Rows already deletion_requested for 30 days have the blob deleted then status deleted. runDueWorkspaceFileGc runs only when the deployment environment is production, at most once per UTC day after a complete batch (default batch 100).

Active references (run, message, document, skill, mail, artifact, research_source) block GC mark/remove. Command job attachments use resourceType: "run" with a default 24h lease.

Workflow file references

Form Trigger, Webhook and manual Start inputs store new bytes in Workspace Files. Downstream items carry kind: "workspace-file", fileId, filename, MIME type, extension, byte size and SHA-256 in binary. They contain no storage keys or permanent download URLs. Pending and quarantined files cannot reach a consumer. Extraction and HTTP request bodies resolve references with the execution's workspace authorization; the Binary inspector downloads through the signed-in session.

Accepted submissions, workflow runs and pinned outputs retain their files without a fixed 24-hour lease. References transfer before submission ownership is released. Removing the owner releases its references; existing deletion and GC retention still apply. Removing an editor selection only clears that selection. Manual simulations acquire temporary ownership before reading, then atomically transfer it to the saved run. A deleted or inaccessible referenced file causes an explicit execution error. CSV extraction rejects malformed quotes and inconsistent column counts.

See the workflow file walkthrough.

Idempotency and retries

Mutations require header Idempotency-Key (withApiIdempotency, required: true). Missing or invalid key: 400 idempotency_key_invalid. Same key, different body (including file id on complete/content/delete): 409 idempotency_replay_mismatch. Successful replay sets Idempotency-Replayed: true and does not re-execute.

Blob put accepts the same key for content uploads. Complete of an already pending_scan or ready file is a no-op unless completion metadata conflicts (409 size/checksum mismatch).

Degradation

ConditionBehavior
Preview/production primary S3 or ClamAV probe fails503; retryable true for files_primary_store_unreachable / files_scanner_unreachable
Config missing (S3 parse fail or scanner not ClamAV+host)Availability blocked (or setup_required when local)
Production backup not ready or restore not approvedCapability blocked (files_backup_store_not_configured / files_restore_test_required)
Infected scanquarantined; open content 423
Active scan leaseCannot scan a file in … / already has an active scan (409)
Content before persistCannot complete … Upload content has not been persisted. (409)

Next steps