concepts
API Keys
Scoped API keys for programmatic access.
Creating a Key
POST /api/v1/orgs/{orgId}/teams/{teamId}/api-keys{ "name": "CI Pipeline", "scopes": ["team.inboxes.create", "team.inboxes.view", "team.emails.view"], "expires_in": "2160h", "allowed_ips": ["203.0.113.0/24"]}The response includes the raw_key field — shown only once (on creation and rotation).
Key Format
API keys are bb_ followed by 64 hex characters (32 bytes of CSPRNG output). The raw key is never stored: the database keeps a SHA-256 hash of the full key plus an 11-character prefix (bb_ and the first 8 hex chars) for display.
Available Scopes
Scopes use the team.* permission key format. When creating or updating a key, specify any combination of these:
| Scope | Permission |
|---|---|
team.view | View team info |
team.settings.manage | Edit team settings |
team.members.view | View team members |
team.members.manage | Add/remove team members |
team.members.role | Change team member roles |
team.domains.view | View domain assignments |
team.domains.manage | Update domain assignment settings |
team.webhooks.view | View webhooks and delivery logs |
team.webhooks.manage | Create/edit/delete webhooks |
team.apikeys.view | View API keys |
team.apikeys.manage | Create/revoke/rotate API keys |
team.inboxes.view | View team inboxes |
team.inboxes.create | Create inboxes |
team.inboxes.manage | Manage inboxes |
team.emails.view | View emails |
team.analytics.view | View team analytics |
The available scopes are derived from the team-scoped permissions in the database. If custom team roles add new permissions, those keys become available as API key scopes automatically.
Authentication
curl -H "Authorization: Bearer bb_your-key-here" \ https://your-instance.com/api/v1/inboxesThe auth middleware detects the bb_ prefix and validates against the API key table instead of JWT. A request is rejected when:
| Condition | Status |
|---|---|
| The hash matches no key | 401 invalid API key |
expires_at is in the past | 401 API key expired |
| The key has been revoked | 401 API key revoked |
The key is deactivated (is_active = false) | 401 API key disabled |
The caller's IP falls outside allowed_ips | 403 IP not allowed for this API key |
| The endpoint's required scope is not in the key's scopes | 403 insufficient scope |
A valid key acts as the user who created it — the same org and team
memberships apply — but never carries system-admin rights, even when its creator
is a system admin. Every accepted request updates request_count,
last_used_at and last_used_ip. Each request checks that the key's scopes include the permission required by the endpoint.
Key Management
Get a Single Key
GET /api/v1/orgs/{orgId}/teams/{teamId}/api-keys/{keyId}Update a Key
PATCH /api/v1/orgs/{orgId}/teams/{teamId}/api-keys/{keyId}{ "name": "Updated Name", "scopes": ["team.inboxes.view", "team.emails.view"], "is_active": false, "allowed_ips": ["10.0.0.0/8"]}Setting is_active to false performs a soft-revoke — the key still exists but is rejected on use. DELETE is also a soft revoke: it stamps revoked_at/revoked_by and the row survives for audit. Rows are hard-deleted only once expired, by the cleanup worker.
Key Rotation
POST /api/v1/orgs/{orgId}/teams/{teamId}/api-keys/{keyId}/rotateGenerates a new secret for an existing key. The old secret is immediately invalidated. The response includes the new raw_key.
Revoke a Key
DELETE /api/v1/orgs/{orgId}/teams/{teamId}/api-keys/{keyId}Bulk Revoke
POST /api/v1/orgs/{orgId}/teams/{teamId}/api-keys/bulk-revoke{ "key_ids": ["uuid-1", "uuid-2"]}Returns the count of revoked keys and any skipped IDs.
IP Allowlist
Keys can be restricted to specific IP addresses or CIDR ranges via the allowed_ips field. When set, requests from IPs outside the allowlist are rejected. An empty list allows all IPs.
Usage Tracking
Each API key tracks:
request_count— total number of requests made with the keylast_used_at— timestamp of the most recent requestlast_used_ip— IP address of the most recent request
These fields are returned when listing or fetching keys.