Skip to main content

REST Files

The REST file workflow uses the same stable file UUIDs, private quarantine, scanner and immutable message copies as agent filespaces. New upload/replacement requires API_REST_FILES_ENABLED=true, enabled direct-upload scanning, local default/quarantine storage and the updated web/scanner/Beat writers. It defaults off. Service credentials also require installation/Organization enablement and current agent grants. Validate scanner and worker behavior in your environment before enabling it. Remote/custom object storage upload is unavailable in this increment; retained reads can use the configured storage outside authority locks.

Upload and poll​

Use an agents:operate credential and a fresh durable replay key. Multipart fields are exactly one file, one explicit name, and optional parent_id for a live directory in the default filespace. Names must be NFC, 1–255 characters, with no separators, percent escapes, controls, dot components or surrounding whitespace. No URL fetching, base64 or resumable upload is accepted. A duplicate name fails; replace by UUID intentionally. The complete file ancestry is bounded to 128 nodes (at most 127 directories); REST and MCP reject new files beyond the readable depth before acceptance.

UPLOAD=$(curl --fail-with-body "$API_BASE/agents/$AGENT_ID/files/" \
-H "X-Api-Key: $API_KEY" -H "Idempotency-Key: $UPLOAD_KEY" \
-F 'name=brief.txt' -F 'file=@brief.txt;type=text/plain')
FILE_ID=$(jq -er '.file_id' <<<"$UPLOAD")
curl --fail-with-body "$API_BASE/agents/$AGENT_ID/files/$FILE_ID/" \
-H "X-Api-Key: $API_KEY"

202 proves local quarantine acceptance, not a clean verdict or worker readiness. Poll detail with bounded backoff until the intended generation is available. Responses contain file_id, name/parent/path, available, availability, scan_status, separate active_generation/pending_generation, active checksum/size and timestamps. Generation objects contain UUID, scan state, checksum and size. Scanner internals, threat names and storage locations are omitted.

AvailabilityMeaning
pendingNo readable active bytes; pending/scanning/retrying generation
availableActive content has readable scan provenance; download still verifies actual bytes
infectedNo readable active generation; malware verdict
failedScanner attempt ended unavailable
unavailableUnavailable or missing provenance; no inferred clean verdict
purgedOriginal retention removed bytes; replacement is refused

The scan state remains unavailable for failed/unavailable outcomes; the availability label distinguishes an attempted terminal failure from missing/unattempted provenance. An older active clean generation can keep available=true while the pending generation is infected, failed or scanning. Select the desired generation explicitly. Policy-exempt legacy content may report not_required; this is never a clean scanner verdict.

List and replace​

GET /agents/{agent_id}/files/?limit=40 lists files in the default filespace, including nested files. limit is 1–100; follow next_cursor with the same principal, agent, owner, filespace and page size. Signed cursors expire after 24 hours. Each page rechecks authority. Ordering is creation time/UUID; concurrent changes mean the inventory is not a snapshot. Application-origin restrictions may close a broad listing rather than expose foreign request content. Original upload ownership is filtered before pagination.

PUT /agents/{agent_id}/files/{file_id}/content/ accepts exactly one multipart file, requires agents:operate and a new Idempotency-Key, and returns 202. Identity/name/parent remain stable. The most recent accepted pending UUID supersedes the earlier pending candidate. Exact clean promotion replaces the active generation; an old verdict cannot overwrite the new candidate. Clean old content remains available until promotion under existing file policy. Upload alone does not trigger agent work.

Upload sizes use deployment MAX_FILE_SIZE, 10 MiB by default. If configured unbounded, this adapter still caps at 10 MiB. Streamed bytes, not multipart metadata or declared client size, determine acceptance. A request parser/proxy may impose an earlier deployment bound.

Attach and download​

Submit the clean file UUID in application-request attachment_file_ids:

{"body":"Read this document.","attachment_file_ids":["30f34d22-243c-471c-9242-a4fc0d1b8a3"]}

