# Authentication

Source: https://joinsimplesms.com/docs/authentication
Index: https://joinsimplesms.com/llms.txt

Every request is authenticated with an API key in the `Authorization` header:

```bash
Authorization: Bearer ssms_sk_test_...
```

## Test and live keys

| Prefix | Mode | Behavior |
| --- | --- | --- |
| `ssms_sk_test_` | Sandbox | Instant and free. Messages are simulated (`"simulated": true`): no text is sent, except to your own verified phone. See [Sandbox](/docs/sandbox#simulated-or-real). |
| `ssms_sk_live_` | Live | Real numbers and real delivery. Mintable once your account has live access. |

Keys issued with an older prefix (`dsms_sk_*`, `resms_sk_*`)
are still accepted and will never be revoked for their prefix; new keys mint
as `ssms_sk_`. See [what else kept working](/docs/changelog#2026-10-05-delivered-is-now-simplesms)
when Delivered became SimpleSMS.

Keys never expire, but you can roll or revoke them anytime from the
[console](/console/keys). Rolling revokes the old key immediately
and mints a replacement.

## Storage

Keys are stored hashed (SHA-256), so we can never display a key again after
minting it. If you lose one, roll it.

## Scopes

A key has full access unless you restrict it. In
[Console → API keys](/console/keys), choose **Restricted** and tick the scopes
the key needs; a key that leaks can then only do what its job required.
Rolling a key keeps its scopes.

| Scope | Allows |
| --- | --- |
| `messages:send` | `POST /v1/messages`, `POST /v1/test/inbound` |
| `messages:read` | `GET /v1/messages`, `GET /v1/messages/{id}`, `GET /v1/deliverability` |
| `numbers:read` | `GET /v1/numbers`, `GET /v1/numbers/available` |
| `numbers:write` | `POST /v1/numbers`, `PATCH /v1/numbers/{id}`, `DELETE /v1/numbers/{id}` |
| `verify` | `/v1/verify`, `/v1/verify/check`, `/v1/verify/{id}` |
| `consent` | `/v1/consent` and everything under it |
| `lookup` | `GET /v1/lookup/{phone}` |
| `webhooks` | `GET /v1/events`, `GET /v1/events/wait`, `POST /v1/events/{id}/replay`, `GET /v1/webhooks`, `GET /v1/webhooks/deliveries` |
| `webhooks:write` | `POST /v1/webhooks`, `GET`/`PATCH`/`DELETE /v1/webhooks/{id}` (creating an endpoint returns its signing secret) |
| `customers` | `/v1/customers` and everything under it |
| `contacts` | `/v1/contacts`, `/v1/segments`, `/v1/topics` and everything under them |
| `registrations` | `/v1/registrations` and everything under it |
| `billing` | `GET`/`PATCH /v1/spend-limit` |
| `automations` | `POST /v1/track`, `/v1/automations` and everything under it |
| `audit_logs:read` | `GET /v1/audit-logs`, `GET /v1/exports/audit_log` |

A restricted key calling outside its scopes gets:

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key is missing the `messages:send` scope. Use a key with that scope, or mint one in the console (API keys).",
    "required_scope": "messages:send"
  }
}
```

Keys minted before scopes existed, and keys minted without choosing any, keep
full access.

## Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `invalid_api_key` | Missing, malformed, revoked, or unknown key. |
| 403 | `live_access_required` | Live key used before live access was granted, or a live-only endpoint hit with a test key. |
| 403 | `insufficient_scope` | A restricted key called an endpoint outside its scopes; `required_scope` names the one it needs. |
| 403 | `tenant_suspended` | The account is suspended. |
