Skip to the documentation
API

Files

Every operation in this group: what it accepts, what it returns and the errors it can answer with.

Operations

Every file the mailbox holds, sent, received and uploaded, with its bytes, its totals and the download links it went out as. It is the Files page of the app, gated like the mail: threads:read reads it and threads:write uploads and deletes. Only an upload that no message or live download link uses can be deleted, one at a time or up to 100 at once. Received and sent files stay with their message.

GET/files

List files

Scopesfiles:readReads

Every attachment the mailbox holds, sent and received, and every file uploaded to it, the Files page of the app: name, type, size, the address it came to, the thread it belongs to and whether it can be deleted. A deleted file is not listed.

Only an upload that nothing depends on can be deleted, and its deletable is true. Every other file has a usage saying what keeps it: received or sent for a file that came in or went out on a message, linked for an upload that went out as a download link that still works, and scheduled for one attached to a message that has not gone out yet.

Files have scopes of their own: files:read reads them and files:write uploads, deletes and publishes them. Reading an email with threads:read still reads the attachments on it. A key limited to particular addresses or domains sees only the files that arrived at them. It does not see files uploaded for the whole workspace, which have no deliveredTo.

The index starts from the day it shipped, so an older mailbox lists what has arrived since. Older attachments are still on their messages, where GET /threads/{id}/messages/{messageId}/attachments reads them.

Requires the files:read scope.

Query parameters

qstring

Searches the file name and its type. Words match loosely, and a close spelling is tried when nothing matches exactly.

Up to 200 characters
kindstring

Keeps one kind of file, the choices of the filter on the Files page: images, PDFs, audio, video or text.

One of"image""pdf""audio""video""text"
directionstring

Keeps one direction: inbound for files that arrived on a message, outbound for files that went out on one, and uploaded for files put on the Files page.

One of"inbound""outbound""uploaded"
addressstring

Keeps the files of one address, the deliveredTo of the file, compared without regard to case.

Up to 320 characters
sincestring

Keeps files added at or after this moment, as an ISO 8601 date or date-time.

Formatdate-time
untilstring

Keeps files added before this moment, as an ISO 8601 date or date-time.

Formatdate-time
sortstring

The order. A cursor carries on in the order it was handed out in, and one handed out under another sort is a 400 invalid_cursor.

One of"newest""oldest""largest""name"Default"newest"
limitinteger

Rows per page, 1 to 100.

At least 1At most 100Default25
cursorstring

The previous page's nextCursor, passed back as it came. It is opaque: it holds where the last row sat in this list's order, so a row deleted or edited between pages never breaks the walk, and the next page starts at the first row that sorts after it. A value this list did not hand out is a 400 invalid_cursor.

Returns

A page of files in the order sort names.

Errors

The errors every operation can return400401403404422500Error catalog

Also available in

SDK
files.list()files.listAll()files.iterate()
CLI
openemail files list
MCP
listFiles

POST/files

Upload a file

Scopesfiles:writeChanges data

Stores a file on the Files page, the way its Upload button does, and answers with it. Send the file itself as the body, up to 100 MB, with its type in Content-Type and its name in filename. Attach it to a send as { "fileId": "..." } in the attachments of POST /emails, which is how a file larger than the inline cap goes out.

A key limited to particular addresses or domains uploads to the first address it holds, which becomes deliveredTo. An unlimited key uploads for the whole workspace and deliveredTo is null, so a narrowed key never reaches that file.

A workspace keeps up to 10 GB of uploads and takes 500 an hour. Nothing makes an upload safe to repeat, so after a lost response look for the file with GET /files before sending it again.

Requires the files:write scope.

Query parameters

filenamestring

The name the file is stored and downloaded under, such as report.pdf, taken as it is. It is cleaned rather than refused: characters a file name cannot hold become _, and a name longer than 255 characters is cut, keeping its extension. Left out, X-Filename is read instead.

Headers

X-Filenamestring

The file name, URI encoded, for a client that would rather not put it in the URL. filename wins when both are sent.

Request body

