Authenticate every request with a bearer token:

```bash
curl https://api.proppertrading.com/v1/ping \
  -H "Authorization: Bearer sk_live_a1b2c3d4_9f83c2e15b7a4d6e8091c3f5a7b9d1e2"
```

A key looks like this:

```
sk_live_a1b2c3d4_9f83c2e15b7a4d6e8091c3f5a7b9d1e2
   |    |        |
   |    |        secret, shown once
   |    key id, safe to log and quote in support tickets
   environment
```

Only the key id is stored in readable form on our side. The secret is stored as a
hash, which is why we cannot show it to you again.

:::danger Server side only
A key acts for your whole firm. Never put it in a browser, a mobile app, or any
repository. Call our API from your own server and keep the key in an environment
variable or a secret manager.
:::

## Creating a key

In your dashboard, go to **Configuration -> API keys** and create one. You choose:

- **Scopes** - what the key may do. Grant the minimum the integration needs.
- **IP allowlist** - optional. Only these addresses may use the key.
- **Expiry** - optional. The key stops working on that date.

The secret is shown exactly once, right after you create it.

## The other kind of credential

Everything on this page is about API keys, which act as a firm. There is a second
kind that acts as a person on a device, used by the Propper apps and by anybody
scripting on their own behalf. It starts with `ptat_`, it is handed out by signing
in rather than generated in a dashboard, and it can never hold the scopes below.

See [Signing in as a person](/docs/api/apps/overview).

## Scopes

Scopes are `resource:action`. A `:write` scope also grants the matching `:read`,
so a key that may create affiliates can also list them.

| Scope              | Allows                                        |
| ------------------ | --------------------------------------------- |
| `affiliates:read`  | Read affiliates and pre-registrations         |
| `affiliates:write` | Create, resend and withdraw pre-registrations |

A key that authenticates but lacks the scope gets `403 insufficient_scope`, and
the response tells you which scope was required. Do not retry with a different
key; ask your firm to widen this one.

## Rotating and revoking

Rotating replaces the secret in place. Everything using the old secret breaks
immediately, so to switch over without downtime:

1. Create a second key with the same scopes.
2. Deploy it.
3. Revoke the first key.

Revoking takes effect immediately and cannot be undone.

## When something is wrong

| Code                 | Meaning                                                |
| -------------------- | ------------------------------------------------------ |
| `missing_api_key`    | No `Authorization` header                              |
| `invalid_api_key`    | The key does not exist or the secret is wrong          |
| `revoked_api_key`    | The key was revoked in the dashboard                   |
| `expired_api_key`    | The key passed its expiry date                         |
| `ip_not_allowed`     | The key has an allowlist and your address is not on it |
| `insufficient_scope` | Valid key, missing scope                               |

Credentials are only accepted in the `Authorization` header. Query-string
credentials are rejected on purpose: they leak into access logs, browser history
and referrer headers.