Skip to main content
Files is Vetta’s 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 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 or speech_generation; job_id names the med_/clj_ job that produced it, url the address an import fetched. null for files written before provenance was recorded.
integer | null
Pixel width, read from the bytes of a PNG, JPEG, GIF or WebP at write time.
integer | null
Pixel height, likewise.
number | null
Length of an audio or video file, as its producer reported it. Set for generated speech; null for every other writer — the bytes are not probed.
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.
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" }.

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
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.
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.
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 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:
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
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