kind, tag or a word from the title. Files are either session-scoped (temporary, tied to a session) or published/persistent (retained at the organization level).
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.
string
Derived from
content_type: image, video, audio, document, model (3D) or other.string | null
A human title, editable.
string | null
Free text, editable and searchable.
string[]
Labels to filter by; up to 32, each up to 64 characters.
object | null
Where the bytes came from:
{ type, job_id, url }. type is upload, url_import, image_generation, video_generation, video_clipping, screenshot, speech_generation, mesh_generation (a generated 3D model) or model_build (a file build-3d published); job_id names the med_/clj_ job that produced it, url the address an import fetched. A generated picture or clip also carries model and prompt: what it was made with. An edited clip also carries from_file_id: the library clip it was made from, when it was made from one. null for files written before provenance was recorded.string | null
The agent whose session made the file.
null for an upload, an import, or a render asked for over the API.integer | null
Pixel width, read from the bytes of a PNG, JPEG, GIF, WebP or MP4 at write time.
integer | null
Pixel height, likewise.
number | null
Length of an audio or video file: as its producer reported it (for generated speech), or read from an MP4’s own header at write time.
null otherwise.string | null
Reserved for a preview image; always
null today.Session-scoped vs published
Anything an agent should keep beyond a run is a published file. Scratch work stays session-scoped. Promotion emits no event; an import emits
file.imported.Getting bytes out of a session
Files travel outward by default: 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[]. Inward, the agent asks: the built-in fetch_file tool takes up to eight fil_ ids the organization holds and writes those files into the session’s sandbox, which is how an agent post-processes an asset the platform rendered for it. The sandbox file tools themselves still address the sandbox filesystem and not file ids — there is no read_file — and find_files still returns ids and metadata, never bytes. To put an input in front of an agent, name its fil_ id in the session’s first message and let it fetch the bytes, or put the content in the message itself.
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, or give it a title and tags and find it by those.
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.
string
A human title for the library.
string
A longer description, searchable.
string
Comma-separated labels, e.g.
launch,web.kind is derived from the content type, and an image’s width/height are read from its bytes. source is { "type": "upload" }.
Upload up to 60 MiB with a link
POST /v1/files/uploads → 201 Created. Scope files:write. Mints a one-shot link; the bytes go to it in a second request that needs no key, so a browser can send them straight from the page.
string
required
The file name.
string
required
A MIME type, e.g.
video/mp4. The PUT must send the same.integer
required
The exact size, at most 60 MiB.
Response
PUT /v1/files/uploads/{token} → 201 Created, with the file object. No key: the token is the proof. Send the bytes as the body within 15 minutes, with the declared Content-Type and a Content-Length of exactly size_bytes; anything else is validation_failed. The link works once. A used, expired or altered link is forbidden. The file is published, with source.type: "upload". The route answers CORS, so any page may call it.
file.created.
Import a file from a URL
POST /v1/files/imports → 201 Created. Scope files:write. Requires an Idempotency-Key header: the same key replays the same file instead of fetching twice.
The API fetches the URL server-side and stores the bytes as a published file with source.type: "url_import" and source.url set to the address you gave. The stored content_type is what the bytes say (PNG, JPEG, GIF, WebP, MP4, WebM, MP3, WAV, Ogg, PDF are recognised by signature), falling back to what the server declared.
string
required
A public
http(s) URL.string
A human title for the library.
string
A longer description, searchable.
string[]
Labels to filter by.
Response
http and https; no credentials in the URL; the host must be public — loopback, private-network, link-local and cloud-metadata addresses are refused, and every redirect is checked the same way before it is followed (three at most). The body is capped at 25 MiB, the same as an upload; a larger declared or actual body fails the import. All of these, and an upstream error status, are validation_failed (400) on url.
The import is audit-logged and emits a file.imported { file_id, url } event to webhooks subscribed to it.
Update a file’s metadata
PATCH /v1/files/{id} → 200 OK with the updated file. Scope files:write. Only the library metadata changes; the bytes, name, scope and provenance do not.
string | null
Set or clear (
null) the title.string | null
Set or clear the description.
string[]
Replace the tags;
[] clears them.validation_failed (400).
Retrieve a file
GET /v1/files/{id} → 200 OK with file metadata. Append ?download=true to receive the raw bytes instead of JSON.
The bytes carry an ETag (the file’s sha256) and honour Range, If-Range and If-None-Match; a copy you already hold is answered 304 Not Modified. A generated image or video is sent Cache-Control: private, no-cache, every other file private, max-age=31536000, immutable.
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 generated picture’s or clip’s
link also carries in=media, which is signed too. 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, newest first. See Pagination. Filters combine:
Working pictures a skill build cuts from its source videos are left out of every list except an
ids one. Each is still read by its id.
Everything an agent’s pictures and clips need is one call: ?kind=image,video&agent_id=agt_….
Response
Folders
A folder is a name on a file. A file is in one folder or none, and folders do not nest. A name is 1–64 characters with no/. There is no folder object to create: a folder exists while a file is in it.
- Case. A name that matches an existing folder in another case takes that folder’s spelling.
launchfiles intoLaunch. - Default. A file written with no folder lands in the folder of the newest filed file in its session, else in none. So an agent’s next render lands beside its last.
- Once. The folder is set when a file is written. Writing the same file again never changes it; only a move does.
Move files
POST /v1/files/move → 200 OK. Scope files:write. Name ids or from, not both.
string[]
Up to 100
fil_ ids to move. An id the organization does not hold is skipped.string
Move every file in this folder: a rename. If
folder is already in use, the two merge.string | null
required
The folder to move to.
null takes the files out of any folder; with from, that empties the folder.Response
folder is the name the files now carry, in the spelling that won. Only the name on each file changes. No file is deleted and no bytes move.
List folders
GET /v1/files/folders → 200 OK. Scope files:read. Every folder, newest first, in one page.
Response
cover_file_ids holds up to four of the newest pictures and clips in the folder. agent_id is set when one agent made every file in it, else null. All of it is worked out when you read it. To read a folder’s files, list with ?folder=.
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); forbidden (403) without files:write.
Delete a file
DELETE /v1/files/{id} → 200 OK. Removes the object.
Response
Delete many files
POST /v1/files/delete → 200 OK. Scope files:write. Deletes up to 100 files in one request, each as DELETE /v1/files/{id} would. An id the organization does not hold is skipped. Each delete is filed as file.deleted in the audit log.
string[]
required
1–100
fil_ ids to delete.Response
file_ids lists the files that were deleted.