# Authentication

> API keys, headers, scoped permissions and the audit log.

Source: https://aimulti.inbox/docs/authentication/

Every request under `/v0` and `/mcp` needs an API key. A key belongs to one organization and sees only that organization's data.

## Sending the key

Use the `Authorization` header with the `Bearer` scheme. `X-Api-Key` is accepted as an alternative, which is convenient for MCP clients that only let you set a plain header.

```http
Authorization: Bearer mai_live_your_key
# or
X-Api-Key: mai_live_your_key
```

## Key format and storage

* Keys start with `mai_live_` followed by 32 random characters. The fixed prefix makes leaked keys easy to detect with secret scanning.
* The plaintext key is shown once, when it is created. The server stores only a SHA-256 hash, so a lost key cannot be recovered: create a new one.
* Treat a key like a password: keep it in environment variables or a secret manager, never in client side code or a public repository.

## Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `missing_authorization` | No key in `Authorization` or `X-Api-Key`. |
| 401 | `unknown_api_key` | The key is malformed, revoked or does not exist. |
| 401 | `api_key_expired` | The key passed its `expires_at`. Create a new one. |
| 403 | `missing_permission` | The key is scoped and lacks the permission the endpoint needs. |
| 403 | `permission_escalation` | A key tried to create or revoke a key with permissions it does not hold. |
| 429 | `rate_limit_exceeded` | Too many requests, or too many failed authentications from the same address. Wait for `Retry-After`. |

## Scoped keys

A key either has full access or a list of permissions. Give each agent a key with only what it needs: a triage agent that reads mail and sets labels does not need `message_send`. Keys can also carry an `expires_at`, and revocation takes effect immediately.

| Permission | Allows |
| --- | --- |
| `inbox_read` | List and get inboxes. |
| `inbox_write` | Create, rename and delete inboxes. |
| `message_read` | List and read threads, messages, raw messages and attachments. |
| `message_send` | Send new messages and replies. |
| `message_update` | Add and remove labels on messages. |
| `domain_read` | List and get domains. |
| `domain_write` | Add, verify and delete domains. |
| `webhook_read` | List webhooks. |
| `webhook_write` | Create and delete webhooks. |
| `api_key_read` | List API keys. |
| `api_key_write` | Create and revoke API keys, within the caller's own permissions. |
| `audit_read` | Read the audit log. |
| `message_review` | Read messages held for security review with `include_review=true`. For a human review console, not for agents. |
| `message_release` | Release a message held for review, which then fires `message.received`. Keep it away from the agent that reads the mail. A full access key does not hold it: only keys that list it do, and the operator issues the first one. |
| `billing_read` | Read the plan and the subscription state with `GET /v0/billing`. |
| `billing_write` | Start a Stripe Checkout for a paid plan and open the customer portal. |
| `data_export` |  |
| `data_delete` |  |
| `ticket_read` |  |
| `ticket_write` |  |
| `notification_write` | Register a device for web push and drop it again. It wakes a browser, it does not read mail. |

* A key can create keys only with permissions it holds itself, and a scoped key cannot create a full access key. Violations return `403 permission_escalation`.
* Omit `permissions` when creating a key to copy the calling key's permissions.
* The same permissions apply to MCP tools, so a scoped key limits what a coding agent can do too.

```bash
curl -s -X POST https://api.162-55-213-24.sslip.io/v0/api-keys \
  -H "Authorization: Bearer $MAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "triage-agent", "permissions": ["inbox_read", "message_read", "message_update"], "expires_at": "2027-01-31T00:00:00Z"}'
```

## Audit log

Security relevant actions of your organization, including actions taken by agents through MCP, are recorded. Read them with `GET /v0/audit-logs` (requires `audit_read`).

## Endpoints

### GET /v0/auth/me

Caller summary (kept for older clients; same body as GET /v0/me)

Responses:

* `200` OK: Me
* `401` Error envelope: Error

### POST /v0/api-keys

Create a key with at most the caller's permissions (key shown once)

Body:

| Name | Type | Notes |
| --- | --- | --- |
| `name` | string | max length 100 |
| `permissions` | Permission[] | omit to copy the caller's permissions |
| `expires_at` | string (date-time) |  |

Responses:

* `201` Created
* `403` Error envelope: Error

### GET /v0/api-keys

List active keys (requires api_key_read)

Responses:

* `200` OK

### DELETE /v0/api-keys/{key}

Revoke a key immediately (requires api_key_write, no escalation)

Parameters:

| Name | Type | Notes |
| --- | --- | --- |
| `key` | string (uuid) | in path, required |

Responses:

* `204` Revoked

### GET /v0/audit-logs

Security relevant actions of the organization (requires audit_read)

Parameters:

| Name | Type | Notes |
| --- | --- | --- |
| `limit` | integer | in query, min 1, max 200, default 50 |

Responses:

* `200` OK
