#Recipe: Authenticate
Every request needs an API key in the Authorization header. Keys are created in the
dashboard (API keys) or with POST /v1/api-keys (admin scope), are shown once, are stored
hashed, carry scopes, and can be rotated (POST /v1/api-keys/{id}/rotate) or revoked.
Keep keys on your server. Browsers get short-lived softphone credentials instead (recipe 03).
#TypeScript (SDK)
// Every request carries an API key. Create keys in the dashboard (API keys) and keep them server-side.
import { createVoiceClient } from '@voice/sdk';
const voice = createVoiceClient({ baseUrl: process.env.VOICE_API_URL!, apiKey: process.env.VOICE_API_KEY! });
const { data: workspace, error } = await voice.GET('/v1/workspace');
if (error) throw new Error(`${error.error.code}: ${error.error.message}`);
console.log('workspace', workspace.name, 'sip domain', workspace.sip_domain);
console.log('allowed destination countries', workspace.limits.allowed_destination_countries);
#curl
# Any endpoint; a missing or wrong key returns 401 with error.code UNAUTHENTICATED.
curl -sS "$VOICE_API_URL/v1/workspace" -H "Authorization: Bearer $VOICE_API_KEY"
#Notes
| Response | Meaning |
|---|---|
401 UNAUTHENTICATED |
Missing, malformed, expired or revoked key |
403 FORBIDDEN |
The key lacks the scope (details names it) |
429 RATE_LIMITED |
Too many requests; wait Retry-After seconds |
This example is executed against the sandbox in CI (tests/e2e/specs/m6-docs.spec.ts).