Upload a file once and reference it by id in chat completions and other requests.
The Files API lets you upload a file once and reference it by id in any number of requests, instead of re-encoding the same bytes into every message. Upload images, audio, PDFs, office documents, or plain text, then pass the returned id as a file content part in chat completions. Uploads, storage and downloads are free — you are only billed for the model requests that use the files.
Files is also where everything your key sends and gets back is kept. A picture, a clip, a song or a spoken reply a model makes lands here under its own id, and so does a file you send inline with a request. A stored file never expires: it stays until you delete it.
| Method | Path | Description |
|---|---|---|
| POST | /v1/files | Upload a file |
| GET | /v1/files | List your files |
| GET | /v1/files/{id} | Retrieve a file's metadata |
| DELETE | /v1/files/{id} | Delete a file |
| GET | /v1/files/{id}/content | Download the raw bytes |
Authenticate every request with your key: Authorization: Bearer $OPENDUNES_API_KEY.
Send a multipart form with the bytes in a field named file.
curl https://opendunes.com/v1/files \
-H "Authorization: Bearer $OPENDUNES_API_KEY" \
-F "file=@photo.png"A successful upload returns 201 with the file object. The id is what you reference in later requests; created_at is an ISO 8601 time in UTC.
{
"id": "0197f2a0-5c1e-7f3a-9b2d-3e4f5a6b7c8d",
"type": "file",
"filename": "photo.png",
"mime_type": "image/png",
"size_bytes": 482716,
"created_at": "2026-09-24T08:00:00Z",
"downloadable": true
}downloadable is false for a file a model has just made whose copy is still on its way into storage — a few seconds, usually. It is always true for an upload.
The file's type is decided from its actual bytes, never from the filename or the claimed content type — a .png that is really an archive is rejected as unsupported_file_type.
curl "https://opendunes.com/v1/files?limit=20" \
-H "Authorization: Bearer $OPENDUNES_API_KEY"| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Page size, 1–1000 (default: 20) |
cursor | string | No | The cursor the previous page returned — fetches the next page |
after | string | No | A file id (UUID) — returns the files created after it; non-UUID values are rejected with a validation error |
Files come back oldest first:
{
"data": [
{
"id": "0197f2a0-5c1e-7f3a-9b2d-3e4f5a6b7c8d",
"type": "file",
"filename": "photo.png",
"mime_type": "image/png",
"size_bytes": 482716,
"created_at": "2026-09-24T08:00:00Z",
"downloadable": true
}
],
"has_more": false,
"first_id": "0197f2a0-5c1e-7f3a-9b2d-3e4f5a6b7c8d",
"last_id": "0197f2a0-5c1e-7f3a-9b2d-3e4f5a6b7c8d",
"cursor": null
}When has_more is true, pass cursor back to fetch the next page (or last_id as after). A cursor this list did not give returns 400.
The list holds your API files only. Files from your chat conversations are not in it; they are on the Storage page of your dashboard, beside these.
GET /v1/files/{id} returns the same file object as the upload response. An unknown or someone else's id returns 404, with the message File not found. in the standard error envelope.
curl -X DELETE https://opendunes.com/v1/files/0197f2a0-5c1e-7f3a-9b2d-3e4f5a6b7c8d \
-H "Authorization: Bearer $OPENDUNES_API_KEY"{
"id": "0197f2a0-5c1e-7f3a-9b2d-3e4f5a6b7c8d",
"type": "file_deleted"
}Deleting a file does not rewrite replies you already received — but any future request that references the id fails.
GET /v1/files/{id}/content streams the raw bytes back as an attachment with the file's stored content type. A file whose copy is not stored yet (downloadable: false) returns 409; try again shortly.
curl -OJ https://opendunes.com/v1/files/0197f2a0-5c1e-7f3a-9b2d-3e4f5a6b7c8d/content \
-H "Authorization: Bearer $OPENDUNES_API_KEY"Reference an uploaded file anywhere a message accepts content parts by sending a file part with the file_id:
curl https://opendunes.com/v1/chat/completions \
-H "Authorization: Bearer $OPENDUNES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemini-2.5-flash",
"messages": [{
"role": "user",
"content": [
{ "type": "text", "text": "Summarize this document." },
{ "type": "file", "file": { "file_id": "0197f2a0-5c1e-7f3a-9b2d-3e4f5a6b7c8d" } }
]
}]
}'The reference is expanded server-side into the right inline shape for the file's type before the request is forwarded, so the model you pick still needs the matching capability — vision for images, audio input for audio, file/PDF input for PDFs and text. A mismatch is not caught before forwarding: the provider rejects the request, and the rejection surfaces through the standard error envelope with the provider's message (see Errors and Debugging).
Image generation input_references and video generation frame_images accept the same part — for those, the referenced file must be an image.
Do not combine file_id with file_data in one part: a part that carries file_data is treated as classic inline content and the id is ignored.
Every file a request sends or gets back is kept in Files under its own id, and every answer names it:
| The file | Where its id is |
|---|---|
| a picture from image generation | file_id beside each entry of data |
| a picture, a song or a spoken reply from a chat completion | file_id beside each picture in images, and beside audio; in a stream, in the chunk that carries the picture |
| a clip from video generation | file_ids on the completed job, one per clip |
speech from /v1/audio/speech, whose answer is raw bytes | the X-File-Id response header |
| a picture, a sound or a document sent inline with a request | kept beside the call; re-sending the same history keeps it once |
Each key's storage is on by default and can be switched off in the key's settings in your dashboard. A key with storage off keeps nothing it sends or gets back, and its answers carry no file_id. Uploads to POST /v1/files are always kept.
Supported types (detected from the bytes): images (png, jpeg, webp, gif), audio (mp3, wav, flac, m4a, ogg, aac), PDF, office documents (docx, xlsx, pptx), and plain text — any file whose bytes read as text is accepted and stored as text/plain (txt, md, csv, and json uploads keep their extensions; other text files are stored as .txt).
File endpoints use the standard error envelope — unsupported_file_type and invalid_file_reference are listed with the rest of the codes in Errors and Debugging. One exception: an upload over the 100 MB per-file cap is rejected by request validation and returns the standard 422 validation body ({"message": …, "errors": {"file": […]}}), not the flat error envelope.