---
title: "API Keys"
description: "Create, list, update and delete MachineMail API keys scoped to the whole organization, to one pod or to a single inbox, each with its own permissions."
canonical: "https://machinemail.dev/docs/reference/api/api-keys"
---

# API Keys

> Create, list, update and delete MachineMail API keys scoped to the whole organization, to one pod or to a single inbox, each with its own permissions.

Source: https://machinemail.dev/docs/reference/api/api-keys

| Method   | Path                                           | Summary                                             |
| -------- | ---------------------------------------------- | --------------------------------------------------- |
| `GET`    | `/v0/api-keys`                                 | [List API Keys](#list-api-keys)                     |
| `POST`   | `/v0/api-keys`                                 | [Create API Key](#create-api-key)                   |
| `GET`    | `/v0/api-keys/{api_key_id}`                    | [Get API Key](#get-api-key)                         |
| `PATCH`  | `/v0/api-keys/{api_key_id}`                    | [Update API Key](#update-api-key)                   |
| `DELETE` | `/v0/api-keys/{api_key_id}`                    | [Delete API Key](#delete-api-key)                   |
| `GET`    | `/v0/inboxes/{inbox_id}/api-keys`              | [List API Keys in Inbox](#list-api-keys-in-inbox)   |
| `POST`   | `/v0/inboxes/{inbox_id}/api-keys`              | [Create API Key in Inbox](#create-api-key-in-inbox) |
| `GET`    | `/v0/inboxes/{inbox_id}/api-keys/{api_key_id}` | [Get API Key in Inbox](#get-api-key-in-inbox)       |
| `PATCH`  | `/v0/inboxes/{inbox_id}/api-keys/{api_key_id}` | [Update API Key in Inbox](#update-api-key-in-inbox) |
| `DELETE` | `/v0/inboxes/{inbox_id}/api-keys/{api_key_id}` | [Delete API Key in Inbox](#delete-api-key-in-inbox) |
| `GET`    | `/v0/pods/{pod_id}/api-keys`                   | [List API Keys in Pod](#list-api-keys-in-pod)       |
| `POST`   | `/v0/pods/{pod_id}/api-keys`                   | [Create API Key in Pod](#create-api-key-in-pod)     |
| `GET`    | `/v0/pods/{pod_id}/api-keys/{api_key_id}`      | [Get API Key in Pod](#get-api-key-in-pod)           |
| `PATCH`  | `/v0/pods/{pod_id}/api-keys/{api_key_id}`      | [Update API Key in Pod](#update-api-key-in-pod)     |
| `DELETE` | `/v0/pods/{pod_id}/api-keys/{api_key_id}`      | [Delete API Key in Pod](#delete-api-key-in-pod)     |

## List API Keys

```http
GET /v0/api-keys
```

Keys that are not revoked. Under `/v0/pods/{pod_id}` or `/v0/inboxes/{inbox_id}`, only the keys scoped to that pod or inbox; a pod or inbox credential sees only keys in its own scope.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_read`.

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer" \| "public_key"` |  | Only this credential family. Every key is bearer, so public_key returns an empty list. |
| `limit` | `integer` |  | Maximum number of items to return. Default 50, maximum 500; larger values are capped. |
| `page_token` | `string` |  | next_page_token from the previous page. |
| `ascending` | `boolean` |  | Oldest first. |

#### Response

**200** A page of keys (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` | required | Page size used. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `api_keys` | `BearerApiKey[]` | required | Keys in the route scope that are not revoked, ordered by created_at, newest first unless ascending is set. |

Each item of `api_keys` is a [BearerApiKey](https://machinemail.dev/docs/reference/api/objects#bearerapikey):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer"` | required | Always bearer: MachineMail has no public-key credentials. |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `organization_id` | `string` | required | ID of the organization. |
| `used_at` | `string (date-time)` |  | Time at which the key was last used. |
| `revoked_at` | `string (date-time)` |  | When the key was deleted (revoked). Revoked keys are not listed but can still be fetched by ID. |
| `updated_at` | `string (date-time)` | required | Time at which the name, permissions or revocation last changed. Use does not change it. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/api-keys" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "api_keys": [
    {
      "type": "bearer",
      "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
      "prefix": "mm_live_4f8K",
      "name": "Production",
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
      "inbox_id": "agent@machinemail.dev",
      "permissions": {
        "*": false,
        "inbox_read": false,
        "inbox_create": false,
        "inbox_update": false,
        "inbox_delete": false,
        "thread_read": false,
        "thread_delete": false,
        "message_read": false,
        "message_reply": false,
        "message_send": false,
        "message_update": false,
        "message_delete": false,
        "label_spam_read": false,
        "label_blocked_read": false,
        "label_unauthenticated_read": false,
        "label_trash_read": false,
        "draft_read": false,
        "draft_create": false,
        "draft_update": false,
        "draft_delete": false,
        "draft_send": false,
        "webhook_read": false,
        "webhook_create": false,
        "webhook_update": false,
        "webhook_delete": false,
        "domain_read": false,
        "domain_create": false,
        "domain_update": false,
        "domain_delete": false,
        "list_entry_read": false,
        "list_entry_create": false,
        "list_entry_delete": false,
        "trusted_domain_read": false,
        "trusted_domain_create": false,
        "trusted_domain_update": false,
        "trusted_domain_delete": false,
        "metrics_read": false,
        "api_key_read": false,
        "api_key_create": false,
        "api_key_update": false,
        "api_key_delete": false,
        "pod_read": false,
        "pod_create": false,
        "pod_update": false,
        "pod_delete": false,
        "inbound_raw": false
      },
      "created_at": "2026-10-01T12:00:00.000Z",
      "expires_at": "2026-10-01T12:00:00.000Z",
      "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
      "used_at": "2026-10-01T12:00:00.000Z",
      "revoked_at": "2026-10-01T12:00:00.000Z",
      "updated_at": "2026-10-01T12:00:00.000Z"
    }
  ]
}
```

## Create API Key

```http
POST /v0/api-keys
```

The route sets the scope of the new key: `POST /v0/api-keys` makes an organization key, `POST /v0/pods/{pod_id}/api-keys` a pod key, `POST /v0/inboxes/{inbox_id}/api-keys` an inbox key.

Only organization-scoped credentials (and dashboard sessions) can create keys, and only with permissions they have themselves (`403`). `api_key` is returned once.

`expires_at` makes a key that stops working at that time (`401` after it). A key created with an expiring API key never outlives it.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_create`.

#### Request body

Content type: `application/json`.

The route sets the scope: `/v0/api-keys` makes an organization key, `/v0/pods/{pod_id}/api-keys` a pod key, `/v0/inboxes/{inbox_id}/api-keys` an inbox key.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | Name of the key. Default API key. |
| `permissions` | `object \| null` |  | Permissions of the new key. Omitted or null: full access for the key scope (inbound_raw excepted). |
| `expires_at` | `string (date-time) \| null` |  | When the key stops working, as an RFC 3339 timestamp at least 5 minutes from now (400 otherwise). A date alone or a time without a zone is read as UTC. Omit it or send null for no expiry (a key created with an expiring API key gets that key’s expiry instead). A key created with another API key cannot outlive it: a later time is a 403. An inherited expiry can be under 5 minutes away. It cannot be changed later; create a new key instead. |

#### Response

**201** Created. api\_key is shown once. (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `api_key` | `string` | required | The key itself (mm_live_ and 32 characters). Returned once; only a hash is stored. |

`permissions` is a [ApiKeyPermissions](https://machinemail.dev/docs/reference/api/objects#apikeypermissions):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `*` | `boolean` |  | Every permission except inbound_raw. {"*": true, "inbound_raw": true} is full access plus raw injection. |
| `inbox_read` | `boolean` |  | Read inbox details. |
| `inbox_create` | `boolean` |  | Create new inboxes. |
| `inbox_update` | `boolean` |  | Update inbox settings. |
| `inbox_delete` | `boolean` |  | Delete inboxes. |
| `thread_read` | `boolean` |  | Read threads. |
| `thread_delete` | `boolean` |  | Delete threads. |
| `message_read` | `boolean` |  | Read messages. |
| `message_reply` | `boolean` |  | Reply and reply-all. message_send allows them too. |
| `message_send` | `boolean` |  | Send messages (and reply, reply-all, forward). |
| `message_update` | `boolean` |  | Update message labels. Also required to update threads. |
| `message_delete` | `boolean` |  | Delete messages. |
| `label_spam_read` | `boolean` |  | Access messages labeled spam. |
| `label_blocked_read` | `boolean` |  | Access messages labeled blocked. |
| `label_unauthenticated_read` | `boolean` |  | Access messages labeled unauthenticated. |
| `label_trash_read` | `boolean` |  | Access messages labeled trash. |
| `draft_read` | `boolean` |  | Read drafts. |
| `draft_create` | `boolean` |  | Create drafts. |
| `draft_update` | `boolean` |  | Update drafts. |
| `draft_delete` | `boolean` |  | Delete drafts. |
| `draft_send` | `boolean` |  | Send drafts. |
| `webhook_read` | `boolean` |  | Read webhook configurations. |
| `webhook_create` | `boolean` |  | Create webhooks. |
| `webhook_update` | `boolean` |  | Update webhooks. |
| `webhook_delete` | `boolean` |  | Delete webhooks. |
| `domain_read` | `boolean` |  | Read domain details. |
| `domain_create` | `boolean` |  | Create domains. |
| `domain_update` | `boolean` |  | Update domains. |
| `domain_delete` | `boolean` |  | Delete domains. |
| `list_entry_read` | `boolean` |  | Read list entries and suppressions. |
| `list_entry_create` | `boolean` |  | Create list entries and suppressions. |
| `list_entry_delete` | `boolean` |  | Delete list entries and suppressions. |
| `trusted_domain_read` | `boolean` |  | Read trusted domains and preview new ones. |
| `trusted_domain_create` | `boolean` |  | Add trusted domains. Organization API keys and the dashboard only: pod and inbox keys get 403 even with it. |
| `trusted_domain_update` | `boolean` |  | Change trusted domains. Organization API keys and the dashboard only, as for trusted_domain_create. |
| `trusted_domain_delete` | `boolean` |  | Remove trusted domains. |
| `metrics_read` | `boolean` |  | Read metrics and events. |
| `api_key_read` | `boolean` |  | Read API keys. |
| `api_key_create` | `boolean` |  | Create API keys. |
| `api_key_update` | `boolean` |  | Update API keys. |
| `api_key_delete` | `boolean` |  | Delete API keys. |
| `pod_read` | `boolean` |  | Read pods. |
| `pod_create` | `boolean` |  | Create pods. |
| `pod_update` | `boolean` |  | Update pods. |
| `pod_delete` | `boolean` |  | Delete pods. |
| `inbound_raw` | `boolean` |  | Inject raw mail with POST /inbound/raw. Never implied by an unrestricted key or *: it must be granted by name. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X POST "https://api.machinemail.dev/v0/api-keys" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Production"
}'
```

```json title="Response 201"
{
  "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
  "prefix": "mm_live_4f8K",
  "name": "Production",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "permissions": {
    "*": false,
    "inbox_read": false,
    "inbox_create": false,
    "inbox_update": false,
    "inbox_delete": false,
    "thread_read": false,
    "thread_delete": false,
    "message_read": false,
    "message_reply": false,
    "message_send": false,
    "message_update": false,
    "message_delete": false,
    "label_spam_read": false,
    "label_blocked_read": false,
    "label_unauthenticated_read": false,
    "label_trash_read": false,
    "draft_read": false,
    "draft_create": false,
    "draft_update": false,
    "draft_delete": false,
    "draft_send": false,
    "webhook_read": false,
    "webhook_create": false,
    "webhook_update": false,
    "webhook_delete": false,
    "domain_read": false,
    "domain_create": false,
    "domain_update": false,
    "domain_delete": false,
    "list_entry_read": false,
    "list_entry_create": false,
    "list_entry_delete": false,
    "trusted_domain_read": false,
    "trusted_domain_create": false,
    "trusted_domain_update": false,
    "trusted_domain_delete": false,
    "metrics_read": false,
    "api_key_read": false,
    "api_key_create": false,
    "api_key_update": false,
    "api_key_delete": false,
    "pod_read": false,
    "pod_create": false,
    "pod_update": false,
    "pod_delete": false,
    "inbound_raw": false
  },
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "api_key": "mm_live_4f8Kq2ZbX9mN3pR7tV1wY6cH0jL5sD8a"
}
```

## Get API Key

```http
GET /v0/api-keys/{api_key_id}
```

Also returns revoked keys (with `revoked_at`).

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `api_key_id` | `string` | required | ID of the API key. |

#### Response

**200** The key (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer"` | required | Always bearer: MachineMail has no public-key credentials. |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `organization_id` | `string` | required | ID of the organization. |
| `used_at` | `string (date-time)` |  | Time at which the key was last used. |
| `revoked_at` | `string (date-time)` |  | When the key was deleted (revoked). Revoked keys are not listed but can still be fetched by ID. |
| `updated_at` | `string (date-time)` | required | Time at which the name, permissions or revocation last changed. Use does not change it. |

`permissions` is a [ApiKeyPermissions](https://machinemail.dev/docs/reference/api/objects#apikeypermissions):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `*` | `boolean` |  | Every permission except inbound_raw. {"*": true, "inbound_raw": true} is full access plus raw injection. |
| `inbox_read` | `boolean` |  | Read inbox details. |
| `inbox_create` | `boolean` |  | Create new inboxes. |
| `inbox_update` | `boolean` |  | Update inbox settings. |
| `inbox_delete` | `boolean` |  | Delete inboxes. |
| `thread_read` | `boolean` |  | Read threads. |
| `thread_delete` | `boolean` |  | Delete threads. |
| `message_read` | `boolean` |  | Read messages. |
| `message_reply` | `boolean` |  | Reply and reply-all. message_send allows them too. |
| `message_send` | `boolean` |  | Send messages (and reply, reply-all, forward). |
| `message_update` | `boolean` |  | Update message labels. Also required to update threads. |
| `message_delete` | `boolean` |  | Delete messages. |
| `label_spam_read` | `boolean` |  | Access messages labeled spam. |
| `label_blocked_read` | `boolean` |  | Access messages labeled blocked. |
| `label_unauthenticated_read` | `boolean` |  | Access messages labeled unauthenticated. |
| `label_trash_read` | `boolean` |  | Access messages labeled trash. |
| `draft_read` | `boolean` |  | Read drafts. |
| `draft_create` | `boolean` |  | Create drafts. |
| `draft_update` | `boolean` |  | Update drafts. |
| `draft_delete` | `boolean` |  | Delete drafts. |
| `draft_send` | `boolean` |  | Send drafts. |
| `webhook_read` | `boolean` |  | Read webhook configurations. |
| `webhook_create` | `boolean` |  | Create webhooks. |
| `webhook_update` | `boolean` |  | Update webhooks. |
| `webhook_delete` | `boolean` |  | Delete webhooks. |
| `domain_read` | `boolean` |  | Read domain details. |
| `domain_create` | `boolean` |  | Create domains. |
| `domain_update` | `boolean` |  | Update domains. |
| `domain_delete` | `boolean` |  | Delete domains. |
| `list_entry_read` | `boolean` |  | Read list entries and suppressions. |
| `list_entry_create` | `boolean` |  | Create list entries and suppressions. |
| `list_entry_delete` | `boolean` |  | Delete list entries and suppressions. |
| `trusted_domain_read` | `boolean` |  | Read trusted domains and preview new ones. |
| `trusted_domain_create` | `boolean` |  | Add trusted domains. Organization API keys and the dashboard only: pod and inbox keys get 403 even with it. |
| `trusted_domain_update` | `boolean` |  | Change trusted domains. Organization API keys and the dashboard only, as for trusted_domain_create. |
| `trusted_domain_delete` | `boolean` |  | Remove trusted domains. |
| `metrics_read` | `boolean` |  | Read metrics and events. |
| `api_key_read` | `boolean` |  | Read API keys. |
| `api_key_create` | `boolean` |  | Create API keys. |
| `api_key_update` | `boolean` |  | Update API keys. |
| `api_key_delete` | `boolean` |  | Delete API keys. |
| `pod_read` | `boolean` |  | Read pods. |
| `pod_create` | `boolean` |  | Create pods. |
| `pod_update` | `boolean` |  | Update pods. |
| `pod_delete` | `boolean` |  | Delete pods. |
| `inbound_raw` | `boolean` |  | Inject raw mail with POST /inbound/raw. Never implied by an unrestricted key or *: it must be granted by name. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/api-keys/key_01k6x3e8mr0000h4r0ty6c9k1n" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "type": "bearer",
  "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
  "prefix": "mm_live_4f8K",
  "name": "Production",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "permissions": {
    "*": false,
    "inbox_read": false,
    "inbox_create": false,
    "inbox_update": false,
    "inbox_delete": false,
    "thread_read": false,
    "thread_delete": false,
    "message_read": false,
    "message_reply": false,
    "message_send": false,
    "message_update": false,
    "message_delete": false,
    "label_spam_read": false,
    "label_blocked_read": false,
    "label_unauthenticated_read": false,
    "label_trash_read": false,
    "draft_read": false,
    "draft_create": false,
    "draft_update": false,
    "draft_delete": false,
    "draft_send": false,
    "webhook_read": false,
    "webhook_create": false,
    "webhook_update": false,
    "webhook_delete": false,
    "domain_read": false,
    "domain_create": false,
    "domain_update": false,
    "domain_delete": false,
    "list_entry_read": false,
    "list_entry_create": false,
    "list_entry_delete": false,
    "trusted_domain_read": false,
    "trusted_domain_create": false,
    "trusted_domain_update": false,
    "trusted_domain_delete": false,
    "metrics_read": false,
    "api_key_read": false,
    "api_key_create": false,
    "api_key_update": false,
    "api_key_delete": false,
    "pod_read": false,
    "pod_create": false,
    "pod_update": false,
    "pod_delete": false,
    "inbound_raw": false
  },
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "used_at": "2026-10-01T12:00:00.000Z",
  "revoked_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}
```

## Update API Key

```http
PATCH /v0/api-keys/{api_key_id}
```

Rename a key or replace its permissions. Only organization-scoped credentials (and dashboard sessions) can update keys, and cannot grant permissions they lack (`403`).

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_update`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `api_key_id` | `string` | required | ID of the API key. |

#### Request body

Content type: `application/json`.

Fields you omit are unchanged. `permissions` replaces the permission object in full. Scope and expiry cannot be changed.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | New name. A blank name is ignored. |
| `permissions` | `object` |  | New permissions; replaces the permission object in full. |

#### Response

**200** The updated key (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer"` | required | Always bearer: MachineMail has no public-key credentials. |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `organization_id` | `string` | required | ID of the organization. |
| `used_at` | `string (date-time)` |  | Time at which the key was last used. |
| `revoked_at` | `string (date-time)` |  | When the key was deleted (revoked). Revoked keys are not listed but can still be fetched by ID. |
| `updated_at` | `string (date-time)` | required | Time at which the name, permissions or revocation last changed. Use does not change it. |

`permissions` is a [ApiKeyPermissions](https://machinemail.dev/docs/reference/api/objects#apikeypermissions):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `*` | `boolean` |  | Every permission except inbound_raw. {"*": true, "inbound_raw": true} is full access plus raw injection. |
| `inbox_read` | `boolean` |  | Read inbox details. |
| `inbox_create` | `boolean` |  | Create new inboxes. |
| `inbox_update` | `boolean` |  | Update inbox settings. |
| `inbox_delete` | `boolean` |  | Delete inboxes. |
| `thread_read` | `boolean` |  | Read threads. |
| `thread_delete` | `boolean` |  | Delete threads. |
| `message_read` | `boolean` |  | Read messages. |
| `message_reply` | `boolean` |  | Reply and reply-all. message_send allows them too. |
| `message_send` | `boolean` |  | Send messages (and reply, reply-all, forward). |
| `message_update` | `boolean` |  | Update message labels. Also required to update threads. |
| `message_delete` | `boolean` |  | Delete messages. |
| `label_spam_read` | `boolean` |  | Access messages labeled spam. |
| `label_blocked_read` | `boolean` |  | Access messages labeled blocked. |
| `label_unauthenticated_read` | `boolean` |  | Access messages labeled unauthenticated. |
| `label_trash_read` | `boolean` |  | Access messages labeled trash. |
| `draft_read` | `boolean` |  | Read drafts. |
| `draft_create` | `boolean` |  | Create drafts. |
| `draft_update` | `boolean` |  | Update drafts. |
| `draft_delete` | `boolean` |  | Delete drafts. |
| `draft_send` | `boolean` |  | Send drafts. |
| `webhook_read` | `boolean` |  | Read webhook configurations. |
| `webhook_create` | `boolean` |  | Create webhooks. |
| `webhook_update` | `boolean` |  | Update webhooks. |
| `webhook_delete` | `boolean` |  | Delete webhooks. |
| `domain_read` | `boolean` |  | Read domain details. |
| `domain_create` | `boolean` |  | Create domains. |
| `domain_update` | `boolean` |  | Update domains. |
| `domain_delete` | `boolean` |  | Delete domains. |
| `list_entry_read` | `boolean` |  | Read list entries and suppressions. |
| `list_entry_create` | `boolean` |  | Create list entries and suppressions. |
| `list_entry_delete` | `boolean` |  | Delete list entries and suppressions. |
| `trusted_domain_read` | `boolean` |  | Read trusted domains and preview new ones. |
| `trusted_domain_create` | `boolean` |  | Add trusted domains. Organization API keys and the dashboard only: pod and inbox keys get 403 even with it. |
| `trusted_domain_update` | `boolean` |  | Change trusted domains. Organization API keys and the dashboard only, as for trusted_domain_create. |
| `trusted_domain_delete` | `boolean` |  | Remove trusted domains. |
| `metrics_read` | `boolean` |  | Read metrics and events. |
| `api_key_read` | `boolean` |  | Read API keys. |
| `api_key_create` | `boolean` |  | Create API keys. |
| `api_key_update` | `boolean` |  | Update API keys. |
| `api_key_delete` | `boolean` |  | Delete API keys. |
| `pod_read` | `boolean` |  | Read pods. |
| `pod_create` | `boolean` |  | Create pods. |
| `pod_update` | `boolean` |  | Update pods. |
| `pod_delete` | `boolean` |  | Delete pods. |
| `inbound_raw` | `boolean` |  | Inject raw mail with POST /inbound/raw. Never implied by an unrestricted key or *: it must be granted by name. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X PATCH "https://api.machinemail.dev/v0/api-keys/key_01k6x3e8mr0000h4r0ty6c9k1n" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Production"
}'
```

```json title="Response 200"
{
  "type": "bearer",
  "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
  "prefix": "mm_live_4f8K",
  "name": "Production",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "permissions": {
    "*": false,
    "inbox_read": false,
    "inbox_create": false,
    "inbox_update": false,
    "inbox_delete": false,
    "thread_read": false,
    "thread_delete": false,
    "message_read": false,
    "message_reply": false,
    "message_send": false,
    "message_update": false,
    "message_delete": false,
    "label_spam_read": false,
    "label_blocked_read": false,
    "label_unauthenticated_read": false,
    "label_trash_read": false,
    "draft_read": false,
    "draft_create": false,
    "draft_update": false,
    "draft_delete": false,
    "draft_send": false,
    "webhook_read": false,
    "webhook_create": false,
    "webhook_update": false,
    "webhook_delete": false,
    "domain_read": false,
    "domain_create": false,
    "domain_update": false,
    "domain_delete": false,
    "list_entry_read": false,
    "list_entry_create": false,
    "list_entry_delete": false,
    "trusted_domain_read": false,
    "trusted_domain_create": false,
    "trusted_domain_update": false,
    "trusted_domain_delete": false,
    "metrics_read": false,
    "api_key_read": false,
    "api_key_create": false,
    "api_key_update": false,
    "api_key_delete": false,
    "pod_read": false,
    "pod_create": false,
    "pod_update": false,
    "pod_delete": false,
    "inbound_raw": false
  },
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "used_at": "2026-10-01T12:00:00.000Z",
  "revoked_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}
```

## Delete API Key

```http
DELETE /v0/api-keys/{api_key_id}
```

Revokes the key: it stops working immediately. The record is kept (with `revoked_at`) and no longer listed.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_delete`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `api_key_id` | `string` | required | ID of the API key. |

#### Response

**200** Revoked (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X DELETE "https://api.machinemail.dev/v0/api-keys/key_01k6x3e8mr0000h4r0ty6c9k1n" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "ok": true
}
```

## List API Keys in Inbox

```http
GET /v0/inboxes/{inbox_id}/api-keys
```

Same as List API Keys (`GET /v0/api-keys`), limited to the inbox.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer" \| "public_key"` |  | Only this credential family. Every key is bearer, so public_key returns an empty list. |
| `limit` | `integer` |  | Maximum number of items to return. Default 50, maximum 500; larger values are capped. |
| `page_token` | `string` |  | next_page_token from the previous page. |
| `ascending` | `boolean` |  | Oldest first. |

#### Response

**200** A page of keys (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` | required | Page size used. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `api_keys` | `BearerApiKey[]` | required | Keys in the route scope that are not revoked, ordered by created_at, newest first unless ascending is set. |

Each item of `api_keys` is a [BearerApiKey](https://machinemail.dev/docs/reference/api/objects#bearerapikey):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer"` | required | Always bearer: MachineMail has no public-key credentials. |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `organization_id` | `string` | required | ID of the organization. |
| `used_at` | `string (date-time)` |  | Time at which the key was last used. |
| `revoked_at` | `string (date-time)` |  | When the key was deleted (revoked). Revoked keys are not listed but can still be fetched by ID. |
| `updated_at` | `string (date-time)` | required | Time at which the name, permissions or revocation last changed. Use does not change it. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/api-keys" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "api_keys": [
    {
      "type": "bearer",
      "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
      "prefix": "mm_live_4f8K",
      "name": "Production",
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
      "inbox_id": "agent@machinemail.dev",
      "permissions": {
        "*": false,
        "inbox_read": false,
        "inbox_create": false,
        "inbox_update": false,
        "inbox_delete": false,
        "thread_read": false,
        "thread_delete": false,
        "message_read": false,
        "message_reply": false,
        "message_send": false,
        "message_update": false,
        "message_delete": false,
        "label_spam_read": false,
        "label_blocked_read": false,
        "label_unauthenticated_read": false,
        "label_trash_read": false,
        "draft_read": false,
        "draft_create": false,
        "draft_update": false,
        "draft_delete": false,
        "draft_send": false,
        "webhook_read": false,
        "webhook_create": false,
        "webhook_update": false,
        "webhook_delete": false,
        "domain_read": false,
        "domain_create": false,
        "domain_update": false,
        "domain_delete": false,
        "list_entry_read": false,
        "list_entry_create": false,
        "list_entry_delete": false,
        "trusted_domain_read": false,
        "trusted_domain_create": false,
        "trusted_domain_update": false,
        "trusted_domain_delete": false,
        "metrics_read": false,
        "api_key_read": false,
        "api_key_create": false,
        "api_key_update": false,
        "api_key_delete": false,
        "pod_read": false,
        "pod_create": false,
        "pod_update": false,
        "pod_delete": false,
        "inbound_raw": false
      },
      "created_at": "2026-10-01T12:00:00.000Z",
      "expires_at": "2026-10-01T12:00:00.000Z",
      "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
      "used_at": "2026-10-01T12:00:00.000Z",
      "revoked_at": "2026-10-01T12:00:00.000Z",
      "updated_at": "2026-10-01T12:00:00.000Z"
    }
  ]
}
```

## Create API Key in Inbox

```http
POST /v0/inboxes/{inbox_id}/api-keys
```

Same as Create API Key (`POST /v0/api-keys`), limited to the inbox.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_create`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |

#### Request body

Content type: `application/json`.

The route sets the scope: `/v0/api-keys` makes an organization key, `/v0/pods/{pod_id}/api-keys` a pod key, `/v0/inboxes/{inbox_id}/api-keys` an inbox key.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | Name of the key. Default API key. |
| `permissions` | `object \| null` |  | Permissions of the new key. Omitted or null: full access for the key scope (inbound_raw excepted). |
| `expires_at` | `string (date-time) \| null` |  | When the key stops working, as an RFC 3339 timestamp at least 5 minutes from now (400 otherwise). A date alone or a time without a zone is read as UTC. Omit it or send null for no expiry (a key created with an expiring API key gets that key’s expiry instead). A key created with another API key cannot outlive it: a later time is a 403. An inherited expiry can be under 5 minutes away. It cannot be changed later; create a new key instead. |

#### Response

**201** Created. api\_key is shown once. (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `api_key` | `string` | required | The key itself (mm_live_ and 32 characters). Returned once; only a hash is stored. |

`permissions` is a [ApiKeyPermissions](https://machinemail.dev/docs/reference/api/objects#apikeypermissions):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `*` | `boolean` |  | Every permission except inbound_raw. {"*": true, "inbound_raw": true} is full access plus raw injection. |
| `inbox_read` | `boolean` |  | Read inbox details. |
| `inbox_create` | `boolean` |  | Create new inboxes. |
| `inbox_update` | `boolean` |  | Update inbox settings. |
| `inbox_delete` | `boolean` |  | Delete inboxes. |
| `thread_read` | `boolean` |  | Read threads. |
| `thread_delete` | `boolean` |  | Delete threads. |
| `message_read` | `boolean` |  | Read messages. |
| `message_reply` | `boolean` |  | Reply and reply-all. message_send allows them too. |
| `message_send` | `boolean` |  | Send messages (and reply, reply-all, forward). |
| `message_update` | `boolean` |  | Update message labels. Also required to update threads. |
| `message_delete` | `boolean` |  | Delete messages. |
| `label_spam_read` | `boolean` |  | Access messages labeled spam. |
| `label_blocked_read` | `boolean` |  | Access messages labeled blocked. |
| `label_unauthenticated_read` | `boolean` |  | Access messages labeled unauthenticated. |
| `label_trash_read` | `boolean` |  | Access messages labeled trash. |
| `draft_read` | `boolean` |  | Read drafts. |
| `draft_create` | `boolean` |  | Create drafts. |
| `draft_update` | `boolean` |  | Update drafts. |
| `draft_delete` | `boolean` |  | Delete drafts. |
| `draft_send` | `boolean` |  | Send drafts. |
| `webhook_read` | `boolean` |  | Read webhook configurations. |
| `webhook_create` | `boolean` |  | Create webhooks. |
| `webhook_update` | `boolean` |  | Update webhooks. |
| `webhook_delete` | `boolean` |  | Delete webhooks. |
| `domain_read` | `boolean` |  | Read domain details. |
| `domain_create` | `boolean` |  | Create domains. |
| `domain_update` | `boolean` |  | Update domains. |
| `domain_delete` | `boolean` |  | Delete domains. |
| `list_entry_read` | `boolean` |  | Read list entries and suppressions. |
| `list_entry_create` | `boolean` |  | Create list entries and suppressions. |
| `list_entry_delete` | `boolean` |  | Delete list entries and suppressions. |
| `trusted_domain_read` | `boolean` |  | Read trusted domains and preview new ones. |
| `trusted_domain_create` | `boolean` |  | Add trusted domains. Organization API keys and the dashboard only: pod and inbox keys get 403 even with it. |
| `trusted_domain_update` | `boolean` |  | Change trusted domains. Organization API keys and the dashboard only, as for trusted_domain_create. |
| `trusted_domain_delete` | `boolean` |  | Remove trusted domains. |
| `metrics_read` | `boolean` |  | Read metrics and events. |
| `api_key_read` | `boolean` |  | Read API keys. |
| `api_key_create` | `boolean` |  | Create API keys. |
| `api_key_update` | `boolean` |  | Update API keys. |
| `api_key_delete` | `boolean` |  | Delete API keys. |
| `pod_read` | `boolean` |  | Read pods. |
| `pod_create` | `boolean` |  | Create pods. |
| `pod_update` | `boolean` |  | Update pods. |
| `pod_delete` | `boolean` |  | Delete pods. |
| `inbound_raw` | `boolean` |  | Inject raw mail with POST /inbound/raw. Never implied by an unrestricted key or *: it must be granted by name. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X POST "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/api-keys" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Production"
}'
```

```json title="Response 201"
{
  "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
  "prefix": "mm_live_4f8K",
  "name": "Production",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "permissions": {
    "*": false,
    "inbox_read": false,
    "inbox_create": false,
    "inbox_update": false,
    "inbox_delete": false,
    "thread_read": false,
    "thread_delete": false,
    "message_read": false,
    "message_reply": false,
    "message_send": false,
    "message_update": false,
    "message_delete": false,
    "label_spam_read": false,
    "label_blocked_read": false,
    "label_unauthenticated_read": false,
    "label_trash_read": false,
    "draft_read": false,
    "draft_create": false,
    "draft_update": false,
    "draft_delete": false,
    "draft_send": false,
    "webhook_read": false,
    "webhook_create": false,
    "webhook_update": false,
    "webhook_delete": false,
    "domain_read": false,
    "domain_create": false,
    "domain_update": false,
    "domain_delete": false,
    "list_entry_read": false,
    "list_entry_create": false,
    "list_entry_delete": false,
    "trusted_domain_read": false,
    "trusted_domain_create": false,
    "trusted_domain_update": false,
    "trusted_domain_delete": false,
    "metrics_read": false,
    "api_key_read": false,
    "api_key_create": false,
    "api_key_update": false,
    "api_key_delete": false,
    "pod_read": false,
    "pod_create": false,
    "pod_update": false,
    "pod_delete": false,
    "inbound_raw": false
  },
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "api_key": "mm_live_4f8Kq2ZbX9mN3pR7tV1wY6cH0jL5sD8a"
}
```

## Get API Key in Inbox

```http
GET /v0/inboxes/{inbox_id}/api-keys/{api_key_id}
```

Same as Get API Key (`GET /v0/api-keys/{api_key_id}`), limited to the inbox.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `api_key_id` | `string` | required | ID of the API key. |

#### Response

**200** The key (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer"` | required | Always bearer: MachineMail has no public-key credentials. |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `organization_id` | `string` | required | ID of the organization. |
| `used_at` | `string (date-time)` |  | Time at which the key was last used. |
| `revoked_at` | `string (date-time)` |  | When the key was deleted (revoked). Revoked keys are not listed but can still be fetched by ID. |
| `updated_at` | `string (date-time)` | required | Time at which the name, permissions or revocation last changed. Use does not change it. |

`permissions` is a [ApiKeyPermissions](https://machinemail.dev/docs/reference/api/objects#apikeypermissions):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `*` | `boolean` |  | Every permission except inbound_raw. {"*": true, "inbound_raw": true} is full access plus raw injection. |
| `inbox_read` | `boolean` |  | Read inbox details. |
| `inbox_create` | `boolean` |  | Create new inboxes. |
| `inbox_update` | `boolean` |  | Update inbox settings. |
| `inbox_delete` | `boolean` |  | Delete inboxes. |
| `thread_read` | `boolean` |  | Read threads. |
| `thread_delete` | `boolean` |  | Delete threads. |
| `message_read` | `boolean` |  | Read messages. |
| `message_reply` | `boolean` |  | Reply and reply-all. message_send allows them too. |
| `message_send` | `boolean` |  | Send messages (and reply, reply-all, forward). |
| `message_update` | `boolean` |  | Update message labels. Also required to update threads. |
| `message_delete` | `boolean` |  | Delete messages. |
| `label_spam_read` | `boolean` |  | Access messages labeled spam. |
| `label_blocked_read` | `boolean` |  | Access messages labeled blocked. |
| `label_unauthenticated_read` | `boolean` |  | Access messages labeled unauthenticated. |
| `label_trash_read` | `boolean` |  | Access messages labeled trash. |
| `draft_read` | `boolean` |  | Read drafts. |
| `draft_create` | `boolean` |  | Create drafts. |
| `draft_update` | `boolean` |  | Update drafts. |
| `draft_delete` | `boolean` |  | Delete drafts. |
| `draft_send` | `boolean` |  | Send drafts. |
| `webhook_read` | `boolean` |  | Read webhook configurations. |
| `webhook_create` | `boolean` |  | Create webhooks. |
| `webhook_update` | `boolean` |  | Update webhooks. |
| `webhook_delete` | `boolean` |  | Delete webhooks. |
| `domain_read` | `boolean` |  | Read domain details. |
| `domain_create` | `boolean` |  | Create domains. |
| `domain_update` | `boolean` |  | Update domains. |
| `domain_delete` | `boolean` |  | Delete domains. |
| `list_entry_read` | `boolean` |  | Read list entries and suppressions. |
| `list_entry_create` | `boolean` |  | Create list entries and suppressions. |
| `list_entry_delete` | `boolean` |  | Delete list entries and suppressions. |
| `trusted_domain_read` | `boolean` |  | Read trusted domains and preview new ones. |
| `trusted_domain_create` | `boolean` |  | Add trusted domains. Organization API keys and the dashboard only: pod and inbox keys get 403 even with it. |
| `trusted_domain_update` | `boolean` |  | Change trusted domains. Organization API keys and the dashboard only, as for trusted_domain_create. |
| `trusted_domain_delete` | `boolean` |  | Remove trusted domains. |
| `metrics_read` | `boolean` |  | Read metrics and events. |
| `api_key_read` | `boolean` |  | Read API keys. |
| `api_key_create` | `boolean` |  | Create API keys. |
| `api_key_update` | `boolean` |  | Update API keys. |
| `api_key_delete` | `boolean` |  | Delete API keys. |
| `pod_read` | `boolean` |  | Read pods. |
| `pod_create` | `boolean` |  | Create pods. |
| `pod_update` | `boolean` |  | Update pods. |
| `pod_delete` | `boolean` |  | Delete pods. |
| `inbound_raw` | `boolean` |  | Inject raw mail with POST /inbound/raw. Never implied by an unrestricted key or *: it must be granted by name. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/api-keys/key_01k6x3e8mr0000h4r0ty6c9k1n" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "type": "bearer",
  "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
  "prefix": "mm_live_4f8K",
  "name": "Production",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "permissions": {
    "*": false,
    "inbox_read": false,
    "inbox_create": false,
    "inbox_update": false,
    "inbox_delete": false,
    "thread_read": false,
    "thread_delete": false,
    "message_read": false,
    "message_reply": false,
    "message_send": false,
    "message_update": false,
    "message_delete": false,
    "label_spam_read": false,
    "label_blocked_read": false,
    "label_unauthenticated_read": false,
    "label_trash_read": false,
    "draft_read": false,
    "draft_create": false,
    "draft_update": false,
    "draft_delete": false,
    "draft_send": false,
    "webhook_read": false,
    "webhook_create": false,
    "webhook_update": false,
    "webhook_delete": false,
    "domain_read": false,
    "domain_create": false,
    "domain_update": false,
    "domain_delete": false,
    "list_entry_read": false,
    "list_entry_create": false,
    "list_entry_delete": false,
    "trusted_domain_read": false,
    "trusted_domain_create": false,
    "trusted_domain_update": false,
    "trusted_domain_delete": false,
    "metrics_read": false,
    "api_key_read": false,
    "api_key_create": false,
    "api_key_update": false,
    "api_key_delete": false,
    "pod_read": false,
    "pod_create": false,
    "pod_update": false,
    "pod_delete": false,
    "inbound_raw": false
  },
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "used_at": "2026-10-01T12:00:00.000Z",
  "revoked_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}
```

## Update API Key in Inbox

```http
PATCH /v0/inboxes/{inbox_id}/api-keys/{api_key_id}
```

Same as Update API Key (`PATCH /v0/api-keys/{api_key_id}`), limited to the inbox.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_update`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `api_key_id` | `string` | required | ID of the API key. |

#### Request body

Content type: `application/json`.

Fields you omit are unchanged. `permissions` replaces the permission object in full. Scope and expiry cannot be changed.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | New name. A blank name is ignored. |
| `permissions` | `object` |  | New permissions; replaces the permission object in full. |

#### Response

**200** The updated key (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer"` | required | Always bearer: MachineMail has no public-key credentials. |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `organization_id` | `string` | required | ID of the organization. |
| `used_at` | `string (date-time)` |  | Time at which the key was last used. |
| `revoked_at` | `string (date-time)` |  | When the key was deleted (revoked). Revoked keys are not listed but can still be fetched by ID. |
| `updated_at` | `string (date-time)` | required | Time at which the name, permissions or revocation last changed. Use does not change it. |

`permissions` is a [ApiKeyPermissions](https://machinemail.dev/docs/reference/api/objects#apikeypermissions):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `*` | `boolean` |  | Every permission except inbound_raw. {"*": true, "inbound_raw": true} is full access plus raw injection. |
| `inbox_read` | `boolean` |  | Read inbox details. |
| `inbox_create` | `boolean` |  | Create new inboxes. |
| `inbox_update` | `boolean` |  | Update inbox settings. |
| `inbox_delete` | `boolean` |  | Delete inboxes. |
| `thread_read` | `boolean` |  | Read threads. |
| `thread_delete` | `boolean` |  | Delete threads. |
| `message_read` | `boolean` |  | Read messages. |
| `message_reply` | `boolean` |  | Reply and reply-all. message_send allows them too. |
| `message_send` | `boolean` |  | Send messages (and reply, reply-all, forward). |
| `message_update` | `boolean` |  | Update message labels. Also required to update threads. |
| `message_delete` | `boolean` |  | Delete messages. |
| `label_spam_read` | `boolean` |  | Access messages labeled spam. |
| `label_blocked_read` | `boolean` |  | Access messages labeled blocked. |
| `label_unauthenticated_read` | `boolean` |  | Access messages labeled unauthenticated. |
| `label_trash_read` | `boolean` |  | Access messages labeled trash. |
| `draft_read` | `boolean` |  | Read drafts. |
| `draft_create` | `boolean` |  | Create drafts. |
| `draft_update` | `boolean` |  | Update drafts. |
| `draft_delete` | `boolean` |  | Delete drafts. |
| `draft_send` | `boolean` |  | Send drafts. |
| `webhook_read` | `boolean` |  | Read webhook configurations. |
| `webhook_create` | `boolean` |  | Create webhooks. |
| `webhook_update` | `boolean` |  | Update webhooks. |
| `webhook_delete` | `boolean` |  | Delete webhooks. |
| `domain_read` | `boolean` |  | Read domain details. |
| `domain_create` | `boolean` |  | Create domains. |
| `domain_update` | `boolean` |  | Update domains. |
| `domain_delete` | `boolean` |  | Delete domains. |
| `list_entry_read` | `boolean` |  | Read list entries and suppressions. |
| `list_entry_create` | `boolean` |  | Create list entries and suppressions. |
| `list_entry_delete` | `boolean` |  | Delete list entries and suppressions. |
| `trusted_domain_read` | `boolean` |  | Read trusted domains and preview new ones. |
| `trusted_domain_create` | `boolean` |  | Add trusted domains. Organization API keys and the dashboard only: pod and inbox keys get 403 even with it. |
| `trusted_domain_update` | `boolean` |  | Change trusted domains. Organization API keys and the dashboard only, as for trusted_domain_create. |
| `trusted_domain_delete` | `boolean` |  | Remove trusted domains. |
| `metrics_read` | `boolean` |  | Read metrics and events. |
| `api_key_read` | `boolean` |  | Read API keys. |
| `api_key_create` | `boolean` |  | Create API keys. |
| `api_key_update` | `boolean` |  | Update API keys. |
| `api_key_delete` | `boolean` |  | Delete API keys. |
| `pod_read` | `boolean` |  | Read pods. |
| `pod_create` | `boolean` |  | Create pods. |
| `pod_update` | `boolean` |  | Update pods. |
| `pod_delete` | `boolean` |  | Delete pods. |
| `inbound_raw` | `boolean` |  | Inject raw mail with POST /inbound/raw. Never implied by an unrestricted key or *: it must be granted by name. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X PATCH "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/api-keys/key_01k6x3e8mr0000h4r0ty6c9k1n" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Production"
}'
```

```json title="Response 200"
{
  "type": "bearer",
  "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
  "prefix": "mm_live_4f8K",
  "name": "Production",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "permissions": {
    "*": false,
    "inbox_read": false,
    "inbox_create": false,
    "inbox_update": false,
    "inbox_delete": false,
    "thread_read": false,
    "thread_delete": false,
    "message_read": false,
    "message_reply": false,
    "message_send": false,
    "message_update": false,
    "message_delete": false,
    "label_spam_read": false,
    "label_blocked_read": false,
    "label_unauthenticated_read": false,
    "label_trash_read": false,
    "draft_read": false,
    "draft_create": false,
    "draft_update": false,
    "draft_delete": false,
    "draft_send": false,
    "webhook_read": false,
    "webhook_create": false,
    "webhook_update": false,
    "webhook_delete": false,
    "domain_read": false,
    "domain_create": false,
    "domain_update": false,
    "domain_delete": false,
    "list_entry_read": false,
    "list_entry_create": false,
    "list_entry_delete": false,
    "trusted_domain_read": false,
    "trusted_domain_create": false,
    "trusted_domain_update": false,
    "trusted_domain_delete": false,
    "metrics_read": false,
    "api_key_read": false,
    "api_key_create": false,
    "api_key_update": false,
    "api_key_delete": false,
    "pod_read": false,
    "pod_create": false,
    "pod_update": false,
    "pod_delete": false,
    "inbound_raw": false
  },
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "used_at": "2026-10-01T12:00:00.000Z",
  "revoked_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}
```

## Delete API Key in Inbox

```http
DELETE /v0/inboxes/{inbox_id}/api-keys/{api_key_id}
```

Same as Delete API Key (`DELETE /v0/api-keys/{api_key_id}`), limited to the inbox.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_delete`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `api_key_id` | `string` | required | ID of the API key. |

#### Response

**200** Revoked (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X DELETE "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/api-keys/key_01k6x3e8mr0000h4r0ty6c9k1n" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "ok": true
}
```

## List API Keys in Pod

```http
GET /v0/pods/{pod_id}/api-keys
```

Same as List API Keys (`GET /v0/api-keys`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer" \| "public_key"` |  | Only this credential family. Every key is bearer, so public_key returns an empty list. |
| `limit` | `integer` |  | Maximum number of items to return. Default 50, maximum 500; larger values are capped. |
| `page_token` | `string` |  | next_page_token from the previous page. |
| `ascending` | `boolean` |  | Oldest first. |

#### Response

**200** A page of keys (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` | required | Page size used. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `api_keys` | `BearerApiKey[]` | required | Keys in the route scope that are not revoked, ordered by created_at, newest first unless ascending is set. |

Each item of `api_keys` is a [BearerApiKey](https://machinemail.dev/docs/reference/api/objects#bearerapikey):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer"` | required | Always bearer: MachineMail has no public-key credentials. |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `organization_id` | `string` | required | ID of the organization. |
| `used_at` | `string (date-time)` |  | Time at which the key was last used. |
| `revoked_at` | `string (date-time)` |  | When the key was deleted (revoked). Revoked keys are not listed but can still be fetched by ID. |
| `updated_at` | `string (date-time)` | required | Time at which the name, permissions or revocation last changed. Use does not change it. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/api-keys" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "api_keys": [
    {
      "type": "bearer",
      "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
      "prefix": "mm_live_4f8K",
      "name": "Production",
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
      "inbox_id": "agent@machinemail.dev",
      "permissions": {
        "*": false,
        "inbox_read": false,
        "inbox_create": false,
        "inbox_update": false,
        "inbox_delete": false,
        "thread_read": false,
        "thread_delete": false,
        "message_read": false,
        "message_reply": false,
        "message_send": false,
        "message_update": false,
        "message_delete": false,
        "label_spam_read": false,
        "label_blocked_read": false,
        "label_unauthenticated_read": false,
        "label_trash_read": false,
        "draft_read": false,
        "draft_create": false,
        "draft_update": false,
        "draft_delete": false,
        "draft_send": false,
        "webhook_read": false,
        "webhook_create": false,
        "webhook_update": false,
        "webhook_delete": false,
        "domain_read": false,
        "domain_create": false,
        "domain_update": false,
        "domain_delete": false,
        "list_entry_read": false,
        "list_entry_create": false,
        "list_entry_delete": false,
        "trusted_domain_read": false,
        "trusted_domain_create": false,
        "trusted_domain_update": false,
        "trusted_domain_delete": false,
        "metrics_read": false,
        "api_key_read": false,
        "api_key_create": false,
        "api_key_update": false,
        "api_key_delete": false,
        "pod_read": false,
        "pod_create": false,
        "pod_update": false,
        "pod_delete": false,
        "inbound_raw": false
      },
      "created_at": "2026-10-01T12:00:00.000Z",
      "expires_at": "2026-10-01T12:00:00.000Z",
      "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
      "used_at": "2026-10-01T12:00:00.000Z",
      "revoked_at": "2026-10-01T12:00:00.000Z",
      "updated_at": "2026-10-01T12:00:00.000Z"
    }
  ]
}
```

## Create API Key in Pod

```http
POST /v0/pods/{pod_id}/api-keys
```

Same as Create API Key (`POST /v0/api-keys`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_create`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |

#### Request body

Content type: `application/json`.

The route sets the scope: `/v0/api-keys` makes an organization key, `/v0/pods/{pod_id}/api-keys` a pod key, `/v0/inboxes/{inbox_id}/api-keys` an inbox key.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | Name of the key. Default API key. |
| `permissions` | `object \| null` |  | Permissions of the new key. Omitted or null: full access for the key scope (inbound_raw excepted). |
| `expires_at` | `string (date-time) \| null` |  | When the key stops working, as an RFC 3339 timestamp at least 5 minutes from now (400 otherwise). A date alone or a time without a zone is read as UTC. Omit it or send null for no expiry (a key created with an expiring API key gets that key’s expiry instead). A key created with another API key cannot outlive it: a later time is a 403. An inherited expiry can be under 5 minutes away. It cannot be changed later; create a new key instead. |

#### Response

**201** Created. api\_key is shown once. (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `api_key` | `string` | required | The key itself (mm_live_ and 32 characters). Returned once; only a hash is stored. |

`permissions` is a [ApiKeyPermissions](https://machinemail.dev/docs/reference/api/objects#apikeypermissions):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `*` | `boolean` |  | Every permission except inbound_raw. {"*": true, "inbound_raw": true} is full access plus raw injection. |
| `inbox_read` | `boolean` |  | Read inbox details. |
| `inbox_create` | `boolean` |  | Create new inboxes. |
| `inbox_update` | `boolean` |  | Update inbox settings. |
| `inbox_delete` | `boolean` |  | Delete inboxes. |
| `thread_read` | `boolean` |  | Read threads. |
| `thread_delete` | `boolean` |  | Delete threads. |
| `message_read` | `boolean` |  | Read messages. |
| `message_reply` | `boolean` |  | Reply and reply-all. message_send allows them too. |
| `message_send` | `boolean` |  | Send messages (and reply, reply-all, forward). |
| `message_update` | `boolean` |  | Update message labels. Also required to update threads. |
| `message_delete` | `boolean` |  | Delete messages. |
| `label_spam_read` | `boolean` |  | Access messages labeled spam. |
| `label_blocked_read` | `boolean` |  | Access messages labeled blocked. |
| `label_unauthenticated_read` | `boolean` |  | Access messages labeled unauthenticated. |
| `label_trash_read` | `boolean` |  | Access messages labeled trash. |
| `draft_read` | `boolean` |  | Read drafts. |
| `draft_create` | `boolean` |  | Create drafts. |
| `draft_update` | `boolean` |  | Update drafts. |
| `draft_delete` | `boolean` |  | Delete drafts. |
| `draft_send` | `boolean` |  | Send drafts. |
| `webhook_read` | `boolean` |  | Read webhook configurations. |
| `webhook_create` | `boolean` |  | Create webhooks. |
| `webhook_update` | `boolean` |  | Update webhooks. |
| `webhook_delete` | `boolean` |  | Delete webhooks. |
| `domain_read` | `boolean` |  | Read domain details. |
| `domain_create` | `boolean` |  | Create domains. |
| `domain_update` | `boolean` |  | Update domains. |
| `domain_delete` | `boolean` |  | Delete domains. |
| `list_entry_read` | `boolean` |  | Read list entries and suppressions. |
| `list_entry_create` | `boolean` |  | Create list entries and suppressions. |
| `list_entry_delete` | `boolean` |  | Delete list entries and suppressions. |
| `trusted_domain_read` | `boolean` |  | Read trusted domains and preview new ones. |
| `trusted_domain_create` | `boolean` |  | Add trusted domains. Organization API keys and the dashboard only: pod and inbox keys get 403 even with it. |
| `trusted_domain_update` | `boolean` |  | Change trusted domains. Organization API keys and the dashboard only, as for trusted_domain_create. |
| `trusted_domain_delete` | `boolean` |  | Remove trusted domains. |
| `metrics_read` | `boolean` |  | Read metrics and events. |
| `api_key_read` | `boolean` |  | Read API keys. |
| `api_key_create` | `boolean` |  | Create API keys. |
| `api_key_update` | `boolean` |  | Update API keys. |
| `api_key_delete` | `boolean` |  | Delete API keys. |
| `pod_read` | `boolean` |  | Read pods. |
| `pod_create` | `boolean` |  | Create pods. |
| `pod_update` | `boolean` |  | Update pods. |
| `pod_delete` | `boolean` |  | Delete pods. |
| `inbound_raw` | `boolean` |  | Inject raw mail with POST /inbound/raw. Never implied by an unrestricted key or *: it must be granted by name. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X POST "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/api-keys" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Production"
}'
```

```json title="Response 201"
{
  "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
  "prefix": "mm_live_4f8K",
  "name": "Production",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "permissions": {
    "*": false,
    "inbox_read": false,
    "inbox_create": false,
    "inbox_update": false,
    "inbox_delete": false,
    "thread_read": false,
    "thread_delete": false,
    "message_read": false,
    "message_reply": false,
    "message_send": false,
    "message_update": false,
    "message_delete": false,
    "label_spam_read": false,
    "label_blocked_read": false,
    "label_unauthenticated_read": false,
    "label_trash_read": false,
    "draft_read": false,
    "draft_create": false,
    "draft_update": false,
    "draft_delete": false,
    "draft_send": false,
    "webhook_read": false,
    "webhook_create": false,
    "webhook_update": false,
    "webhook_delete": false,
    "domain_read": false,
    "domain_create": false,
    "domain_update": false,
    "domain_delete": false,
    "list_entry_read": false,
    "list_entry_create": false,
    "list_entry_delete": false,
    "trusted_domain_read": false,
    "trusted_domain_create": false,
    "trusted_domain_update": false,
    "trusted_domain_delete": false,
    "metrics_read": false,
    "api_key_read": false,
    "api_key_create": false,
    "api_key_update": false,
    "api_key_delete": false,
    "pod_read": false,
    "pod_create": false,
    "pod_update": false,
    "pod_delete": false,
    "inbound_raw": false
  },
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "api_key": "mm_live_4f8Kq2ZbX9mN3pR7tV1wY6cH0jL5sD8a"
}
```

## Get API Key in Pod

```http
GET /v0/pods/{pod_id}/api-keys/{api_key_id}
```

Same as Get API Key (`GET /v0/api-keys/{api_key_id}`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `api_key_id` | `string` | required | ID of the API key. |

#### Response

**200** The key (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer"` | required | Always bearer: MachineMail has no public-key credentials. |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `organization_id` | `string` | required | ID of the organization. |
| `used_at` | `string (date-time)` |  | Time at which the key was last used. |
| `revoked_at` | `string (date-time)` |  | When the key was deleted (revoked). Revoked keys are not listed but can still be fetched by ID. |
| `updated_at` | `string (date-time)` | required | Time at which the name, permissions or revocation last changed. Use does not change it. |

`permissions` is a [ApiKeyPermissions](https://machinemail.dev/docs/reference/api/objects#apikeypermissions):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `*` | `boolean` |  | Every permission except inbound_raw. {"*": true, "inbound_raw": true} is full access plus raw injection. |
| `inbox_read` | `boolean` |  | Read inbox details. |
| `inbox_create` | `boolean` |  | Create new inboxes. |
| `inbox_update` | `boolean` |  | Update inbox settings. |
| `inbox_delete` | `boolean` |  | Delete inboxes. |
| `thread_read` | `boolean` |  | Read threads. |
| `thread_delete` | `boolean` |  | Delete threads. |
| `message_read` | `boolean` |  | Read messages. |
| `message_reply` | `boolean` |  | Reply and reply-all. message_send allows them too. |
| `message_send` | `boolean` |  | Send messages (and reply, reply-all, forward). |
| `message_update` | `boolean` |  | Update message labels. Also required to update threads. |
| `message_delete` | `boolean` |  | Delete messages. |
| `label_spam_read` | `boolean` |  | Access messages labeled spam. |
| `label_blocked_read` | `boolean` |  | Access messages labeled blocked. |
| `label_unauthenticated_read` | `boolean` |  | Access messages labeled unauthenticated. |
| `label_trash_read` | `boolean` |  | Access messages labeled trash. |
| `draft_read` | `boolean` |  | Read drafts. |
| `draft_create` | `boolean` |  | Create drafts. |
| `draft_update` | `boolean` |  | Update drafts. |
| `draft_delete` | `boolean` |  | Delete drafts. |
| `draft_send` | `boolean` |  | Send drafts. |
| `webhook_read` | `boolean` |  | Read webhook configurations. |
| `webhook_create` | `boolean` |  | Create webhooks. |
| `webhook_update` | `boolean` |  | Update webhooks. |
| `webhook_delete` | `boolean` |  | Delete webhooks. |
| `domain_read` | `boolean` |  | Read domain details. |
| `domain_create` | `boolean` |  | Create domains. |
| `domain_update` | `boolean` |  | Update domains. |
| `domain_delete` | `boolean` |  | Delete domains. |
| `list_entry_read` | `boolean` |  | Read list entries and suppressions. |
| `list_entry_create` | `boolean` |  | Create list entries and suppressions. |
| `list_entry_delete` | `boolean` |  | Delete list entries and suppressions. |
| `trusted_domain_read` | `boolean` |  | Read trusted domains and preview new ones. |
| `trusted_domain_create` | `boolean` |  | Add trusted domains. Organization API keys and the dashboard only: pod and inbox keys get 403 even with it. |
| `trusted_domain_update` | `boolean` |  | Change trusted domains. Organization API keys and the dashboard only, as for trusted_domain_create. |
| `trusted_domain_delete` | `boolean` |  | Remove trusted domains. |
| `metrics_read` | `boolean` |  | Read metrics and events. |
| `api_key_read` | `boolean` |  | Read API keys. |
| `api_key_create` | `boolean` |  | Create API keys. |
| `api_key_update` | `boolean` |  | Update API keys. |
| `api_key_delete` | `boolean` |  | Delete API keys. |
| `pod_read` | `boolean` |  | Read pods. |
| `pod_create` | `boolean` |  | Create pods. |
| `pod_update` | `boolean` |  | Update pods. |
| `pod_delete` | `boolean` |  | Delete pods. |
| `inbound_raw` | `boolean` |  | Inject raw mail with POST /inbound/raw. Never implied by an unrestricted key or *: it must be granted by name. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/api-keys/key_01k6x3e8mr0000h4r0ty6c9k1n" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "type": "bearer",
  "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
  "prefix": "mm_live_4f8K",
  "name": "Production",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "permissions": {
    "*": false,
    "inbox_read": false,
    "inbox_create": false,
    "inbox_update": false,
    "inbox_delete": false,
    "thread_read": false,
    "thread_delete": false,
    "message_read": false,
    "message_reply": false,
    "message_send": false,
    "message_update": false,
    "message_delete": false,
    "label_spam_read": false,
    "label_blocked_read": false,
    "label_unauthenticated_read": false,
    "label_trash_read": false,
    "draft_read": false,
    "draft_create": false,
    "draft_update": false,
    "draft_delete": false,
    "draft_send": false,
    "webhook_read": false,
    "webhook_create": false,
    "webhook_update": false,
    "webhook_delete": false,
    "domain_read": false,
    "domain_create": false,
    "domain_update": false,
    "domain_delete": false,
    "list_entry_read": false,
    "list_entry_create": false,
    "list_entry_delete": false,
    "trusted_domain_read": false,
    "trusted_domain_create": false,
    "trusted_domain_update": false,
    "trusted_domain_delete": false,
    "metrics_read": false,
    "api_key_read": false,
    "api_key_create": false,
    "api_key_update": false,
    "api_key_delete": false,
    "pod_read": false,
    "pod_create": false,
    "pod_update": false,
    "pod_delete": false,
    "inbound_raw": false
  },
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "used_at": "2026-10-01T12:00:00.000Z",
  "revoked_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}
```

## Update API Key in Pod

```http
PATCH /v0/pods/{pod_id}/api-keys/{api_key_id}
```

Same as Update API Key (`PATCH /v0/api-keys/{api_key_id}`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_update`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `api_key_id` | `string` | required | ID of the API key. |

#### Request body

Content type: `application/json`.

Fields you omit are unchanged. `permissions` replaces the permission object in full. Scope and expiry cannot be changed.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | New name. A blank name is ignored. |
| `permissions` | `object` |  | New permissions; replaces the permission object in full. |

#### Response

**200** The updated key (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `type` | `"bearer"` | required | Always bearer: MachineMail has no public-key credentials. |
| `api_key_id` | `string` | required | ID of the API key. |
| `prefix` | `string` | required | First 12 characters of the key (mm_live_ and 4 more), safe to display. |
| `name` | `string` | required | Name of the key. |
| `pod_id` | `string` |  | Pod the key is scoped to: it can only access resources in this pod. |
| `inbox_id` | `string` |  | Inbox the key is scoped to: it can only access this inbox. |
| `permissions` | `ApiKeyPermissions` |  | Permissions of the key, as { "<name>": true }. Only names set to true are granted; false is the same as leaving the name out. An omitted (or null) permission object means full access for the key scope, like {"*": true}; neither includes inbound_raw, which must be granted by name. A key can only be given permissions its creator has. Another name set to true (for example AgentMail’s calendar_*, app_* or account_update, which MachineMail does not support) is a 400. |
| `created_at` | `string (date-time)` | required | Time at which the key was created. |
| `expires_at` | `string (date-time)` |  | Time at which the key expires: requests with it then get 401. Absent when it does not expire. |
| `organization_id` | `string` | required | ID of the organization. |
| `used_at` | `string (date-time)` |  | Time at which the key was last used. |
| `revoked_at` | `string (date-time)` |  | When the key was deleted (revoked). Revoked keys are not listed but can still be fetched by ID. |
| `updated_at` | `string (date-time)` | required | Time at which the name, permissions or revocation last changed. Use does not change it. |

`permissions` is a [ApiKeyPermissions](https://machinemail.dev/docs/reference/api/objects#apikeypermissions):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `*` | `boolean` |  | Every permission except inbound_raw. {"*": true, "inbound_raw": true} is full access plus raw injection. |
| `inbox_read` | `boolean` |  | Read inbox details. |
| `inbox_create` | `boolean` |  | Create new inboxes. |
| `inbox_update` | `boolean` |  | Update inbox settings. |
| `inbox_delete` | `boolean` |  | Delete inboxes. |
| `thread_read` | `boolean` |  | Read threads. |
| `thread_delete` | `boolean` |  | Delete threads. |
| `message_read` | `boolean` |  | Read messages. |
| `message_reply` | `boolean` |  | Reply and reply-all. message_send allows them too. |
| `message_send` | `boolean` |  | Send messages (and reply, reply-all, forward). |
| `message_update` | `boolean` |  | Update message labels. Also required to update threads. |
| `message_delete` | `boolean` |  | Delete messages. |
| `label_spam_read` | `boolean` |  | Access messages labeled spam. |
| `label_blocked_read` | `boolean` |  | Access messages labeled blocked. |
| `label_unauthenticated_read` | `boolean` |  | Access messages labeled unauthenticated. |
| `label_trash_read` | `boolean` |  | Access messages labeled trash. |
| `draft_read` | `boolean` |  | Read drafts. |
| `draft_create` | `boolean` |  | Create drafts. |
| `draft_update` | `boolean` |  | Update drafts. |
| `draft_delete` | `boolean` |  | Delete drafts. |
| `draft_send` | `boolean` |  | Send drafts. |
| `webhook_read` | `boolean` |  | Read webhook configurations. |
| `webhook_create` | `boolean` |  | Create webhooks. |
| `webhook_update` | `boolean` |  | Update webhooks. |
| `webhook_delete` | `boolean` |  | Delete webhooks. |
| `domain_read` | `boolean` |  | Read domain details. |
| `domain_create` | `boolean` |  | Create domains. |
| `domain_update` | `boolean` |  | Update domains. |
| `domain_delete` | `boolean` |  | Delete domains. |
| `list_entry_read` | `boolean` |  | Read list entries and suppressions. |
| `list_entry_create` | `boolean` |  | Create list entries and suppressions. |
| `list_entry_delete` | `boolean` |  | Delete list entries and suppressions. |
| `trusted_domain_read` | `boolean` |  | Read trusted domains and preview new ones. |
| `trusted_domain_create` | `boolean` |  | Add trusted domains. Organization API keys and the dashboard only: pod and inbox keys get 403 even with it. |
| `trusted_domain_update` | `boolean` |  | Change trusted domains. Organization API keys and the dashboard only, as for trusted_domain_create. |
| `trusted_domain_delete` | `boolean` |  | Remove trusted domains. |
| `metrics_read` | `boolean` |  | Read metrics and events. |
| `api_key_read` | `boolean` |  | Read API keys. |
| `api_key_create` | `boolean` |  | Create API keys. |
| `api_key_update` | `boolean` |  | Update API keys. |
| `api_key_delete` | `boolean` |  | Delete API keys. |
| `pod_read` | `boolean` |  | Read pods. |
| `pod_create` | `boolean` |  | Create pods. |
| `pod_update` | `boolean` |  | Update pods. |
| `pod_delete` | `boolean` |  | Delete pods. |
| `inbound_raw` | `boolean` |  | Inject raw mail with POST /inbound/raw. Never implied by an unrestricted key or *: it must be granted by name. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X PATCH "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/api-keys/key_01k6x3e8mr0000h4r0ty6c9k1n" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Production"
}'
```

```json title="Response 200"
{
  "type": "bearer",
  "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
  "prefix": "mm_live_4f8K",
  "name": "Production",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "permissions": {
    "*": false,
    "inbox_read": false,
    "inbox_create": false,
    "inbox_update": false,
    "inbox_delete": false,
    "thread_read": false,
    "thread_delete": false,
    "message_read": false,
    "message_reply": false,
    "message_send": false,
    "message_update": false,
    "message_delete": false,
    "label_spam_read": false,
    "label_blocked_read": false,
    "label_unauthenticated_read": false,
    "label_trash_read": false,
    "draft_read": false,
    "draft_create": false,
    "draft_update": false,
    "draft_delete": false,
    "draft_send": false,
    "webhook_read": false,
    "webhook_create": false,
    "webhook_update": false,
    "webhook_delete": false,
    "domain_read": false,
    "domain_create": false,
    "domain_update": false,
    "domain_delete": false,
    "list_entry_read": false,
    "list_entry_create": false,
    "list_entry_delete": false,
    "trusted_domain_read": false,
    "trusted_domain_create": false,
    "trusted_domain_update": false,
    "trusted_domain_delete": false,
    "metrics_read": false,
    "api_key_read": false,
    "api_key_create": false,
    "api_key_update": false,
    "api_key_delete": false,
    "pod_read": false,
    "pod_create": false,
    "pod_update": false,
    "pod_delete": false,
    "inbound_raw": false
  },
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "used_at": "2026-10-01T12:00:00.000Z",
  "revoked_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}
```

## Delete API Key in Pod

```http
DELETE /v0/pods/{pod_id}/api-keys/{api_key_id}
```

Same as Delete API Key (`DELETE /v0/api-keys/{api_key_id}`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `api_key_delete`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `api_key_id` | `string` | required | ID of the API key. |

#### Response

**200** Revoked (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X DELETE "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/api-keys/key_01k6x3e8mr0000h4r0ty6c9k1n" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "ok": true
}
```
