Skip to main content
Files is Naive Managed Agents’ object storage and its media asset library. Use it to upload inputs an agent can read, to hold artifacts an agent publishes, and to find them again by 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.
string | null
The folder the file is in, or null.
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-in publish_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.
string
Put the file in this folder.
The body is capped at 25 MiB. The stored kind is derived from the content type, and an image’s width/height are read from its bytes. source is { "type": "upload" }.
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.
string
Put the file in this folder when it lands.
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.
The mint is audit-logged as 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.
string
Put the file in this folder.
Response
Limits. Only 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.
string | null
Move the file to this folder; null takes it out.
Any other field in the body is 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
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. launch files into Launch.
  • 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.
Grouping by agent, chat, kind or date is a filter on the list, never a folder.

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
The file now outlives its session and is billed as org-level storage. No event is emitted. Errors — 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.