# Enrollment tokens & scopes

{/* // THE IDENTITY PILLAR */}

The enrollment token is the identity pillar of Postern — the "scoped key that creates up to *N*
mailboxes without the master key." It is a **capability token**: it carries only the access it was
granted, and that access is enforced server-side, not on trust.

## Anatomy of an enrollment key

A minted enrollment key looks like `pk_enroll_<id>_<secret>`. The raw value is shown **once**; Postern
stores only its SHA-256 hash and compares in constant time. A key carries:

| Property | Meaning |
|---|---|
| `label` | Human-readable name for the console (e.g. "support-bot bootstrap"). |
| `scopes` | The capabilities it can mint into agent keys: `mailbox:create`, `mailbox:read`, `mailbox:send`. |
| `allowed_domains` | The domains mailboxes may be created on. Empty = any org domain. |
| `max_mailboxes` | The hard cap *N* on how many mailboxes this key may ever mint. |
| `used_count` | How many it has minted so far. At `max_mailboxes`, the key is exhausted. |
| `reusable` | Whether one key can mint many agents/mailboxes, or is single-use. |
| `expires_at` | When the key stops working, regardless of remaining quota. |
| `revoked` | A kill switch — instant, isolated. |

## The capability model

Postern's capability model is inspired by Panocrypt's: the **token carries a high-entropy secret, but
the server is the source of truth** for the counter and revocation.

1. **Mint.** A human or org-admin call issues a key with scopes, allowed domains, `max_mailboxes`,
   and an expiry. POST /v1/enrollment-tokens (console)

2. **Redeem.** An agent redeems the key for a scoped agent key (`pk_agent_…`), idempotent on a
   client-provided `agent_handle`. POST /v1/enroll

3. **Spend.** Each `mailbox:create` increments `used_count`. At `max_mailboxes` the key is exhausted —
   **a cloned key cannot exceed *N***, because the counter lives server-side.

4. **Revoke.** Per-token and per-agent-key, instant, isolated. Killing one key never rotates others.
**Exhausted is a feature, not an error:** When a key has minted its max, the next create returns `409 Conflict` with a clear message:
  `This enrollment key is exhausted — it minted its max of 5 mailboxes. Issue a new key.` That cap is
  the point — it bounds the blast radius of a leaked key.

## Scopes

Scopes are additive capabilities. A key grants a subset; the minted agent key can never exceed it.

| Scope | Grants | Typical use |
|---|---|---|
| `mailbox:create` | Create new mailboxes (against the quota) | Provisioning agents |
| `mailbox:read` | List/read messages and threads | Read-only monitors, OTP readers |
| `mailbox:send` | Send and reply | Outbound/transactional agents |

```text title="example: a read-only OTP runner"
scopes          = mailbox:create, mailbox:read   // can make inboxes + read, but never send
allowed_domains = (empty → any org domain)
max_mailboxes   = 20
expires_at      = +24h
```

## Why this contains the blast radius

The motivating failure is a supply-chain compromise of an MCP host that was handed an org-wide key.
Postern's answer is structural: **no org-wide key ever reaches an MCP host.** The host gets only a
single-agent, single-purpose, expiring enrollment key. If it leaks:

- it can't exceed `max_mailboxes` (server counter);
- it can't reach domains outside `allowed_domains`;
- it can't do anything outside its `scopes`;
- it can be revoked instantly, in isolation, without touching any other agent;
- every action it took is attributable in the audit log.
**Phase-2: offline attenuation:** Biscuit/macaroon-style offline attenuation (so a sub-agent can further narrow a key without a
  server round-trip) is a planned upgrade. In V1 the server is the single source of truth for the
  counter and revocation.

## Next

- [Agents](https://docs.agents.mszazu.com/concepts/agents/) — the principal a key binds to.
- [Authentication & keys](https://docs.agents.mszazu.com/quickstart/authentication/) — the redeem flow.
- [API · Enrollment](https://docs.agents.mszazu.com/api/enrollment/) — the `/v1/enroll` contract.