The file object
string
Unique id (e.g.
fil_01H...).string
Always
file.string
File name.
string
MIME type.
integer
Size in bytes.
string
session (temporary) or published (persistent).string | null
Owning session, for session-scoped files.
string
Content hash.
string
Upload timestamp.
Session-scoped vs published
Anything an agent should keep beyond a run is promoted to a published file. Scratch work stays session-scoped. Promotion emits no event — the event stream carries session activity only, so read the file back if you need to confirm it.
Getting bytes out of a session
Files travel outward: an agent writes an artifact with the built-inpublish_file tool and you read it back through this API. There is no create-time mount — a session takes no files[] — and there is no read_file tool; the built-in toolset is bash, read, write, edit, ls, find, browser, read_skill, publish_file, web_search, web_fetch, generate_image and generate_video, and the file tools address the sandbox filesystem, not file ids. To put an input in front of an agent today, put it in the session’s first message or have the agent fetch it.
Each publish_file call mints a new file id, including for a name that already exists: nothing is superseded and both remain listed. Pick the one you want by created_at.
Upload a file
POST /v1/files → 201 Created. Accepts a multipart upload. The file part carries the bytes; other parts set metadata.
file
required
The file contents (multipart).
string
session or published. Defaults to published.string
Required when
scope is session.string
Override the file name.
Retrieve a file
GET /v1/files/{id} → 200 OK with file metadata. Append ?download=true to receive the raw bytes instead of JSON.
Response
A link that needs no key
POST /v1/files/{id}/url → 201 Created. Scope files:read.
A file id is not something a person can watch. ?download=true above wants an authorization
header, and an <img> or <video> tag sends none — so this mints an address that carries its own
proof instead, good for seven days.
Response
GET /v1/files/{id}/{name}?org=&expires=&sig= — that URL, with no credential at all — returns the bytes, inline, with the content type they
were stored under. The filename is in the path so the address ends in a real extension, which is what
lets a browser tag and a fetch-once publishing provider recognise it before opening it.
The signature covers every other field in the URL, so there is nothing in it you can edit: change the
expiry, the file, the organization or the name and it answers 403. A forged link and an expired one
answer the same thing. Deleting the file revokes every link ever minted for it.
List files
GET /v1/files → 200 OK, cursor-paginated. Filter with ?scope= and ?session_id=. See Pagination.
Response
Publish a file
POST /v1/files/{id}/publish → 200 OK. Scope files:write.
The promotion boundary: a session-scoped file becomes a durable, org-level published one. Use it when work that started as scratch turns out to be the deliverable — it saves re-uploading the bytes.
Idempotent. Publishing an already-published file returns it unchanged rather than erroring, so a retry is safe. No request body.
Response
not_found (404); insufficient_scope (403) without files:write.
Delete a file
DELETE /v1/files/{id} → 200 OK. Removes the object.
Response