AssemblyAI Hub API reference (v3.4.0)
All paths and bodies match the official AssemblyAI API (https://www.assemblyai.com/docs). This file lists only what the Hub adds or changes.
Hosts
| Hub path | Official service |
|---|---|
/v2/* | api.assemblyai.com (pre-recorded) |
/v3/token, /v3/ws | streaming.assemblyai.com (real-time v3) |
/llm-gateway/v1/* | llm-gateway.assemblyai.com |
/sync/v1/* | sync.assemblyai.com |
/dictation/v1/* | dictation.assemblyai.com |
/hub/*, /healthz | Hub only |
EU accounts automatically use the EU hosts.
Auth
Any of: Authorization: hub-..., Authorization: Bearer hub-..., ?token=hub-... (WebSocket). Optional X-Hub-Region: us|eu restricts the request to accounts in that region.
What the Hub rewrites
Authorization→ the chosen upstream account's key.webhook_urland its auth header: the Hub receives AssemblyAI's callback, then POSTs{"transcript_id", "status"}to yourwebhook_urlwith your header (retries 5x on 5xx / network errors). Responses show your webhook settings, not the Hub's.- Legacy
speech_model(singular) →speech_models; retireduniversal-3-pro→universal-3-5-pro.
Everything else passes through, so new AssemblyAI parameters work without a Hub upgrade.
Pre-recorded
| Call | Notes |
|---|---|
POST /v2/upload | Raw bytes. The Hub remembers which account holds the file and submits transcripts that use it on the same account. Official limit ~2.2 GB. |
POST /v2/transcript | JSON body. Response adds nothing; webhook_url shows your own. |
GET /v2/transcript/{id} | Routed to the owning account. Adds webhook_status_code (last status from forwarding to your webhook). |
DELETE /v2/transcript/{id} | Routed to the owning account. |
GET /v2/transcript | From the Hub's own records across all accounts. Params: limit (max 200), status, before_id, after_id. Same shape as official. |
GET /v2/transcript/{id}/{sentences,paragraphs,srt,vtt,word-search,redacted-audio} | Routed to the owning account; query params pass through. |
Other /v2/* | Forwarded to any account. |
Ids not created through this Hub return 404.
Models (as of 2026-10)
universal-3-5-pro: best accuracy, 18 languages incl. Chinese; supportsprompt,temperature,format_text: false.universal-2: 99 languages.- Retired / rejected:
universal-3-pro,slam-1, singularspeech_model,summarization,auto_chapters,summary_model,summary_type. - Some features (e.g.
iab_categories,auto_highlights) are English-centric and error on unsupported languages.
Real-time v3
Direct:
GET /v3/token?expires_in_seconds=60&max_session_duration_seconds=3600
→ {"token": "...", "expires_in_seconds": 60, "websocket_url": "wss://streaming.assemblyai.com/v3/ws"}Then connect to websocket_url?token=...&sample_rate=16000[&speech_model=...]. expires_in_seconds 1–600, max_session_duration_seconds 60–10800. Audio does not pass through the Hub.
Relay: wss://<hub>/v3/ws?sample_rate=16000&format_turns=true&token=hub-...
- Query params (except
token) and all frames pass through both ways: binary PCM audio,UpdateConfiguration,ForceEndpoint,Terminate; eventsBegin,Turn,Termination. - If the client drops without
Terminate, the Hub sends it upstream. - Account chosen once per session; no mid-session failover.
- Close codes:
4001invalid key,4003no upstream account, others are AssemblyAI's.
Minimal relay client (Python, pip install websockets):
import asyncio, json, websockets
async def main(url, pcm_path):
async with websockets.connect(url) as ws:
async def send():
with open(pcm_path, "rb") as f:
while chunk := f.read(3200): # 100 ms of 16 kHz 16-bit mono
await ws.send(chunk); await asyncio.sleep(0.1)
await ws.send(json.dumps({"type": "Terminate"}))
task = asyncio.create_task(send())
async for msg in ws:
e = json.loads(msg)
if e.get("type") == "Turn" and e.get("end_of_turn"):
print(e["transcript"])
await task
asyncio.run(main("wss://hub.example.com/v3/ws?sample_rate=16000&format_turns=true&token=hub-...", "audio.raw"))Convert audio to that format: ffmpeg -i in.mp3 -ac 1 -ar 16000 -f s16le audio.raw.
LLM gateway and speech understanding
POST /llm-gateway/v1/chat/completions: OpenAI-compatible.GET /llm-gateway/v1/modelsfor models. Requires a paid AssemblyAI account.POST /llm-gateway/v1/understandingwith{"transcript_id": ..., "speech_understanding": {"request": {...}}}: routed to the account that owns the transcript. Request types includetranslation(target_languages,formal) andspeaker_identification(speaker_type:nameorrole).
Sync and Dictation
POST /sync/v1/transcribe (≤20 min / 256 MB), POST /sync/v1/transcribe/live (≤120 s WAV/PCM), GET /sync/v1/warm, POST /dictation/v1/transcribe, POST /dictation/v1/transcribe/live. Multipart or binary bodies pass through; any account, with failover.
Hub endpoints
GET /healthz→{"ok": true, "version": "3.4.0"}(no auth)GET /hub/usage→{"key": name, "usage": [{"kind", "events", "seconds", "tokens"}]}; kinds:transcript,stream,token,llm,sync,dictation,apiGET /hub/docs,GET /hub/openapi.jsonwhenALLOW_OPEN_DOCS=true
Errors and failover
| Status | Body | Cause |
|---|---|---|
| 401 | {"detail": {"error": "Missing API key"}} / "Invalid API key" | Hub key |
| 400 | {"detail": {"error": "Body must be JSON"}} | POST /v2/transcript |
| 404 | {"detail": {"error": "Transcript not found"}} | Id unknown to the Hub |
| 503 | {"error": "hub: No upstream account available"} | No usable account |
| 503 | {"error": "hub: All upstream accounts failed"} | Network errors on every tried account |
| other | AssemblyAI's body | Upstream error, passed through |
Failover: 429 → account cools down COOLDOWN_SECONDS; 401/402/403 → account marked error; 5xx and network errors → retried. Up to MAX_RETRY_ACCOUNTS accounts per request. Requests bound to an existing transcript or upload never switch accounts.