Shunya Labs DocsShunya Labs Docs
🌐 International
🇺🇸 English
🇯🇵 Japanese
🇨🇳 Chinese (Simplified)
🇹🇼 Chinese (Traditional)
🇸🇦 Arabic
🇩🇪 German
🇫🇷 French
🇪🇸 Spanish
🇧🇷 Portuguese
🇷🇺 Russian
🇰🇷 Korean
🇹🇷 Turkish
🇻🇳 Vietnamese
🇮🇩 Indonesian
🇮🇳 Hindi Belt
हिन्दी — Hindi
भोजपुरी — Bhojpuri
मैथिली — Maithili
राजस्थानी — Rajasthani
🇮🇳 South India
தமிழ் — Tamil
తెలుగు — Telugu
ಕನ್ನಡ — Kannada
മലയാളം — Malayalam
🇮🇳 West India
मराठी — Marathi
ગુજરાતી — Gujarati
कोंकणी — Konkani
🇮🇳 East India
বাংলা — Bengali
ଓଡ଼ିଆ — Odia
অসমীয়া — Assamese
🇮🇳 North-East India
মেইতেই — Meitei
नेपाली — Nepali
🇮🇳 North India
ਪੰਜਾਬੀ — Punjabi
اردو — Urdu
کٲشُر — Kashmiri
डोगरी — Dogri
سنڌي — Sindhi

API authentication

Two steps. Exchange your API key for a short-lived access token, then send that token to the speech APIs. The key itself is never accepted by ASR or TTS.

Step 1 — get a token

Send your API key to the token endpoint. You get back a JWT that is valid for a short period (typically 15 minutes; the response tells you exactly).

There are two ways to get a token. The quickest is the Generate token button in the console (next to each API key): it mints one and copies it to your clipboard — paste it straight into Postman or a request. Start here. For production code, scripts and automated jobs, call the token endpoint as shown below and reuse the token until it is close to expiry, so your app can refresh it on its own. Both return the same short-lived JWT — use the console button to get going, and the token endpoint for anything that must keep running past a single token's lifetime.

curl -X POST https://app.shunyalabs.ai/api/auth/token \
  -H "api-key: $SHUNYALABS_API_KEY" \
  -H "accept: application/json"

# {"token": "eyJhbGciOiJSUzI1NiIs...", "expires_in": 900, "expires_at": "..."}
import os, requests

auth = requests.post(
    "https://app.shunyalabs.ai/api/auth/token",
    headers={"api-key": os.environ["SHUNYALABS_API_KEY"], "accept": "application/json"},
)
auth.raise_for_status()
access_token = auth.json()["token"]
const auth = await fetch("https://app.shunyalabs.ai/api/auth/token", {
  method: "POST",
  headers: { "api-key": process.env.SHUNYALABS_API_KEY, accept: "application/json" },
});
const { token: accessToken } = await auth.json();

Endpoint — POST /api/auth/token

POST https://app.shunyalabs.ai/api/auth/token

Request headers

HeaderRequiredDescription
api-keyYesYour API key from the console. This is the only place it is sent — never to ASR or TTS.
acceptNoapplication/json.

Query parameters

ParameterTypeDefaultDescription
expires_ininteger (seconds)900Requested token lifetime in seconds. Defaults to 900 (15 minutes) and is clamped to a maximum of 86400 (24 hours).

Response200

{
  "token": "eyJhbGciOiJSUzI1NiIs...",
  "expires_in": 900,
  "expires_at": "2026-08-18T02:15:00Z"
}
FieldTypeDescription
tokenstringThe RS256 JWT — send it as Authorization: Bearer to ASR and TTS.
expires_inintegerSeconds until the token expires.
expires_atstringAbsolute expiry, ISO 8601.

Errors

StatusMeaning
400The api-key header is missing or malformed.
403The API key was refused — wrong, revoked, or the account balance is too low. Retrying will not help; check the key in the console.
429Too many mint requests — the limit is 60 per minute per API key. Reuse the token you already have instead of minting one per request.

Step 2 — call the API

Send the token as a bearer credential. This is the same for ASR and TTS.

Authorization: Bearer $ACCESS_TOKEN
curl -X POST https://asrv2prod.shunyalabs.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "file=@call.wav" \
  -F "language_code=en"
curl -X POST https://ttsv2.shunyalabs.ai/v1/audio/speech \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"input": "Hello", "voice": "Meera", "language": "en"}' \
  --output speech.wav

Send the token as an Authorization header on the upgrade. Browsers cannot set headers on a WebSocket, so a query parameter is accepted as a fallback — anything in a URL is written to access logs in plaintext, so use it only from a browser and only with a short-lived token.

wss://ttsv2.shunyalabs.ai/v1/realtime?token=$ACCESS_TOKEN

Why two steps

An API key is long-lived and identifies your account for as long as it exists. An access token expires in minutes. Keeping the key in your server and putting only the token on the wire means a token captured in a log, a proxy or a browser stops working almost immediately, while the key never leaves your control.

Mint on your server, not in a browser or a mobile app. Anything that ships the API key to a client has handed out the long-lived credential to get the short-lived one, which gives up the entire benefit.

Reuse a token until it is close to expiry rather than minting per request — the response carries expires_in and expires_at for exactly that. Minting is rate limited to 60 requests per minute per key, and a token is not cheaper to obtain than to keep.

When a token is rejected

A 401 from ASR or TTS means the token is missing, malformed or expired — mint a new one and retry once. A 403 from the token endpoint means the API key itself was refused: it is wrong, revoked, or the account balance is too low. Retrying will not help; check the key in the console.

Authentication | Shunya Labs API Reference | Shunya Labs Docs