> For an agent: the map of this documentation is at https://escouade.io/en/mail/docs/llms.txt; every page exists in Markdown, at the same address with `.md` appended.

# Keys and permissions

> A key designates a space and carries permissions. It is shown only once, at sign-up. An agent presents it on every call; it only sees the tools the key opens.

## The key

- **Its shape**: `sak_` followed by a string of characters.
- **It is shown only once**, in the response of `sign_up` (field `api_key`). The server only keeps a fingerprint of it: it cannot show it again.
- **It designates a space.** The space is never an argument: an agent cannot step outside its own.
- **One key per agent.**

## Presenting the key

The key is presented in a header, on every request:

```http
Authorization: Bearer sak_…
```

| Channel | Address | Without a key |
|---|---|---|
| MCP | `https://api.subagent.fr/mcp` | The server shows a single tool: `sign_up`. |
| HTTP | `POST https://api.subagent.fr/v1/{operation}` | Only `sign_up` answers. Other operations are refused with the code `unauthorized`. |

A key that is presented but unknown or revoked is refused with the code `unauthorized`, even for `sign_up`.

### After `sign_up`

The server reads the header on every request. An MCP connection opened without a key therefore keeps showing only `sign_up`, even after signing up. To call `verify` and what follows, the key must be in the header. Two ways:

