Authentication
API keys, scopes, rotation.
Every programmatic request authenticates with an API key. This page covers the key format and environments, the header shape, what each scope grants, and how to rotate with zero downtime.
The header
Send your key as a Bearer token on every request. The match is case-insensitive on Bearer; a missing or invalid key returns 401 unauthenticated.
curl https://api.rexa.ai/v1/voices \
-H "Authorization: Bearer vk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Key format + environments
Keys look like vk_<env>_<random> — a vk_ marker (greppable in logs), the environment, and a 256-bit random body.
- vk_live_…
- — dispatches real telecom traffic and deducts production credits.
- vk_test_…
- — routes to the sandbox call agent + your verified-phone allow-list; never bills production credits. Use it in CI and local dev.
Only the first 12 characters (e.g. vk_live_a1b2) are stored — as key_prefix — for dashboard listing and log correlation. The full key is Argon2id-hashed at rest and shown once, at creation. If you lose it, create a new one.
Scopes
Each key carries a set of resource:action scopes. An endpoint requires a specific scope; calling it without that scope returns 403 forbidden. Grant the narrowest set a key needs — a key that only places calls needs sessions:write, not the full surface. Keys created in the dashboard default to every scope; pass an explicit scopes array to narrow it.
| resource | scopes |
|---|---|
| Sessions (calls + rooms) | sessions:readsessions:write |
| Campaigns | campaigns:readcampaigns:write |
| Messaging | sms:sendemail:send |
| Webhooks | webhooks:readwebhooks:write |
| Functions | functions:readfunctions:write |
| Voices | voices:readvoices:write |
| Numbers | numbers:readnumbers:write |
| Credits | credits:readcredits:write |
| Agents | agents:readagents:write |
| Concurrency | concurrency:readconcurrency:write |
| Discovery answers | questions:read |
Creating + revoking keys
Manage keys in the dashboard under Settings → API Keys, or programmatically. Create accepts a name, optional env (defaults to live), optional scopes (defaults to all), and an optional expires_at.
curl -X POST https://api.rexa.ai/v1/_internal/t/<workspace-slug>/api-keys \
-H "Content-Type: application/json" \
-H "Cookie: <dashboard session>" \
-d '{
"name": "server-prod",
"env": "live",
"scopes": ["sessions:write", "voices:read"]
}'The response includes plaintext — the only time the full key is returned. Store it in your secret manager immediately.
{
"id": "019eb8af-1557-7451-a3b2-3f2633f49125",
"name": "server-prod",
"prefix": "vk_live_a1b2",
"env": "live",
"scopes": ["sessions:write", "voices:read"],
"plaintext": "vk_live_a1b2c3d4e5...<shown once>",
"expires_at": null,
"created_at": "2026-06-16T10:00:00.000Z"
}Revoke a key with DELETE /v1/_internal/t/<slug>/api-keys/<id> (or the dashboard). Revocation is immediate — the next request with that key gets 401.
Rotating with zero downtime
There is no in-place “rotate” — and that’s deliberate: overlapping keys let you roll without a gap. Rotate in three steps:
- Create a new key with the same scopes.
- Deploy it to your services and confirm traffic is flowing on the new key (watch its
last_used_at). - Revoke the old key. Because both were valid during the overlap, no request ever 401s.
Set expires_at on a new key if you want it to self-retire on a schedule. Rotate on a cadence, and immediately if a key is ever exposed.
Next
Ready to make a call? Your first call in five minutes. Or browse the webhooks reference.