X-API-Key header. The gateway resolves the key
to a user and injects identity downstream — you never pass a user id or subject yourself; the server
derives it from the key.
Base URL
Getting a key
Don’t have a key yet? Hosted: sign in at vexa.ai/signin with a Google account and copy your key from your account page — free credit, no card required. Self-hosted:make all prints a key when the stack comes up.
make all mints a key as part of bring-up and prints it (along with the service URLs) when the
stack is ready — copy it from the make all output and use it as your X-API-Key.
Minting more keys
To mint additional keys, use theprovision-token make target with your ADMIN_TOKEN (it’s set in
.env, default dev-admin-token — change it before exposing anything):
http://localhost:18057 by default
(ADMIN_API_PORT) and is protected by ADMIN_TOKEN via the X-Admin-API-Key header — you can call it
directly for finer control over users and scopes:
token is your X-API-Key. It is shown once — store it.
Scopes
A key carries one or more scopes. Pass them in the JSON body as{"scopes":["bot","tx"]}, or as
query scope=<one> / scopes=<a>,<b>. The body wins when both are present. An unknown body field
is refused with 422 — never silently dropped.
/recordings*, /user/webhook*, /user/models, /user/transcription, /agent/* and /mcp accept
either bot or tx. Most keys want both: {"scopes":["bot","tx"]}. The three /user/* routes in
that list are the Settings API — model credentials, transcription backend, and
webhook delivery, stored per key owner.
Every route requires a scope. A key that holds none of the scopes a route accepts gets 403,
whichever route it is.
Keys are prefixed by their primary scope — vxa_bot_…, vxa_tx_…, vxa_browser_…. A key without a
recognized scope is rejected.
Rotating and revoking
Mint a new key, switch your clients over, then delete the old one by its token id:expires_in=<seconds>; expired keys are rejected automatically.
Login-minted tokens (terminal-login)
When a user signs in through the terminal (OAuth or email login), the terminal mints an API token
named terminal-login to populate the auth cookie. These are bounded per user: after each
sign-in the terminal keeps only the newest VEXA_TERMINAL_LOGIN_TOKEN_CAP (default 3) and revokes
the older ones. So when you audit a user’s tokens —
terminal-login-named entries are login sessions (capped, not unbounded), while any
differently-named entries are self-serve keys the user minted and are never pruned by login.
What can go wrong
See the full error reference.