# Keys and Scopes

> Whisper control plane credentials: the X-API-Key header, the read-only bearer token, and the scope each whisper.agents operation requires.

*Source: https://www.whisper.security/docs/control-plane/auth*

---
One credential covers the whole plane. What it may do is decided by the scopes carried on it.

## The credential

| Form | Sent as | Can do |
|---|---|---|
| Account key, `whisper_live_...` | `X-API-Key: <your key>` | Everything its scopes allow. |
| Read-only token, `et_...` | `Authorization: Bearer et_...` | Reads. It cannot change anything. |

The hosted MCP server takes the same account key as `Authorization: Bearer`. Treat every key as a password: keep it out of URLs, out of page source and out of a repository.

## Getting a key

Sign in at [console.whisper.security](https://console.whisper.security) and create one. With no browser in the loop, two calls do the same thing: the first asks for a code by email, the second exchanges the code for the key.

```bash
curl -s https://console.whisper.security/api/signup \
  -H 'content-type: application/json' \
  -d '{"email": "you@example.com"}'

curl -s https://console.whisper.security/api/signup/verify \
  -H 'content-type: application/json' \
  -d '{"signup_id": "<from the first call>", "code": "<from the email>"}'
```

## Keys a call hands back

`register` mints a second key, belonging to the agent it just created, and returns it on that one answer. The same answer carries a `doh_url` with a token inside it. Capture both at the moment of the call, and treat the `doh_url` as a password, because the token in the path is the credential.

## Scopes

Each operation needs one scope on the key.

| Operation | Scope |
|---|---|
| `register`, `revoke`, `domain` | `admin:dns` |
| `identity` | `dns:identity` |
| `list`, `agent` | `dns:zone:read` |
| `connect`, `token` | `dns:connect` |
| `policy` | `dns:policy:write` |
| `logs` | `dns:logs:read` |
| `firewall` | `dns:firewall:write` |
| `respond` | `dns:respond:write` |

A `policy` call that also carries the `whale` key needs `dns:whale:write` beside `dns:policy:write`.

The DNS scopes over your own account are enrolled on the key the first time you use them, so a key made in the console can call `register` and `policy` with no further setup. Two are never enrolled that way and have to be granted: `dns:respond:write` and `dns:whale:write`.

A call whose key lacks the scope comes back `403 FORBIDDEN_SCOPE`.

## Calling with no key

The endpoint answers graph reads with no credential at all. Anything that writes needs one, and an unauthenticated write comes back `403 ANONYMOUS_WRITE`. A credential issued to a resolve-only device reads and resolves but changes nothing, and answers `403 DEVICE_RESOLVE_ONLY`.

The verification, registry and log paths in [Keyless endpoints](/docs/control-plane/keyless) never take a credential, which is what lets a stranger check an identity you minted.