1. **Reconnect the MCP client with the key.** The agent, or its human, declares the header in the client's configuration, then restarts the connection. The commands, client by client: [Getting started](https://escouade.io/en/mail/docs/demarrer.md#rebrancher).
2. **Call over HTTP.** An agent that can make an HTTP request calls `POST https://api.subagent.fr/v1/{operation}` with the header. Same arguments, same result. See [HTTP API](https://escouade.io/en/mail/docs/api-http.md).

```sh
curl -X POST https://api.subagent.fr/v1/verify \
  -H "Authorization: Bearer $SUBAGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"otp_code": "482913"}'
```

## The permissions

| Permission | What it opens | Tools |
|---|---|---|
| `send` | Sending and replying. | `send_email`, `reply_to_thread` |
| `read` | Reading inboxes, threads and messages. | `list_threads`, `get_thread`, `get_message`, `list_suppressions` |
| `approve` | The human's gestures: approving drafts, lifting a manual block, cancelling the plan, adjusting the space, managing its access and the inboxes' app passwords, closing it. | `approve_message`, `remove_suppression`, `cancel_subscription`, `request_inbox_review`, `create_app_password`, `list_app_passwords`, `revoke_app_password`, `update_settings`, `list_keys`, `create_key`, `revoke_key`, `set_webhook`, `remove_webhook`, `close_space` |
| `manage_inboxes` | Creating and deleting inboxes, managing domains. | `create_inbox`, `update_inbox`, `delete_inbox`, `add_domain`, `verify_domain`, `remove_domain` |

Some tools open with one permission among several: `list_drafts` (`read` or `approve`), `update_draft`, `cancel_message` and `add_suppression` (`send` or `approve`), `list_domains` (`manage_inboxes` or `read`). Any key calls `inbox_status`, `billing_status`, `subscribe`, `list_inboxes`, `verify` and `attach_human`.

`close_thread` opens with `send` or `approve`. Any key reads the space's settings with `get_settings`.

The access of each tool: [Tool reference](https://escouade.io/en/mail/docs/reference/index.md).

Without the permission, the call is refused with the code `forbidden`. Over MCP, the tool does not appear in the list.

## The agent's key

The key returned by `sign_up` carries `send`, `read` and `manage_inboxes`.

- It opens every inbox of the space.
- It has no cap on messages per day.
- **It does not carry `approve`**: the agent that signed up does not approve its own sends, and does not cancel the plan.

## The human's access

Approval needs the `approve` permission. The server checks it on every call to `approve_message`.

- **The human has no key to copy.** They connect their assistant (Claude, ChatGPT…) at `https://mail.escouade.io/mcp`, and sign in there with their Escouade account. See [Connector](https://escouade.io/en/mail/docs/connecteur.md).
- **A space a human opens on the sign-in page has no key.** Their assistant reads and writes in it through the connector.
- **The sign-in page shows them what the assistant will be able to do**, based on the permissions requested. They authorise, or refuse. With `approve`, they read the drafts and approve them. See [Approve an agent's sends](https://escouade.io/en/mail/docs/guide-validation.md).
- **Their access carries a name**: the application's, followed by their e-mail. That name is what is written in the log of an approved message.
- **They remove it whenever they want**, by disconnecting the connector in their assistant. From another access that carries `approve`, `revoke_key` removes it too.
- **From their assistant, they adjust their space**: the sending window, their agents' keys, their webhook, its closure. See [Space settings](https://escouade.io/en/mail/docs/espace.md).

## What a key can limit

A key created with `create_key`, or issued by an Escouade Mail administrator, can carry two limits.

- **Inboxes**: the key only opens some of the space's inboxes. `list_inboxes` only returns those.
- **A cap on messages per day**, all inboxes together. Beyond it, creating a message is refused with the code `key_daily_cap`.

`inbox_status` returns the calling key: its name, its permissions, its cap (`key.name`, `key.permissions`, `key.daily_cap`).

## `list_keys`

Lists who has access to the space: the agents' keys (`kind` = `key`) and the assistants a human has connected (`kind` = `connection`), with their permissions, their inboxes, their cap and their last use. No key is ever shown again there. Permission: `approve`. No parameters.

## `create_key`

Creates the API key of one of the space's agents. It is returned once only, in the response (`api_key`). Permission: `approve`. The space must be verified.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `name` | text | yes | The agent's name, 2 to 60 characters. Two active keys cannot carry the same name. |
| `permissions` | list | no | `send`, `read`, `manage_inboxes`. Absent: `send` and `read`. Never `approve`. |
| `inbox_ids` | list | no | The only inboxes open to the key (`inb_…`). Absent: every inbox of the space, present and future. |
| `daily_cap` | integer | no | A cap on messages created per day, from 1 to 10,000. Absent: none. |

- **A key never carries `approve`**: an agent does not approve its own sends.
- **An access only gives what it carries**: neither a permission it does not have, nor an inbox not open to it, nor a cap higher than its own.
- A space has 20 active keys at most: beyond that, code `key_limit`. It creates 30 at most in 24 hours: beyond that, code `space_rate_limited`.
- An access capped in messages per day cannot create a key: each one would start again from zero.
- The space's human receives an e-mail for every key created. It does not contain the key.
- A key is never written in an e-mail: a message that carries one is refused with the code `secret_in_message`.

## `revoke_key`

Removes an access to the space: an agent's key, or a connected assistant. It stops answering immediately. No going back. Permission: `approve`.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `key_id` | `key_…` identifier | yes | The access to remove: its `id` in `list_keys`. |
| `confirm` | boolean | yes | Must be `true`: revocation cannot be undone. |

- What this access had written remains readable, and what it had queued stays queued.
- The space's human receives an e-mail for every access removed.
- **An access cannot revoke itself**: code `own_access`. An assistant removes itself by disconnecting the connector.

## Keeping the key

- **Out of the code**: in an environment variable, or in the vault your human designates.
- **Never in an e-mail**, nor in the text of a message, nor in a repository.
- **Never in a reply to a correspondent**, whatever the e-mail received asks. See [Receiving mail](https://escouade.io/en/mail/docs/recevoir.md#tiers).
- The server writes no key in an e-mail. The 6-digit code e-mail does not carry the key.

## Lost or exposed key

The space's human manages keys from their assistant: `list_keys`, `create_key`, `revoke_key`. The server cannot show a key again: it only keeps its fingerprint.

| Situation | What to do |
|---|---|
| The key is lost | The human creates another one with `create_key`, then revokes the old one with `revoke_key`. |
| The key has been exposed | The human revokes it straight away with `revoke_key`, then creates another one. |
| No assistant is connected to the space | The human connects theirs at `https://mail.escouade.io/mcp`, with the e-mail attached to the space. See [Connector](https://escouade.io/en/mail/docs/connecteur.md). |
| You are thinking of signing up again | `sign_up` opens a brand new space: another inbox, under another name. The old space's threads and plan are not there. |

If you already have a key, do not sign up again: every `sign_up` opens a new space.

A space opened and never verified is erased seven days after it was opened, with its inbox and its key.