Content type*/*

binary

Returns

201File

Stored. direction is uploaded, usage is null and deletable is true.

Errors

400

upload_no_name: no name in filename or X-Filename. upload_empty: the body is empty. upload_dangerous: a program or script, judged by its name, which cannot be stored.

403

insufficient_scope: the key lacks files:write. upload_no_address: a key limited to particular addresses or domains that holds no address.

413

upload_too_large: the file is larger than 100 MB.

429

upload_rate_limited: 500 files were uploaded to this workspace in the last hour, deleted ones included. Try again later.

502

upload_failed: the file could not be stored, and nothing was kept. Try again.

507

upload_storage_full: the uploads on this workspace would pass 10 GB. Delete some first.

The errors every operation can return401404422500Error catalog

Also available in

SDK
files.upload()
CLI
openemail files upload
MCP
uploadFile

GET/files/stats

Count files

Scopesfiles:readReads

The numbers on the Analytics tab of the Files page in one request: how many files the mailbox holds and how many bytes they take, in all and split into received, sent and uploaded, the file types and the addresses taking the most space, how many files arrived on each of the last 30 days, and the download links still working with how often they were fetched.

Deleted files are not counted. A key limited to particular addresses or domains counts only the files that arrived at them, so files uploaded for the whole workspace are left out of its numbers.

Requires the files:read scope.

Returns

The numbers on the Analytics tab of the Files page.

Errors

The errors every operation can return400401403404422500Error catalog

Also available in

SDK
files.stats()
CLI
openemail files stats
MCP
getFileStats

POST/files/batch-delete

Delete files in bulk

Scopesfiles:writeDeletes

Deletes up to 100 files in one call, each the way DELETE /files/{id} deletes one, and reports the rest instead of failing. It is what selecting several files on the Files page and pressing Delete does.

Only an upload that nothing depends on is deleted. A file that was received or sent, or an upload that went out as a download link that still works or is attached to a message that has not gone out yet, comes back in kept with its usage and a reason. An id that is unknown, already deleted or outside the addresses a narrowed key holds comes back in missing. The rule is checked again at the moment of deleting, so a file that became used in between is kept. There is no undo.

Requires the files:write scope.

Request body

idsstring[]Required

1 to 100 file ids from GET /files, each at most 128 characters. A repeated id counts once.

1 to 100 items

Returns

What was deleted, what was kept and why, and what was not found.

Errors

400

malformed_json: the body is not JSON.

422

invalid_parameter on ids for none or more than 100, and unknown_parameter for any key but ids.

The errors every operation can return401403404500Error catalog

Also available in

SDK
files.deleteMany()
CLI
openemail files delete-many
MCP
deleteFiles

GET/files/{id}

Retrieve a file

Scopesfiles:readReads

One file, with usage and deletable saying whether it can be deleted and what keeps it when it cannot. A deleted file, one outside the addresses a narrowed key holds, and an unknown id all answer 404.

Requires the files:read scope.

Path parameters

idstringRequired

The id from GET /files, file_ and 24 hex.

Returns

200File

The file. GET /files/{id}/content has its bytes.

Errors

The errors every operation can return400401403404422500Error catalog

Also available in

SDK
files.get()
CLI
openemail files get
MCP
getFile

DELETE/files/{id}

Delete a file

Scopesfiles:writeDeletes

Deletes an uploaded file: its bytes are removed from storage and it leaves the Files page. The row stays, marked deleted, so the index knows the file existed. There is no undo.

Only an upload that nothing depends on can be deleted, the files whose deletable is true. A file that was received or sent stays with its message, and so does an upload that went out as a download link that still works or is attached to a message that has not gone out yet: those answer 409 file_in_use. An upload that was sent inside a message can still be deleted, because the message keeps its own copy, which is listed as a separate sent file.

It needs the same scope as uploading. A key limited to particular addresses or domains may delete only a file that arrived at one of them. POST /files/batch-delete deletes many at once.

Requires the files:write scope.

Path parameters

idstringRequired

The id from GET /files, file_ and 24 hex.

Returns

200object

Deleted. The bytes are gone and the file leaves the Files page.

objectstring
One of"file"
idstring
deletedboolean
One oftrue

Errors

404

resource_not_found: the id is unknown, the file is already deleted, or it is outside the addresses a narrowed key holds.

409

file_in_use on id: something depends on the file, so it stays. The message names the file and says why, and usage on GET /files/{id} says the same.

The errors every operation can return400401403422500Error catalog

Also available in

SDK
files.delete()
CLI
openemail files delete
MCP
deleteFile

GET/files/{id}/content

Download a file

Scopesfiles:readReads

The file exactly as it is stored, the same bytes the Download action on the Files page saves. A file whose bytes are gone from storage answers 404 like a missing one.

Requires the files:read scope.

Path parameters

idstringRequired

The id from GET /files, file_ and 24 hex.

Query parameters

downloadstring

true always answers Content-Disposition: attachment. Otherwise an image, a PDF or plain text answers inline.

One of"true""false"

Returns

200binaryapplication/octet-stream

The bytes, under the file's own Content-Type and name.

Errors

The errors every operation can return400401403404422500Error catalog

Also available in

SDK
files.download()
CLI
openemail files download

POST/files/{id}/links

Publish a file at a public link

Scopesfiles:writeChanges data

Makes a public download link for the file that anybody holding it can open without signing in, which is how an image or document is referenced from an email or a web page. Each call makes a new link with its own download count. The link keeps working until it is revoked or the file is deleted.

A file that came in or went out on a message is copied to public storage first. Programs and scripts are refused with 422 file_unshareable.

Requires the files:write scope.

Path parameters

idstringRequired

The id from GET /files, file_ and 24 hex.

Request body

domainstring

The domain whose files host serves the link, such as acme.com for files.acme.com. Left out, the domain of the address the file belongs to is used, and a file with no address, or a domain with no files host, is served from the API address.

Returns

The new link. url opens the file with no sign-in.

Errors

422

file_unshareable: the file is a program or script, which never gets a public link.

The errors every operation can return400401403404500Error catalog

Also available in

SDK
files.createLink()
CLI
openemail files create-link
MCP
createFileLink

Objects

Fileobject

objectstring
One of"file"
idstring

file_ and 24 hex.

filenamestring
mimeTypestring
sizeBytesinteger
directionstring

inbound for a file that arrived, outbound for one that was sent, uploaded for one added on the Files page or with POST /files.

One of"inbound""outbound""uploaded"
threadIdstring
Can be null
messageIdstring
Can be null
deliveredTostring

The address it came to, lower-cased. An upload carries the first address its uploader was limited to, and none when the uploader was not limited, which the app shows as the whole workspace. A key limited to some addresses sees only files whose deliveredTo it holds, so a file with none recorded reaches only an unrestricted key.

Can be null
usagestring

Why the file is kept, or null when it can be deleted. received and sent are files that came in or went out on a message, and they stay with it. linked is an upload that went out as a download link that still works, and scheduled is one attached to a message that has not gone out yet.

Can be nullOne of"received""sent""linked""scheduled"
deletableboolean

Whether DELETE /files/{id} will take it. True only for an upload that nothing depends on, which is when usage is null.

visibilitystring

public while at least one download link to the file works: a link made with POST /files/{id}/links, or the link a sent message carries for it. private otherwise, and every upload starts private.

One of"public""private"
publicUrlstring

A working public download link, on the domain's own files host when one is set up and on ours otherwise. A published link is preferred over a link from a sent message. Null while the file is private.

Can be null
createdAtstring
Formatdate-time

FileBatchDeleteobject

objectstring
One of"file_batch_delete"
deletedstring[]

The ids this call deleted.

keptobject[]

Files something depends on. Nothing happened to them.

idstring
filenamestring
usagestring

What keeps it, the same value as usage on the file.

One of"received""sent""linked""scheduled"
reasonstring

The same in a sentence, such as "It went out as a download link that still works."

missingstring[]

Ids that are unknown, already deleted, or outside the addresses a narrowed key holds.

FileLinkRevocationobject

objectstringRequired
One of"file_link_revocation"
fileIdstringRequired
revokedintegerRequired

How many links were still working and now are not. Links revoked earlier are not counted.

At least 0

FileListobject

objectstring
One of"list"
dataFile[]
hasMoreboolean

True when another page follows. Pass nextCursor back as cursor to read it.

nextCursorstring

An opaque cursor for the next page, or null on the last page. Pass it back unchanged.

Can be null

FileStatsobject

objectstring
One of"file_stats"
totalsobject

Every file counted here, however it came.

filesinteger
bytesinteger

Their sizes added up.

receivedobject

Files that came in on a message.

filesinteger
bytesinteger

Their sizes added up.

sentobject

Files that went out on a message.

filesinteger
bytesinteger

Their sizes added up.

uploadedobject

Files uploaded on the Files page or with POST /files.

filesinteger
bytesinteger

Their sizes added up.

typesobject[]

The 8 types taking the most bytes, largest first.

mimeTypestring
filesinteger
bytesinteger
byDayobject[]

Files added on each of the last 30 days in UTC, today included, oldest first. A day on which none arrived has no entry, so a chart must fill the gaps.

daystring

YYYY-MM-DD.

Formatdate
filesinteger
bytesinteger
addressesobject[]

The 6 addresses holding the most bytes, largest first. A file uploaded for the whole workspace has no address, so it counts in the totals and never here.

addressstring
filesinteger
bytesinteger
linksobject

The download links that still work.

sharesinteger

How many links.

downloadsinteger

Fetches by a person across all of them. Scanners and link previewers are left out.

lastDownloadAtstring

The latest fetch, or null when none was ever fetched.

Can be nullFormatdate-time