Application requests stage immutable exact bytes and rechecks generation/scan/authority before accepting the request. Later replacement never rewrites its retained message copy. Independent principals do not receive foreign application attachment content by knowing an ID. Shared model context/general filespace is not a confidentiality compartment; isolate independent workloads with separate agents/workspaces.

Download with agents:read using GET /agents/{agent_id}/files/{file_id}/download/?generation_id={active_UUID}. Missing, pending, infected, superseded, corrupted or unavailable bytes fail closed (409 file_unavailable). Generation ID is required (400 if malformed/missing). GET /agents/{agent_id}/attachments/{attachment_id}/download/ returns only an authorized retained immutable copy, with exact request/message lineage; this route does not follow mutable working-node fallback references. It uses the same byte cap.

Downloads snapshot bounded bytes outside authority locks, verify SHA-256/size and reacquire current authority/generation before admitting the response. Responses are streamed from that private snapshot as application/octet-stream, with attachment disposition, nosniff, and private, no-store. There are no permanent public or short-lived signed storage URLs. Revocation cannot recall an HTTP response already admitted or bytes already received.

Retry, retention and deployment​

Retry identical multipart bytes/name/parent/content-type on the identical route with the same key after an ambiguous response. Safe-retry encrypted receipts retain the original acceptance for 24 hours by default; changed semantics conflict (409). Member keys have credential-specific namespaces; service rotation retains the stable identity namespace. Replay rechecks current scopes/grants, live ownership, file authority and retention. Turning the upload gate off preserves authorized reads/replay. After expiry a key can accept new work; retain file IDs and reconcile before retrying expired keys.

Original effective owner is frozen on API uploads. Transfer does not widen REST/MCP read/reuse/replay authority. Original Organization file-retention clocks still apply; there is no new retention guarantee. Staging keys are persisted before private local writes so rollback/storage failure remains discoverable. Detached staging/superseded blobs expire into existing bounded quarantine maintenance after 24 hours (failed admission cleanup is attempted immediately). Existing scanner retry-window/quarantine retention governs terminal unavailable content. A five-minute existing Beat job republishes up to 100 eligible pending/stale scan intents after lost queue publication; it does not infer a verdict. Actual recovery requires a working malware queue/worker and Beat.

Apply 0585_rest_file_upload_owner after the earlier API-surface migrations and upgrade/drain web/scanner/retention/import/read writers before enabling admission. Keep source/readers, receipt authority and maintenance throughout rollback retention. New intake rejects an emergency-halted agent; retained reads and accepted scan promotion keep existing file-content semantics. A purge cannot be undone by delayed promotion. Shared request admission applies when enabled; file upload creates no queued-input claim; this increment makes no new throughput/load claim. Move/delete, remote staging and large resumable upload are not supported.

New REST generations and application-request immutable/imported application copies use api-private/ keys in the existing private malware storage alias; clean promotion never places those new bytes beneath publicly served default media. Legacy active keys continue to resolve their original store until replacement, and old attachment paths are not backfilled. Migration 0585 carries the generation-aware file fields; rollback must retain their storage resolution. Private clean content shares the private storage volume but is distinct from scan quarantine keys, so quarantine pruning does not delete active clean generations. Configure the private root outside all publicly served media/static roots.

application-request acceptance and current request reads expose retained immutable input IDs in input_attachments. Download those copies using the attachment route after working-file replacement; response/reply copies continue to use the existing attachments list. Purged content exposes no copy inventory. An older stored replay may omit this additive metadata; follow current request detail.

Private scanner promotion records its target before writing and names new objects with the exact scan UUID. The existing five-minute maintenance task checks at most 100 terminal or expired detached public private-generation records, locks scan then node, removes unreferenced local promotion remnants and preserves active keys/immutable copy namespaces. Failed cleanup retries remain durable. Remote backend transitions need separate original-volume cleanup validation.

Attachments still snapshot the current clean working generation at application-request admission; no client generation precondition is added. Reconcile input_attachments to identify the exact immutable admitted copy.