---
title: "API keys"
description: "`keys.list`, `list_all`, `iterate`, `get`, `create`, `update`, `delete`, `rotate` and `revoke`, and the request log and activity readers."
url: "https://openemail.uk/docs/python/keys"
area: "Python"
category: "Mailbox"
---

# API keys

`keys.list`, `list_all`, `iterate`, `get`, `create`, `update`, `delete`, `rotate` and `revoke`, and the request log and activity readers.

## Every method

**keys.py**

```
from pathlib import Path

from openemail import openemail

key = openemail.keys.create({
    'name': 'Billing sender',
    'scopes': ['emails:send'],
    'domainAllowlist': ['billing.acme.com'],
    'expiresInMinutes': 60 * 24 * 90,
})

secret = Path('.openemail-billing-key')
secret.touch(mode=0o600)
secret.write_text(key['token'])

openemail.keys.update(key['id'], {'enabled': False})

rotated = openemail.keys.rotate(key['id'])
secret.write_text(rotated['token'])

openemail.keys.revoke(key['id'], {'reason': 'Replaced'})
openemail.keys.delete(key['id'])
```

`create` and `rotate` are the only calls that return a secret, in `token`, once. Every read returns `maskedKey` instead. `update` switches a key off and on with `enabled`, which is the reversible alternative to `revoke`, and `delete` only removes a key that has been revoked. Reading needs `keys:read` and every change needs `keys:manage`.

> The client never retries `create` or `rotate`. A retry after a lost response would mint a second key, or invalidate the secret the first attempt returned. `update` and `revoke` are retried like reads, because repeating them leaves the same key, and `delete` is not.

## Never wider than the caller

A key never makes or reaches a key wider than itself: scopes, role, expiry, mode and send scope all have to sit inside the calling key, or the call is refused with 403 `beyond_caller_authority`. A key narrowed to some domains or addresses only sees the keys inside its own send scope. `rotate` on the calling key also works with `keys:write`, like `me.rotate()`.

> Step-up verification cannot apply to a call made with a key, so `keys:manage` is a credential that makes credentials. Give it only to automation that provisions keys, give that key a role, a send scope and an expiry, and watch `list_workspace_activity`, where everything it does is recorded against it.

## Request log and activity

**key_logs.py**

```
from datetime import datetime, timedelta, timezone

from openemail import openemail

failures = openemail.keys.list_requests(
    '4c1b257a66287fd113bd89d0',
    failed_only=True,
    since=datetime.now(timezone.utc) - timedelta(days=1),
)
for request in failures['items']:
    print(request['status'], request['method'], request['path'])

for change in openemail.keys.iterate_workspace_activity():
    actor = change['actor']
    print(change['keyName'], change['type'], actor['label'] if actor else None)
```

`list_requests` and `list_activity` read one key, `list_workspace_requests` and `list_workspace_activity` read every key or the ones `key_ids` names, and each has a `list_all_…` and an `iterate_…` beside it. They take `since` and `until`, and the request readers also take `failed_only`.

> `since` and `until` take a `datetime` or an ISO 8601 string. A `datetime` is sent in UTC, and a naive one is read as local time first.

## Reference

- [`keys.list()`](https://openemail.uk/docs/python/reference/keys#list): full reference
- [`keys.list_all()`](https://openemail.uk/docs/python/reference/keys#listAll): full reference
- [`keys.iterate()`](https://openemail.uk/docs/python/reference/keys#iterate): full reference
- [`keys.get()`](https://openemail.uk/docs/python/reference/keys#get): full reference
- [`keys.create()`](https://openemail.uk/docs/python/reference/keys#create): full reference
- [`keys.update()`](https://openemail.uk/docs/python/reference/keys#update): full reference
- [`keys.delete()`](https://openemail.uk/docs/python/reference/keys#delete): full reference
- [`keys.rotate()`](https://openemail.uk/docs/python/reference/keys#rotate): full reference
- [`keys.revoke()`](https://openemail.uk/docs/python/reference/keys#revoke): full reference
- [`keys.list_requests()`](https://openemail.uk/docs/python/reference/keys#listRequests): full reference
- [`keys.list_all_requests()`](https://openemail.uk/docs/python/reference/keys#listAllRequests): full reference
- [`keys.iterate_requests()`](https://openemail.uk/docs/python/reference/keys#iterateRequests): full reference
- [`keys.list_activity()`](https://openemail.uk/docs/python/reference/keys#listActivity): full reference
- [`keys.list_all_activity()`](https://openemail.uk/docs/python/reference/keys#listAllActivity): full reference
- [`keys.iterate_activity()`](https://openemail.uk/docs/python/reference/keys#iterateActivity): full reference
- [`keys.list_workspace_requests()`](https://openemail.uk/docs/python/reference/keys#listWorkspaceRequests): full reference
- [`keys.list_all_workspace_requests()`](https://openemail.uk/docs/python/reference/keys#listAllWorkspaceRequests): full reference
- [`keys.iterate_workspace_requests()`](https://openemail.uk/docs/python/reference/keys#iterateWorkspaceRequests): full reference
- [`keys.list_workspace_activity()`](https://openemail.uk/docs/python/reference/keys#listWorkspaceActivity): full reference
- [`keys.list_all_workspace_activity()`](https://openemail.uk/docs/python/reference/keys#listAllWorkspaceActivity): full reference
- [`keys.iterate_workspace_activity()`](https://openemail.uk/docs/python/reference/keys#iterateWorkspaceActivity): full reference
