# Authentication and API keys

Bearer API keys, scopes, IP allow-lists, expiry and rotation.

Send the key as a bearer token on every request:

```http
GET /v1/balances HTTP/1.1
Host: api.banking.wirebloom.com
Authorization: Bearer wb_live_7Hk2cQab…
X-Tenant-Id: 0192a6f0-0000-7000-8000-0000000000c1
```

## Creating a key

A customer Owner or Admin creates keys in the web app (Settings → API keys) or with `createApiKey` (`POST /v1/api-keys`, an interactive-session operation, not part of the public API-key reference) from an interactive session; a fresh step-up verification (`api_key.create`) is required. The full key is shown **once** in the response (`secret`); WireBloom stores only its SHA-256 hash and a display prefix (`wb_live_7Hk2cQab`). Store it in your secrets manager; if it is lost, rotate or create a new one.

```json
POST /v1/api-keys
{ "name": "ERP sync", "environment": "live",
  "scopes": ["balances:read", "transactions:read", "payments:write"],
  "ipAllowlist": ["203.0.113.0/24"], "expiresAt": "2027-09-30T00:00:00Z" }
```

## Scopes

A key reaches only the operations whose contract lists its scope (`x-api-key-scopes`, shown on every operation in the [reference](/reference)); anything else is `403` [`insufficient-scope`](/errors#insufficient-scope).

| Scope               | Grants                                                                                                                        |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `balances:read`     | balances, balance details, holds, enabled currencies                                                                          |
| `transactions:read` | transactions, journal, exports and jobs, annotations and attachments (read)                                                   |
| `statements:read`   | statements and PDF confirmations                                                                                              |
| `recipients:read`   | recipients and field requirements                                                                                             |
| `recipients:write`  | create, edit, delete and verify recipients                                                                                    |
| `payments:read`     | payments, transfers, scheduled payments, mandates, collections, bulk payments, approvals, funds requests                      |
| `payments:write`    | create/submit/cancel payments, transfers, add money, scheduled payments, mandates, collections, bulk payments, funds requests |
| `exchanges:read`    | quotes, exchanges, forward contracts                                                                                          |
| `exchanges:write`   | quotes, exchanges, forward contracts and drawdowns                                                                            |
| `webhooks:manage`   | integrator webhook endpoints and deliveries ([Webhooks](/docs/webhooks))                                                      |

Keys can never approve payments, manage people, settings, cards or API keys. Payments created with a key follow the same limits and approval tiers as the web app (a payment may wait in `pending_approval` for a human approver).

## Restrictions

- `ipAllowlist` (IPv4/IPv6 addresses or CIDR blocks, up to 20): requests from other addresses get `403` [`ip-not-allowed`](/errors#ip-not-allowed).
- `expiresAt`: after it the key is `401` [`invalid-api-key`](/errors#invalid-api-key).
- A key is bound to one customer: another `X-Tenant-Id` is `403` [`tenant-forbidden`](/errors#tenant-forbidden).
- `wb_test_` keys work only in environments with the [sandbox](/docs/sandbox).

## Rotation

`rotateApiKey` (`POST /v1/api-keys/{id}/rotate`, from an interactive session, optional `{ "gracePeriodHours": 24 }`, default 24, max 168, `0` revokes the old key at once) returns a new key with the same settings; the old key keeps working until the end of the grace period. Deploy the new key, confirm traffic, then revoke the old one (`revokeApiKey`, `DELETE /v1/api-keys/{id}`, immediate). `lastUsedAt` on the key and the per-key usage in the console ([Integration and usage](/docs/integration-usage)) help confirm the switch. Rotation needs step-up `api_key.create`.
