Every request to /v1 carries a key:
Creating one
Dashboard → API keys → Create key. The secret appears once, at creation. We
store an HMAC of it rather than the key itself, so there is no endpoint that can
show it to you again and no support request that can recover it. A lost key is
replaced, not retrieved.
You can also create keys from the API, with a key that holds api_keys:write —
but the first key has to come from the dashboard, because there is nothing to
authenticate the request with yet.
Scopes
Keys are scoped, and there is no wildcard. agents:write does not imply
agents:read; grant both if you need both.
webhooks:read and webhooks:write appear in the API’s scope list but are
reserved — there are no webhook endpoints yet, so granting them does
nothing.
The quickstart needs sip_trunks:write, phone_numbers:write, agents:read,
agents:write, calls:read, calls:write, usage:read and billing:read —
which is exactly what the dashboard preselects.
billing:write authorizes spending money — it is what creates a top-up
checkout. Do not put it on a key that only needs to read a balance.
There is no admin scope
One cannot be created either. Everything an administrator can do is unreachable
from an API key by construction, not by a permission check that could be
misconfigured — the credential is stripped before it reaches those routes.
A key cannot grant what it does not hold
Creating a key with scopes the calling key lacks returns 403 scope_escalation.
Otherwise a narrow key would be a step toward a broad one rather than a limit.
Rotation
A replacement is issued immediately and the old key keeps working for 24
hours. That window exists so a deploy is never caught between two keys with
neither valid: ship the new one, let the old one expire.
Revocation
Immediate, with no grace period. This is what you reach for when a key has
leaked — anything still using it starts failing at once.
Storing them
Treat a key like a password. Environment variables or a secret manager; never a
repository, a client-side bundle, or a URL. Keys carry a wv_live_ or
wv_test_ prefix so a secret scanner can spot one, and so a test key pasted
into production fails loudly instead of quietly operating on real data.