Sayna supports two authentication strategies. Choose the one that matches your deployment needs, or enable both. When both are configured Sayna first checks the API secret, then falls back to delegated JWT validation. Protected HTTP endpoints accept the authentication token via either method:Documentation Index
Fetch the complete documentation index at: https://docs.sayna.ai/llms.txt
Use this file to discover all available pages before exploring further.
- Header:
Authorization: Bearer <token> - Query parameter:
?api_key=<token>
Token precedence and fallback
When both anAuthorization header and an api_key query parameter are present, Sayna evaluates them in this order:
- The
Authorizationheader is evaluated first. - If the header format is invalid, the server falls back to the
api_keyquery parameter.
missing_auth_header.
Quick summary
| Mode | When to use | Highlights |
|---|---|---|
| API secret | Single-tenant deployments or trusted networks | Lowest latency, no external service, bearer-token comparison against AUTH_API_SECRET. |
| JWT + auth service | Multi-tenant or context-aware authorization | Sayna signs the entire request payload, calls out to your service, and trusts the HTTP status you return. |
Toggle auth with the
AUTH_REQUIRED environment variable. When it’s false, all endpoints accept unauthenticated requests.Option A — API secret authentication
Setup steps
-
Generate a long random secret.
-
Set the environment variables:
-
Clients send the secret as a bearer token in the header or as a query parameter:
Security notes
- Use at least 32 random bytes; rotate periodically and never commit secrets to VCS.
- Always terminate TLS before traffic reaches Sayna so the bearer token stays encrypted in transit.
- Pair with IP allowlists when you use this mode for back-office tools.
Option B — Delegated JWT authentication
API secret not enough? Let your own service validate tokens and return200/401 decisions.
1. Generate signing keys
2. Configure Sayna
3. Implement the auth service
Your service must:- Accept
POSTrequests whose body is the JWT Sayna generated. - Verify the JWT signature using the public key (RS256 or ES256).
- Validate the extracted bearer token and any contextual data.
- Return
200 OK(allow) or401 Unauthorized(deny). Other 4xx map to401, 5xx become502, and timeouts surface as503upstream.
Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
AUTH_REQUIRED | No | false | Enable or disable authentication middleware. |
AUTH_API_SECRET | Conditional | – | Shared secret for API secret auth. |
AUTH_SERVICE_URL | Conditional | – | External auth service endpoint (JWT mode). |
AUTH_SIGNING_KEY_PATH | Conditional | – | Path to private key used to sign the JWT payload. |
AUTH_TIMEOUT_SECONDS | No | 5 | Timeout for auth service requests. |
AUTH_REQUIRED=true you must specify either AUTH_API_SECRET or both AUTH_SERVICE_URL and AUTH_SIGNING_KEY_PATH. You can configure both to let Sayna short-circuit known service-to-service traffic with the secret and route user traffic through the JWT flow.
JWT payload schema (delegated mode)
sub: Alwayssayna-authso your service knows who issued the JWT.iat/exp: Issued-at plus 5-minute expiration.auth_data.token: The extracted usable token, resolved from theAuthorizationheader or theapi_keyquery parameter (following the precedence rule above).auth_data.request_body: JSON payload of the HTTP request.auth_data.request_headers: Filtered headers (excludesauthorization,cookie,x-forwarded-*,x-sayna-*,host,x-real-ip).auth_data.request_path/request_method: Full request context for policy decisions.
Verification checklist
Protected endpoints
WhenAUTH_REQUIRED=true the following endpoints require authentication:
POST /speakGET /voicesPOST /livekit/tokenGET /livekit/roomsGET /livekit/rooms/{room_name}DELETE /livekit/participantPOST /livekit/participant/muteGET /recording/{stream_id}POST /sip/callGET /sip/hooksPOST /sip/hooksDELETE /sip/hooksPOST /sip/transfer
GET /(health)GET /ws(WebSocket handshake — see the WebSocket Auth roadmap below)
Room-scoped authorization
Beyond authentication, room-scoped endpoints enforce ownership viametadata.auth_id:
| Endpoint | Ownership check |
|---|---|
POST /livekit/token | Creates room with metadata.auth_id if missing; returns 403 if owned by another tenant. |
GET /livekit/rooms | Filters to rooms matching metadata.auth_id. |
GET /livekit/rooms/{room_name} | Returns 404 if metadata.auth_id doesn’t match. |
DELETE /livekit/participant | Returns 404 if metadata.auth_id doesn’t match. |
POST /livekit/participant/mute | Returns 404 if metadata.auth_id doesn’t match. |
POST /sip/call | Creates room with metadata.auth_id; returns 404 if owned by another tenant. |
POST /sip/transfer | Returns 404 if metadata.auth_id doesn’t match. |
Room names are no longer prefixed or modified. Access control is enforced entirely through the
metadata.auth_id field stored in room metadata.WebSocket authentication status
WebSocket auth is not implemented yet. The open tasks from the design document:- Authenticate during the HTTP upgrade by reading query parameters or headers.
- Require an auth payload inside the first
configmessage. - Or leave
/wspublic but scope LiveKit mirroring to trusted rooms.
Usage examples
Client guidance
Recommended default: Use theAuthorization: Bearer <token> header whenever possible. Header-based auth keeps tokens out of URL strings, server logs, and browser history.
When to use query parameter auth: The ?api_key=<token> query parameter is practical in environments where adding custom headers is difficult — for example, browser redirects, webhook callbacks from third-party services, or server-sent event streams.
Backward compatibility
- Existing header-based clients continue to work unchanged.
- Query-based auth is additive and non-breaking.
- Clients currently sending an invalid
Authorizationheader alongside a validapi_keyquery parameter will now authenticate successfully via fallback.
Scope clarification
This dual-input auth behavior applies to protected HTTP endpoints only. It does not change:- The WebSocket auth model (see WebSocket authentication status below).
- The LiveKit webhook signature verification flow, which uses its own signing mechanism independent of bearer tokens.
Provider-level credentials are separate from server-level authentication. In addition to the API secret and JWT strategies described above, Sayna supports per-session and per-request provider credential overrides via the
auth field in STT/TTS config objects. This allows multi-tenant clients to supply their own Deepgram, ElevenLabs, Cartesia, Google, or Azure credentials without changing server configuration. See the WebSocket guide for details.Error responses
| Error code | HTTP status | Description |
|---|---|---|
missing_auth_header | 401 | No usable token was found in either the Authorization header or the api_key query parameter. |
invalid_auth_header | 401 | Header format is invalid and no valid api_key fallback was provided. |
unauthorized | 401 | Token validation failed. |
auth_service_error | 401 or 502 | Auth service returned 4xx/5xx. |
auth_service_unavailable | 503 | Auth service unreachable or timed out. |
config_error | 500 | Misconfigured auth flags/keys. |
jwt_signing_error | 500 | Failed to sign JWT payload. |
Mapping auth service responses
| Auth service status | Sayna response | Meaning |
|---|---|---|
200 OK | 200 OK | Token valid; request proceeds. |
401 Unauthorized | 401 Unauthorized | Invalid token; client should refresh credentials. |
Other 4xx | 401 Unauthorized | Client error mapped to unauthorized. |
5xx | 502 Bad Gateway | Temporary auth service failure. |
| Timeout / network error | 503 Service Unavailable | Auth service unreachable. |
Security considerations
- Private keys: Store private keys with
chmod 600, never commit them, and rotate periodically. - Network security: Run Sayna + auth service over HTTPS/TLS. Consider mutual TLS between the two if you run them across trust zones.
- Token validation: Reject replayed JWTs by checking
iat; combine with short expirations. - Error handling: Return generic error bodies to clients but log detailed errors internally for auditing.
- Replay protection: Compare
iatagainstnow(±60 seconds) before trusting the payload.