Soniox
Guides

Temporary API keys

Short-lived credentials that let untrusted clients connect directly to Soniox without exposing your long-lived API key.

Overview

A long-lived Soniox API key authenticates against your account and bills usage to it. If it leaks (embedded in a client app, captured in transit, or shared by mistake), anyone can spend your credits until you rotate the key.

Temporary API keys are short-lived credentials your backend mints from a long-lived key and hands to a client. They are the only safe way for an untrusted client to connect directly to Soniox. Usage is still billed to the issuing account, so each key carries restrictions that limit what a leaked or replayed key can do:

  • usage_type: which Soniox service the key is valid for (always required).
  • expires_in_seconds: how long the key can be used to open new streams (always required).
  • single_use: the key may open at most one stream.
  • max_session_duration_seconds: the maximum duration of any one stream opened with the key.
  • client_reference_id: tracking identifier bound to the key; appears in usage logs for every request authenticated with it.

The flow is always the same:

  1. Client requests a temporary key from an endpoint on your backend.
  2. Backend uses its long-lived API key to call Soniox and returns the temporary key to the client.
  3. Client opens a stream directly to Soniox using the temporary key.

See the direct stream guide for a complete client/server example.


Creating a temporary API key

Run this code on a backend server you control, never in the client. The long-lived API key must never leave your server.

The minimal request needs only usage_type and expires_in_seconds. See the API reference for the full request shape.

# Server-side endpoint your client calls
@app.post("/temporary-api-key")
def create_temporary_api_key():
    response = requests.post(
        "https://api.soniox.com/v1/auth/temporary-api-key",
        headers={
            "Authorization": f"Bearer {SONIOX_API_KEY}",
            "Content-Type": "application/json",
        },
        json={
            "usage_type": "transcribe_websocket",
            "expires_in_seconds": 60,
        },
    )
    response.raise_for_status()
    return response.json()

Our SDKs provide helpers for the same call:

  • Node SDK: client.auth.createTemporaryKey(...)
  • Python SDK: client.auth.create_temporary_api_key(...)

Usage type

usage_type is required and locks the key to exactly one Soniox service:

A key issued for one service cannot be used against the other. Issue a separate key for each service the client needs.


Expiration

expires_in_seconds controls how long the key can be used to open new streams. It does not terminate streams that are already open. Use max_session_duration_seconds (below) to bound an individual session.


Single use

When single_use is true, the temporary API key may be used to open a stream exactly once. Any later attempt to open a stream with the same key is rejected, even if the key has not yet expired.

Use this whenever the client is expected to perform a single transcription or speech generation session. It ensures that a key intercepted in transit cannot be reused to start additional billable sessions.

{
  "usage_type": "transcribe_websocket",
  "expires_in_seconds": 60,
  "single_use": true
}

Max session duration

max_session_duration_seconds sets the maximum duration of a single stream opened with the temporary API key. The timer starts when the stream is opened (not when the underlying connection is established). Omitting the field disables the limit.

The limit applies per stream. On a multi-stream connection, each stream is timed independently using the value carried by the temporary API key.

{
  "usage_type": "tts_rt",
  "expires_in_seconds": 300,
  "max_session_duration_seconds": 60
}

Set max_session_duration_seconds based on the longest legitimate session you expect. A value that is too low will cut off real users; a value that is too high reduces protection if the key is leaked.

For text-to-speech, the limit caps how long the stream stays open, not the duration of generated audio. In practice, TTS streams audio at roughly real-time pace, so a stream-duration cap also bounds how much audio a single session can produce.

When the limit is reached, the stream is terminated with HTTP status 403 and the message Temporary API key session duration limit exceeded. How that error is delivered depends on the endpoint.

Speech-to-Text WebSocket

/transcribe-websocket carries one stream per connection. When the session duration limit is reached, the server sends a final JSON response and closes the WebSocket with StatusNormalClosure:

{
  "error_code": 403,
  "error_message": "Temporary API key session duration limit exceeded."
}

Text-to-Speech WebSocket

/tts-websocket supports multiple streams over a single connection. The limit is enforced per stream. Each start_stream starts its own timer using the value from the temporary API key.

When a stream's limit is reached, the server returns a stream-scoped error response followed by a terminated message for that stream_id. Other streams on the same connection are unaffected and the connection stays open:

{
  "stream_id": "...",
  "error_code": 403,
  "error_message": "Temporary API key session duration limit exceeded."
}

Text-to-Speech REST

/tts serves a single stream per request. How the error is reported depends on whether audio bytes have already been sent:

  • Before any audio has been sent, a standard HTTP error response:
HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error_code": 403,
  "error_message": "Temporary API key session duration limit exceeded."
}
  • After audio has started streaming, the response status remains 200 OK (the headers and Content-Type: audio/... are already sent), and the error is delivered through the announced HTTP trailers:
X-Tts-Error-Code: 403
X-Tts-Error-Message: Temporary API key session duration limit exceeded.

Tracking

Bind a client_reference_id to the temporary API key at creation time. Every request authenticated with the key is recorded in usage logs with that identifier — clients cannot override it.

{
  "usage_type": "transcribe_websocket",
  "expires_in_seconds": 60,
  "client_reference_id": "user_8f2c4b1a"
}