# Authentication

Every request carries a bearer token in the Authorization header. Keys are formatted mh_{mode}_{22 base62 characters} where mode is live or test. We store only a SHA-256 hash and a 14-character display prefix — the full key is shown once, at creation, and cannot be recovered.

```http
Authorization: Bearer mh_live_a1b2c3d4e5f6g7h8i9j0k1l2
```

## Scopes

A key carries only the scopes it needs. A key that only sends should not be able to read your contact list.

| Scope | Grants |
|---|---|
| emails:send | Send transactional email and batches |
| emails:read | Read message status, events and metadata |
| emails:read_body | Read stored bodies — every call is audit-logged |
| domains:read | List domains and their DNS state |
| domains:write | Add, verify and modify domains |
| contacts:write | Create and modify audiences, contacts and suppressions |
| campaigns:write | Create, schedule and control campaigns |
| templates:read | Read and render templates |
| webhooks:write | Manage webhook endpoints |
| analytics:read | Read aggregated analytics |

## Restricting a key further

- Pin the key to a single domain, so a leaked key cannot send from your other identities.
- Add an IP allowlist, so it only works from your own servers.
- Set an expiry, which is what you want for anything a contractor holds.

> **Keys found in public repositories are revoked automatically** — We run a secret-scanner integration. A key committed to a public repository is auto-revoked and the owner notified — which is disruptive, and much less disruptive than the alternative.
