# Authentication and keys

> API keys, console session, scopes, environments, and security best practices.

## Two kinds of credentials

| | Console session | API key |
|---|---|---|
| **Format** | `ses_…` | `pk_test_…` (sandbox) or `pk_live_…` (production) |
| **Used for** | Managing your account: apps, keys, billing, logs | Calling the API from your server: connections, messages, templates, webhooks, usage |
| **Obtained via** | `POST /auth/signup` or `POST /auth/login` | `POST /apps/{appId}/keys` |
| **Lifetime** | 30 days, revocable (`POST /auth/logout`) | Until revoked |
| **Header** | `Authorization: Bearer ses_…` | `Authorization: Bearer pk_…` |

The routes in the "Authentication" and "Apps" sections, `GET /billing/overview`, and `GET /logs/requests` use the **session**. All the others use an **API key**. Each page of the [reference](/docs/api) states its authentication mode.

### The console uses the same routes

The API-key routes (connections, messages, templates, webhooks, usage) **also accept the console session**, with an `X-Environment-Id` header that specifies the target environment (an environment's identifier is returned by `GET /apps`). The signed-in member then has **all the scopes** of their account. This is what the Bref Point Inbox console does. These calls do not appear in the [request log](/docs/api/logs), which is reserved for API keys.

For use from your server, always use a key: its rights are limited by scope and its environment is fixed.

## Create a key

<ApiExample method="POST" path="/apps/{appId}/keys" />

<Callout type="warning" title="The secret is shown only once">
Only an irreversible hash is kept. If you lose it, create a new key and revoke the old one. The key list only shows their **prefix** (`prefix`) so you can recognize them.
</Callout>

## Scopes

A key can only do what you grant it. A call that requires a scope the key doesn't have is rejected with `403 forbidden`, and the message names the missing scope.

| Scope | Allows |
|---|---|
| `messages:send` | Sending messages (`POST /messages`) and using the sandbox simulator |
| `messages:read` | Reading a message or the history (`GET /messages`) |
| `connections:manage` | Creating connected businesses and connecting WhatsApp accounts |
| `connections:read` | Listing connected businesses, their accounts, and their numbers |
| `templates:manage` | Creating, listing, syncing, and deleting templates |
| `webhooks:manage` | Managing webhook endpoints and viewing/replaying deliveries |
| `usage:read` | Reading usage (`GET /usage`) |

<Callout type="tip" title="Least privilege">
Give each service **only** the scopes it needs: the server that sends reminders doesn't need `webhooks:manage`.
</Callout>

## Environments

Each app has a **sandbox** environment and a **production** environment. A key belongs to **one** environment only and sees only its data: connected businesses, numbers, messages, webhook endpoints.

- A `pk_test_…` key can never touch production, and vice versa.
- The [sandbox](/docs/guides/sandbox) simulator rejects production keys (`403 sandbox_only`).
- The sandbox is neither rate limited nor billed.

## Use a key

```bash
curl "$INBOX_API/messages" -H "Authorization: Bearer $INBOX_KEY"
```

A missing, invalid, or revoked key returns `401 unauthorized`.

<ErrorExample name="unauthorized" />

## List and revoke

<ApiExample method="GET" path="/apps/{appId}/keys" />

`lastUsedAt` shows the last use: a key that hasn't been used for weeks is a good candidate for revocation.

<ApiExample method="DELETE" path="/apps/{appId}/keys/{keyId}" />

Revocation is **immediate** and permanent.

## Best practices

- **Call the API from your server, never from a browser or a mobile app.** A key placed in client code is public. The API also doesn't allow browser calls (no CORS).
- Store keys in **environment variables** or a secrets manager, never in source code or a repository.
- Use **one key per service** and per environment: you can revoke one without interrupting the others.
- **Rotate your keys**: create the new one, deploy it, check that the old key's `lastUsedAt` no longer changes, then revoke it.
- Monitor the [request log](/docs/api/logs): every call appears there with its status and its `requestId`.

<Callout type="note" title="Webhook secret">
A webhook endpoint's `whsec_…` secret is **different** from an API key: it is used only to verify the signature of the events we send you. See [Webhooks](/docs/guides/webhooks).
</Callout>
