---
title: "openemail.files"
description: "Every method in this namespace: its signature, its parameters, what it returns and an example."
url: "https://openemail.uk/docs/python/reference/files"
area: "Python"
category: "Reference"
---

# openemail.files

Every method in this namespace: its signature, its parameters, what it returns and an example.

## Methods

Every attachment the mailbox holds, sent and received, and the files uploaded to it, with their bytes, the download links they went out as and whether each can be deleted.

### `files.list()`

List one page of the files the mailbox holds

```python
def list(
    *,
    limit: int | None = None,
    cursor: str | None = None,
    q: str | None = None,
    kind: FileKind | None = None,
    direction: FileDirection | None = None,
    address: str | None = None,
    since: datetime | str | None = None,
    until: datetime | str | None = None,
    sort: FileSort | None = None,
    api_key: str | None = None,
    timeout: float | None = None,
) -> Page[FileResource]
```

Returns one page of the Files page: every attachment the mailbox holds, sent and received, and every file uploaded to it, with its name, type, size, the address it came to, the thread it belongs to and whether it can be deleted. `list_all` collects every page and `iterate` walks them lazily.

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

A key limited to particular addresses or domains sees only the files that arrived at them, the same boundary the thread list uses, so it does not see files uploaded for the whole workspace. A deleted file is not listed.

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 `threads.list_attachments` reads them.

Scopes: `files:read`.

**Parameters**

- `limit` (`int`): Page size, from 1 to 100. The server defaults to 25.
- `cursor` (`str`): The `nextCursor` of the previous page. Leave it out for the first page.
- `q` (`str`): Searches the file name and its type. Words match loosely, and a close spelling is tried when nothing matches exactly.
- `kind` (`FileKind`): Keeps one kind: `image`, `pdf`, `audio`, `video` or `text`, the choices of the filter on the Files page.
- `direction` (`FileDirection`): Keeps one direction: `inbound` for files that arrived on a message, `outbound` for files that went out on one, `uploaded` for files put on the Files page.
- `address` (`str`): Keeps the files of one address, the `deliveredTo` of the file, compared without regard to case.
- `since` (`datetime | str`): Keeps files added at or after this moment. A `datetime` or an ISO 8601 string.
- `until` (`datetime | str`): Keeps files added before this moment. A `datetime` or an ISO 8601 string.
- `sort` (`FileSort`): `newest` (the default), `oldest`, `largest` or `name`. A cursor carries on in the order it was handed out in.
- `api_key` (`str`): Overrides the client's API key for this call only.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`Page[FileResource]`, a dict with `items`, `hasMore` and `nextCursor`. Each item has `id`, `filename`, `mimeType`, `sizeBytes`, `direction`, `threadId`, `messageId`, `deliveredTo`, `usage`, `deletable`, `visibility`, `publicUrl` and `createdAt`.

**Example**

```python
from openemail import openemail

page = openemail.files.list(kind='pdf', sort='largest')

for file in page['items']:
    print(file['filename'], file['sizeBytes'], file['usage'] or 'deletable')

print(page['hasMore'], page['nextCursor'])
```

**Notes**

- Needs `files:read`. Reading an email with `threads:read` still reads the attachments on it, while `files:read` reads every file, uploads included.
- The cursor is opaque and holds where the last row sat in this order. A cursor this list did not hand out, or one handed out under another `sort`, is a 400 `invalid_cursor`.

