Files API
A file you upload once and refer to by id afterwards, instead of resending the bytes every request. Files belong to the account the key draws from: a file stored with one of your keys is readable with any other key on the same account, and reads as absent to everyone else.
Files are temporary. A file expires 48 hours after it is stored, whether or not anything has read
it, and the response states the instant in expires_at. From that instant every route answers 404
for it, exactly as for a file you deleted, and its bytes are removed from the database shortly after.
Keep your own copy of anything you need for longer; deleting earlier is always allowed.
The bytes live in the marketplace's database rather than on a gateway's disk. That is what makes a file you upload readable on the next request: the API runs on more than one gateway, and a file written to one machine's disk is a file the next call cannot find. They are stored encrypted, under a key that is kept outside the database and destroyed within 50 hours of the upload, so a backup of the database cannot give a file back after that.
Two limits apply. One file may hold 32 MiB, and one account may hold 512 MiB across every file it has stored and not yet expired. Deleting a file, or its expiry, frees what it held immediately.
POST /files
Stores a file. The body is multipart/form-data with the bytes in a part named file; the part's
filename and content type are what the store keeps.
curl https://api-staging.heimdallm.com/api/v1/files \
-H "Authorization: Bearer $HEIMDALL_API_KEY" \
-F "file=@handbook.pdf"
{
"data": {
"id": "file_2f1c4a7e9b0d46538a1c7e5f3b204d61",
"filename": "handbook.pdf",
"mime_type": "application/pdf",
"size_bytes": 184320,
"created_at": "2026-08-16T11:59:31.000Z",
"expires_at": "2026-08-18T11:59:31.000Z",
"downloadable": true
}
}
The accepted types are application/json, application/pdf, image/gif, image/jpeg, image/png,
image/webp, text/csv, text/markdown and text/plain. A part sent as application/octet-stream
— what a client says when it knows nothing about the file — is typed from its filename extension.
Anything else is refused with 400.
The filename comes back in the download's Content-Disposition, so it is kept as a single path
segment: directory separators, control characters and quotes are stripped, and the rest is bounded to
200 characters.
GET /files
Every unexpired file on the account, newest first. limit is 1 through 100 and defaults to 20; after is the
id the previous page ended on, and next_cursor is null on the last page.
curl "https://api-staging.heimdallm.com/api/v1/files?limit=20" \
-H "Authorization: Bearer $HEIMDALL_API_KEY"
{
"data": [{ "id": "file_2f1c4a7e9b0d46538a1c7e5f3b204d61", "filename": "handbook.pdf", "…": "…" }],
"next_cursor": null
}
An after naming no file of your account is refused as an unknown cursor rather than answered with an
empty page, so the cursor cannot be used to learn whether an id exists somewhere else. A file that has
expired since the previous page was read names no file either, so restart from the first page.
GET /files/:id
The file's metadata, without its content.
curl https://api-staging.heimdallm.com/api/v1/files/file_2f1c4a7e9b0d46538a1c7e5f3b204d61 \
-H "Authorization: Bearer $HEIMDALL_API_KEY"
GET /files/:id/content
The stored bytes, served whole under the type the file was stored as, with the filename in
Content-Disposition. Ranges are not offered.
curl https://api-staging.heimdallm.com/api/v1/files/file_2f1c4a7e9b0d46538a1c7e5f3b204d61/content \
-H "Authorization: Bearer $HEIMDALL_API_KEY" \
-o handbook.pdf
DELETE /files/:id
Removes the file and its content, and frees what it held against the quota.
curl -X DELETE https://api-staging.heimdallm.com/api/v1/files/file_2f1c4a7e9b0d46538a1c7e5f3b204d61 \
-H "Authorization: Bearer $HEIMDALL_API_KEY"
{ "data": { "success": true } }
Not idempotent: a second delete answers 404, because a file that is gone and a file that was never
stored are the same answer.
Fields
| Field | Type | Notes |
|---|---|---|
id | string | file_ followed by 32 hex characters. How every other route addresses it. |
filename | string | The name the upload carried, kept as one path segment. |
mime_type | string | The type the content route serves it under. |
size_bytes | integer | The stored size, and what the file counts for against the account quota. |
created_at | string | ISO 8601. |
expires_at | string | ISO 8601. When the file stops existing: 48 hours after created_at. |
downloadable | boolean | The bytes are held and will be served at /files/:id/content. |
Refusals
| Status | When |
|---|---|
400 | The body was not multipart, carried no file part or an empty one, or named a refused type. |
404 | No such file on your account — the same answer an id another account owns gets, and the one a file past expires_at gets. |
409 | The file would put the account past its quota. |
413 | The file is larger than 32 MiB. |
503 | The keys stored files are encrypted under could not be reached. Retry shortly; the answer says nothing about the file. |
A 409 carries metadata.quota_bytes, metadata.used_bytes and metadata.file_bytes, so a client
can say exactly how much to free rather than guess.
Using a file in a request
A stored file is named by file_id wherever a request would otherwise carry the document: in a file
part on /chat/completions, in an input_file block on
/responses, or in a document or image block whose source is
{ "type": "file", "file_id": "…" } on /messages. The marketplace reads the
file under your account when the request arrives, and sends the model's endpoint its bytes as a
data: URL, so the endpoint must declare a file input that admits the file's type. An id the
account cannot read is the same 404 described above. The
Documents & files guide walks through it.
Your files are your data
Files are exported with the rest of your account — metadata and content both, for the files that have not expired — and erased when an account deletion runs; Data & privacy is where both are described, and where the limits of expiry are stated. Nothing about a file reaches a provider by reference: a file part sent upstream carries the bytes the marketplace read for that request.