Also available in: API [`GET /files`](https://openemail.uk/docs/api/reference/files#get-files); TypeScript [`files.list()`](https://openemail.uk/docs/sdk/reference/files#list); Ruby [`files.list`](https://openemail.uk/docs/ruby/reference/files#list); CLI [`openemail files list`](https://openemail.uk/docs/cli/reference/files#files-list).

### `files.list_all()`

Collect every file into one list

```python
def list_all(
    *,
    limit: int | None = None,
    cursor: str | None = None,
    q: str | None = None,
    kind: FileKind | None = None,
    direction: FileDirection | None = None,
    address: str | None = None,
    since: datetime | str | None = None,
    until: datetime | str | None = None,
    sort: FileSort | None = None,
    api_key: str | None = None,
    timeout: float | None = None,
) -> builtins.list[FileResource]
```

Walks every page of `list` and returns every file in one list, in the order `sort=` names. One request per page, with the same filters on each.

Scopes: `files:read`.

**Parameters**

- `limit` (`int`): Page size for each request, from 1 to 100. The server defaults to 25.
- `cursor` (`str`): Starts the walk after this cursor instead of the first page.
- `q` (`str`): Searches the file name and its type. Words match loosely, and a close spelling is tried when nothing matches exactly.
- `kind` (`FileKind`): Keeps one kind: `image`, `pdf`, `audio`, `video` or `text`, the choices of the filter on the Files page.
- `direction` (`FileDirection`): Keeps one direction: `inbound` for files that arrived on a message, `outbound` for files that went out on one, `uploaded` for files put on the Files page.
- `address` (`str`): Keeps the files of one address, the `deliveredTo` of the file, compared without regard to case.
- `since` (`datetime | str`): Keeps files added at or after this moment. A `datetime` or an ISO 8601 string.
- `until` (`datetime | str`): Keeps files added before this moment. A `datetime` or an ISO 8601 string.
- `sort` (`FileSort`): `newest` (the default), `oldest`, `largest` or `name`. A cursor carries on in the order it was handed out in.
- `api_key` (`str`): Overrides the client's API key for every page of this walk.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`list[FileResource]` holding every file.

**Example**

```python
from openemail import openemail

invoices = openemail.files.list_all(q='invoice', direction='inbound')

total = sum(file['sizeBytes'] for file in invoices)
print(len(invoices), 'invoices received,', total, 'bytes in all')
```

**Notes**

- If any page fails the call raises, and the rows already fetched are discarded.

Also available in: API [`GET /files`](https://openemail.uk/docs/api/reference/files#get-files); TypeScript [`files.listAll()`](https://openemail.uk/docs/sdk/reference/files#listAll); Ruby [`files.list_all`](https://openemail.uk/docs/ruby/reference/files#listAll).

### `files.iterate()`

Stream the files one at a time

```python
def iterate(
    *,
    limit: int | None = None,
    cursor: str | None = None,
    q: str | None = None,
    kind: FileKind | None = None,
    direction: FileDirection | None = None,
    address: str | None = None,
    since: datetime | str | None = None,
    until: datetime | str | None = None,
    sort: FileSort | None = None,
    api_key: str | None = None,
    timeout: float | None = None,
) -> Iterator[FileResource]
```

Returns a generator that yields one file at a time, in the order `sort=` names, and requests the next page only once the current one is used up. Nothing is fetched until the loop starts, and leaving the loop with `break` stops the requests.

Scopes: `files:read`.

**Parameters**

- `limit` (`int`): Page size for each request, from 1 to 100. The server defaults to 25.
- `cursor` (`str`): Starts the walk after this cursor instead of the first page.
- `q` (`str`): Searches the file name and its type. Words match loosely, and a close spelling is tried when nothing matches exactly.
- `kind` (`FileKind`): Keeps one kind: `image`, `pdf`, `audio`, `video` or `text`, the choices of the filter on the Files page.
- `direction` (`FileDirection`): Keeps one direction: `inbound` for files that arrived on a message, `outbound` for files that went out on one, `uploaded` for files put on the Files page.
- `address` (`str`): Keeps the files of one address, the `deliveredTo` of the file, compared without regard to case.
- `since` (`datetime | str`): Keeps files added at or after this moment. A `datetime` or an ISO 8601 string.
- `until` (`datetime | str`): Keeps files added before this moment. A `datetime` or an ISO 8601 string.
- `sort` (`FileSort`): `newest` (the default), `oldest`, `largest` or `name`. A cursor carries on in the order it was handed out in.
- `api_key` (`str`): Overrides the client's API key for every page of this walk.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`Iterator[FileResource]`, a generator yielding one file per step.

**Example**

```python
from openemail import openemail

for file in openemail.files.iterate(sort='oldest'):
    print(file['createdAt'], file['filename'], file['deliveredTo'])
```

**Notes**

- The generator is lazy, so an abandoned loop costs only the pages you consumed.

Also available in: API [`GET /files`](https://openemail.uk/docs/api/reference/files#get-files); TypeScript [`files.iterate()`](https://openemail.uk/docs/sdk/reference/files#iterate); Ruby [`files.iterate`](https://openemail.uk/docs/ruby/reference/files#iterate).

### `files.get()`

Read one file by id

```python
def get(
    id: str,
    *,
    api_key: str | None = None,
    timeout: float | None = None,
) -> FileResource
```

Returns one file: its name, type, size, whether it was sent, received or uploaded, the address it came to, the thread and message it belongs to, and whether it can be deleted. `download` fetches its bytes.

`deletable` is `True` only for an upload that nothing depends on. Otherwise `usage` says 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.

Scopes: `files:read`.

**Parameters**

- `id` (`str`, required): The id from `list`, `file_` and 24 hex.
- `api_key` (`str`): Overrides the client's API key for this call only.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`FileResource`, with `usage` and `deletable`. `visibility` is `public` while a download link to it works, and `publicUrl` is then one of those links.

**Example**

```python
from openemail import openemail

file = openemail.files.get('file_6bb640f5b99e47deb758f1f5')

print(file['filename'], file['mimeType'], file['deletable'])
```

**Notes**

- A deleted file, one outside the addresses a narrowed key holds, and an unknown id all answer 404 `resource_not_found`, which raises `OpenEmailApiError` with `is_not_found`.

Also available in: API [`GET /files/{id}`](https://openemail.uk/docs/api/reference/files#get-files-id); TypeScript [`files.get()`](https://openemail.uk/docs/sdk/reference/files#get); Ruby [`files.get`](https://openemail.uk/docs/ruby/reference/files#get); CLI [`openemail files get`](https://openemail.uk/docs/cli/reference/files#files-get).

### `files.stats()`

Count the files and the space they take

```python
def stats(
    *,
    api_key: str | None = None,
    timeout: float | None = None,
) -> FileStatsResource
```

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

Scopes: `files:read`.

**Parameters**

- `api_key` (`str`): Overrides the client's API key for this call only.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`FileStatsResource` with `totals`, `received`, `sent` and `uploaded`, each a dict with `files` and `bytes`, then `types`, `byDay`, `addresses` and `links`.

**Example**

```python
from openemail import openemail

stats = openemail.files.stats()

print(stats['totals']['files'], 'files in', stats['totals']['bytes'], 'bytes')

for kind in stats['types']:
    print(kind['mimeType'], kind['files'], kind['bytes'])
```

**Notes**

- Needs `files:read`.
- `types` holds at most the 8 types taking the most bytes and `addresses` at most the 6 addresses holding the most. A file uploaded for the whole workspace has no address, so it counts in the totals and never in `addresses`.
- `byDay` covers the last 30 days in UTC, today included, and is sparse: a day on which no file arrived has no entry, so a chart must fill the gaps. Each `day` is `YYYY-MM-DD`.
- `links` counts the download links that still work: `shares` of them, fetched `downloads` times in all, last at `lastDownloadAt`, which is `None` when none was ever fetched. Scanners and link previewers are left out of the count.

Also available in: API [`GET /files/stats`](https://openemail.uk/docs/api/reference/files#get-files-stats); TypeScript [`files.stats()`](https://openemail.uk/docs/sdk/reference/files#stats); Ruby [`files.stats`](https://openemail.uk/docs/ruby/reference/files#stats); CLI [`openemail files stats`](https://openemail.uk/docs/cli/reference/files#files-stats).

### `files.download()`

Fetch the bytes of a file

```python
def download(
    id: str,
    *,
    api_key: str | None = None,
    timeout: float | None = None,
) -> bytes
```

Returns the file exactly as it is stored, the same bytes the Download action on the Files page saves. The server sends it with the file's own `Content-Type` and name, but the method returns only the bytes, so read the name and the type from `get`.

Scopes: `files:read`.

**Parameters**

- `id` (`str`, required): The id from `list`.
- `api_key` (`str`): Overrides the client's API key for this call only.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`bytes` holding the file.

**Example**

```python
from pathlib import Path

from openemail import openemail

file = openemail.files.get('file_6bb640f5b99e47deb758f1f5')
data = openemail.files.download(file['id'])

name = Path(file['filename']).name
Path(name).write_bytes(data)
print('Saved', name, len(data), 'bytes')
```

**Notes**

- The whole file is held in memory, so fetch very large ones one at a time.
- A file whose bytes are gone from storage answers 404 like a missing one.

Also available in: API [`GET /files/{id}/content`](https://openemail.uk/docs/api/reference/files#get-files-id-content); TypeScript [`files.download()`](https://openemail.uk/docs/sdk/reference/files#download); Ruby [`files.download`](https://openemail.uk/docs/ruby/reference/files#download); CLI [`openemail files download`](https://openemail.uk/docs/cli/reference/files#files-download).

### `files.list_links()`

List the download links a file went out as

```python
def list_links(
    id: str,
    *,
    limit: int | None = None,
    cursor: str | None = None,
    api_key: str | None = None,
    timeout: float | None = None,
) -> Page[FileLinkResource]
```

Returns one page of the download links made for this file, newest first. A large attachment sent from OpenEmail travels as a link rather than inside the message, and each send makes a link of its own. Every row says how often it was fetched, when last, and whether it was revoked. While one of its links still works, an upload cannot be deleted.

A file attached inside a message has no links, because nothing records when those are opened.

Scopes: `files:read`.

**Parameters**

- `id` (`str`, required): The file id from `list`.
- `limit` (`int`): Page size, from 1 to 100. The server defaults to 25.
- `cursor` (`str`): The `nextCursor` of the previous page. Leave it out for the first page.
- `api_key` (`str`): Overrides the client's API key for this call only.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`Page[FileLinkResource]`, a dict with `items`, `hasMore` and `nextCursor`. Each item has `id`, `url`, `host`, `published`, `downloads`, `lastDownloadAt`, `revokedAt` and `createdAt`.

**Example**

```python
from openemail import openemail

page = openemail.files.list_links('file_6bb640f5b99e47deb758f1f5')

for link in page['items']:
    print(link['url'], link['downloads'], link['revokedAt'])
```

**Notes**

- The count leaves out scanners and link previewers, the same classifier the `email.downloaded` webhook uses.
- `published` is `True` for a link made with `create_link`, which does not stop the file being deleted, and `False` for a link that went out in a message.

Also available in: API [`GET /files/{id}/links`](https://openemail.uk/docs/api/reference/files#get-files-id-links); TypeScript [`files.listLinks()`](https://openemail.uk/docs/sdk/reference/files#listLinks); Ruby [`files.list_links`](https://openemail.uk/docs/ruby/reference/files#listLinks); CLI [`openemail files list-links`](https://openemail.uk/docs/cli/reference/files#files-list-links).

### `files.list_all_links()`

Collect every download link of a file into one list

```python
def list_all_links(
    id: str,
    *,
    limit: int | None = None,
    cursor: str | None = None,
    api_key: str | None = None,
    timeout: float | None = None,
) -> builtins.list[FileLinkResource]
```

Walks every page of `list_links` and returns every download link of the file in one list, newest first. One request per page, with the same `limit=` on each.

Scopes: `files:read`.

**Parameters**

- `id` (`str`, required): The file id from `list`.
- `limit` (`int`): Page size for each request, from 1 to 100. The server defaults to 25.
- `cursor` (`str`): Starts the walk after this cursor instead of the first page.
- `api_key` (`str`): Overrides the client's API key for every page of this walk.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`list[FileLinkResource]` holding every download link of the file.

**Example**

```python
from openemail import openemail

links = openemail.files.list_all_links('file_6bb640f5b99e47deb758f1f5')

fetched = sum(link['downloads'] for link in links)
print(len(links), 'links fetched', fetched, 'times')
```

**Notes**

- If any page fails the call raises, and the rows already fetched are discarded.

Also available in: API [`GET /files/{id}/links`](https://openemail.uk/docs/api/reference/files#get-files-id-links); TypeScript [`files.listAllLinks()`](https://openemail.uk/docs/sdk/reference/files#listAllLinks); Ruby [`files.list_all_links`](https://openemail.uk/docs/ruby/reference/files#listAllLinks).

### `files.iterate_links()`

Stream the download links of a file one at a time

```python
def iterate_links(
    id: str,
    *,
    limit: int | None = None,
    cursor: str | None = None,
    api_key: str | None = None,
    timeout: float | None = None,
) -> Iterator[FileLinkResource]
```

Returns a generator that yields one link at a time, newest first, and requests the next page only once the current one is used up. Nothing is fetched until the loop starts, and leaving the loop with `break` stops the requests.

Scopes: `files:read`.

**Parameters**

- `id` (`str`, required): The file id from `list`.
- `limit` (`int`): Page size for each request, from 1 to 100. The server defaults to 25.
- `cursor` (`str`): Starts the walk after this cursor instead of the first page.
- `api_key` (`str`): Overrides the client's API key for every page of this walk.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`Iterator[FileLinkResource]`, a generator yielding one link per step.

**Example**

```python
from openemail import openemail

for link in openemail.files.iterate_links('file_6bb640f5b99e47deb758f1f5'):
    if link['revokedAt'] is None:
        print('Still working:', link['url'])
        break
```

**Notes**

- The generator is lazy, so an abandoned loop costs only the pages you consumed.

Also available in: API [`GET /files/{id}/links`](https://openemail.uk/docs/api/reference/files#get-files-id-links); TypeScript [`files.iterateLinks()`](https://openemail.uk/docs/sdk/reference/files#iterateLinks); Ruby [`files.iterate_links`](https://openemail.uk/docs/ruby/reference/files#iterateLinks).

### `files.create_link()`

Publish a file at a public link

```python
def create_link(
    id: str,
    *,
    domain: str | None = None,
    api_key: str | None = None,
    timeout: float | None = None,
) -> FileLinkResource
```

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

The link lives on the files host of `domain=` when that domain has one set up, such as `files.acme.com`, else on the files host of the address the file belongs to, else on the OpenEmail API address. 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`.

Scopes: `files:write`.

**Parameters**

- `id` (`str`, required): The file id from `list`.
- `domain` (`str`): The domain whose files host serves the link, such as `acme.com`.
- `api_key` (`str`): Overrides the client's API key for this call only.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`FileLinkResource` with `id`, `url`, `host`, `downloads`, `lastDownloadAt`, `revokedAt` and `createdAt`.

**Example**

```python
from openemail import openemail

link = openemail.files.create_link('file_6bb640f5b99e47deb758f1f5', domain='acme.com')

print(link['url'], link['host'])
```

**Notes**

- Needs `files:write`.
- Not retried after a lost response, because a second call makes a second link. Look for it with `list_links` first.

Also available in: API [`POST /files/{id}/links`](https://openemail.uk/docs/api/reference/files#post-files-id-links); TypeScript [`files.createLink()`](https://openemail.uk/docs/sdk/reference/files#createLink); Ruby [`files.create_link`](https://openemail.uk/docs/ruby/reference/files#createLink); CLI [`openemail files create-link`](https://openemail.uk/docs/cli/reference/files#files-create-link).

### `files.revoke_link()`

Revoke a public link

```python
def revoke_link(
    id: str,
    link_id: str,
    *,
    api_key: str | None = None,
    timeout: float | None = None,
) -> FileLinkResource
```

Stops a public link from working, for good, including in mail that already went out with it, and returns the link with `revokedAt` set. Revoking a link that is already revoked returns it unchanged, so the call is safe to repeat, and the SDK retries it after a network failure. An unknown link is a 404.

Scopes: `files:write`.

**Parameters**

- `id` (`str`, required): The file id from `list`.
- `link_id` (`str`, required): The link id from `list_links` or `create_link`, `fshr_` and 24 hex.
- `api_key` (`str`): Overrides the client's API key for this call only.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`FileLinkResource` with `revokedAt` set.

**Example**

```python
from openemail import openemail

link = openemail.files.revoke_link(
    'file_6bb640f5b99e47deb758f1f5', 'fshr_2f9c4a1e7b3d48a06c1e9f4b'
)

print(link['url'], 'stopped working at', link['revokedAt'])
```

**Notes**

- Needs `files:write`.

Also available in: API [`DELETE /files/{id}/links/{linkId}`](https://openemail.uk/docs/api/reference/files#delete-files-id-links-linkid); TypeScript [`files.revokeLink()`](https://openemail.uk/docs/sdk/reference/files#revokeLink); Ruby [`files.revoke_link`](https://openemail.uk/docs/ruby/reference/files#revokeLink); CLI [`openemail files revoke-link`](https://openemail.uk/docs/cli/reference/files#files-revoke-link).

### `files.revoke_all_links()`

Revoke every link to a file

```python
def revoke_all_links(
    id: str,
    *,
    api_key: str | None = None,
    timeout: float | None = None,
) -> FileLinkRevocationResource
```

Makes the file private, as Make private on the Files page does: every public download link to it stops working at once, including the ones in mail that already went out. Returns `revoked`, how many links were still working. The file itself stays, and `create_link` can publish it again at a new link.

A key limited to particular addresses reaches only the files that arrived at them, and any other file is a 404.

Scopes: `files:write`.

**Parameters**

- `id` (`str`, required): The file id from `list`.
- `api_key` (`str`): Overrides the client's API key for this call only.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`FileLinkRevocationResource` with `fileId` and `revoked`, the number of links that stopped working.

**Example**

```python
from openemail import openemail

result = openemail.files.revoke_all_links('file_6bb640f5b99e47deb758f1f5')

print(result['revoked'], 'links stopped working')
```

**Notes**

- A file with no working links returns `revoked` as `0`, so the call is safe to repeat, and the SDK retries it after a network failure.
- Needs `files:write`.

Also available in: API [`DELETE /files/{id}/links`](https://openemail.uk/docs/api/reference/files#delete-files-id-links); TypeScript [`files.revokeAllLinks()`](https://openemail.uk/docs/sdk/reference/files#revokeAllLinks); Ruby [`files.revoke_all_links`](https://openemail.uk/docs/ruby/reference/files#revokeAllLinks); CLI [`openemail files revoke-all-links`](https://openemail.uk/docs/cli/reference/files#files-revoke-all-links).

### `files.upload()`

Upload a file to the Files page

```python
def upload(
    data: RawBody,
    *,
    filename: str,
    content_type: str | None = None,
    api_key: str | None = None,
    timeout: float | None = None,
) -> FileResource
```

Stores the bytes as a new file, the way the Upload button on the Files page does, and returns it: `direction` is `uploaded`, `usage` is `None` and `deletable` is `True`. Attach it to a send as `{'fileId': file['id']}` in `emails.send`, which is how a file larger than the inline cap goes out.

`data` is `bytes`, a `bytearray` or a `memoryview`, sent as the request body. The name travels as the `filename` query parameter, and 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. The type is `content_type=`, and `application/octet-stream` without it. A value that is not shaped like a MIME type is stored as `application/octet-stream` too.

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 `None`, so a narrowed key never reaches that file afterwards.

Scopes: `files:write`.

**Parameters**

- `data` (`RawBody`, required): The file as `bytes`, a `bytearray` or a `memoryview`, not empty and at most 100 MB.
- `filename` (`str`, required): The name the file is stored and downloaded under, such as `report.pdf`. A blank name raises `ValueError` before anything is sent.
- `content_type` (`str`): The MIME type, such as `application/pdf`. Left out, the file is stored as `application/octet-stream`.
- `api_key` (`str`): Overrides the client's API key for this call only.
- `timeout` (`float`): Seconds the upload may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. Left out, it is 600 seconds, or the client's `timeout` when that is longer, and no limit at all when the client's `timeout` is `0`. `0` turns the limit off for this call.

**Returns**

`FileResource` for the new file, with `direction` set to `'uploaded'`, `usage` set to `None` and `deletable` set to `True`.

**Example**

```python
from pathlib import Path

from openemail import openemail

file = openemail.files.upload(
    Path('invoice.pdf').read_bytes(), filename='invoice.pdf', content_type='application/pdf'
)

openemail.emails.send(
    {
        'from': 'billing@acme.com',
        'to': 'ada@example.com',
        'subject': 'Your September invoice',
        'text': 'The invoice is attached.',
        'attachments': [{'fileId': file['id']}],
    }
)
```

**Notes**

- Needs `files:write`, the same scope that deletes files.
- The SDK does not retry an upload, because a second attempt after a lost response could store the file twice. Look for it with `list` before trying again.
- An empty file is a 400 `upload_empty`, a program or script, judged by its name, is a 400 `upload_dangerous`, and a file over 100 MB is a 413 `upload_too_large`. A key limited to addresses that holds none is a 403 `upload_no_address`.
- A workspace keeps up to 10 GB of uploads, and past that a 507 `upload_storage_full` holds until some are deleted. It takes 500 uploads an hour, deleted ones included, and more is a 429 `upload_rate_limited`. A 502 `upload_failed` means storage failed and nothing was kept, so it is safe to try again.

Also available in: API [`POST /files`](https://openemail.uk/docs/api/reference/files#post-files); TypeScript [`files.upload()`](https://openemail.uk/docs/sdk/reference/files#upload); Ruby [`files.upload`](https://openemail.uk/docs/ruby/reference/files#upload); CLI [`openemail files upload`](https://openemail.uk/docs/cli/reference/files#files-upload).

### `files.delete()`

Delete an uploaded file for good

```python
def delete(
    id: str,
    *,
    api_key: str | None = None,
    timeout: float | None = None,
) -> DeletedFileResource
```

Removes an uploaded file: its bytes are deleted 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 are refused with 409 `file_in_use`, and the message names the file and says why. 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.

`delete_many` deletes up to 100 in one call and reports the ones it kept instead of failing.

Scopes: `files:write`.

**Parameters**

- `id` (`str`, required): The id from `list`.
- `api_key` (`str`): Overrides the client's API key for this call only.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`DeletedFileResource`, such as `{'object': 'file', 'id': 'file_6bb640f5b99e47deb758f1f5', 'deleted': True}`.

**Example**

```python
from openemail import OpenEmailApiError, openemail

try:
    removed = openemail.files.delete('file_6bb640f5b99e47deb758f1f5')
    print(removed['id'], removed['deleted'])
except OpenEmailApiError as error:
    if error.is_not_found:
        print('Already deleted')
    elif error.code == 'file_in_use':
        print(error.message)
    else:
        raise
```

**Notes**

- Needs `files:write`, the same scope that uploads. A key limited to some addresses may delete only files that arrived at them, so an upload made for the whole workspace answers it 404.
- A file that is unknown, already deleted or out of reach answers 404 `resource_not_found`. A file in use answers 409 `file_in_use`, with `param` set to `'id'` on the error, and its `usage` on `get` says which rule keeps it.
- The SDK does not retry a delete. A 404 on your own second attempt after a lost response means the first one worked.

Also available in: API [`DELETE /files/{id}`](https://openemail.uk/docs/api/reference/files#delete-files-id); TypeScript [`files.delete()`](https://openemail.uk/docs/sdk/reference/files#delete); Ruby [`files.delete`](https://openemail.uk/docs/ruby/reference/files#delete); CLI [`openemail files delete`](https://openemail.uk/docs/cli/reference/files#files-delete).

### `files.delete_many()`

Delete up to 100 files in one call

```python
def delete_many(
    ids: Sequence[str],
    *,
    api_key: str | None = None,
    timeout: float | None = None,
) -> FileBatchDeleteResource
```

Deletes every file in `ids` that can be deleted, the way `delete` 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 uploaded file that nothing depends on can be deleted. 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. Each of those comes back in `kept` with its `usage` and a `reason` in plain words. 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.

Scopes: `files:write`.

**Parameters**

- `ids` (`Sequence[str]`, required): A list of 1 to 100 file ids from `list`. More than 100, or none, is a 422 on `ids`. A repeated id counts once.
- `api_key` (`str`): Overrides the client's API key for this call only.
- `timeout` (`float`): Seconds this call may take, the response included, before it raises `OpenEmailNetworkError` with `is_timeout`. It overrides the client's `timeout` for this call, and `0` turns the limit off.

**Returns**

`FileBatchDeleteResource` with `object` set to `'file_batch_delete'`, `deleted`, `kept` and `missing`. `deleted` and `missing` are lists of ids, and each entry of `kept` is a dict with `id`, `filename`, `usage` and `reason`.

**Example**

```python
from openemail import openemail

result = openemail.files.delete_many(
    ['file_6bb640f5b99e47deb758f1f5', 'file_0c3e2a91d4b7f6058e1a2b3c']
)

print(len(result['deleted']), 'deleted,', len(result['missing']), 'missing')

for kept in result['kept']:
    print(kept['filename'], kept['reason'])
```

**Notes**

- Needs `files:write`, the same scope that uploads.
- A partial result returns rather than raises, so read `kept` and `missing` to see what stayed.
- The SDK does not retry it. Calling it again after a lost response reports the files the first call deleted in `missing`.
- To clear out every upload that can go, keep the rows of `list_all` whose `deletable` is `True` and pass their ids 100 at a time.

Also available in: API [`POST /files/batch-delete`](https://openemail.uk/docs/api/reference/files#post-files-batch-delete); TypeScript [`files.deleteMany()`](https://openemail.uk/docs/sdk/reference/files#deleteMany); Ruby [`files.delete_many`](https://openemail.uk/docs/ruby/reference/files#deleteMany); CLI [`openemail files delete-many`](https://openemail.uk/docs/cli/reference/files#files-delete-many).
