STT quickstart

Stream your first audio sample and receive a transcript.
View as Markdown

In this quickstart, you will stream a short PCM audio sample over WebSocket and print its final transcript.

1. Create an API key

Create a key in API Keys and copy it when it is shown. Store it in your server environment:

$export NARI_API_KEY="your-api-key"

Keys are server-side credentials. The authentication guide covers key management and browser applications.

2. Download the sample audio

The example expects raw 16 kHz, mono PCM16 audio. Download this sample saying “Hello, welcome to Nari Labs.”

Download hello.pcm

3. Install the WebSocket client

$python -m pip install "websockets>=14,<16"

4. Create the transcription script

Save the following code as transcribe.py in the same folder as hello.pcm:

1import asyncio
2import base64
3import json
4import os
5from pathlib import Path
6
7from websockets.asyncio.client import connect
8
9URL = "wss://api.narilabs.com/v1/realtime?intent=transcription"
10
11
12async def receive(socket, expected=None):
13 event = json.loads(await socket.recv())
14 if event["type"] == "error":
15 raise RuntimeError(event["error"]["code"] + ": " + event["error"]["message"])
16 if event["type"] == "input_audio_buffer.commit_empty":
17 raise RuntimeError("No pending audio to transcribe")
18 if expected and event["type"] != expected:
19 raise RuntimeError(f"Expected {expected}, received {event['type']}")
20 return event
21
22
23async def main():
24 pcm = Path("hello.pcm").read_bytes()
25
26 async with connect(
27 URL,
28 additional_headers={"Authorization": f"Bearer {os.environ['NARI_API_KEY']}"},
29 ) as socket:
30 await receive(socket, "session.created")
31 await socket.send(json.dumps({
32 "type": "session.configure",
33 "session": {"model": "qwen3-asr:free", "language": "en", "turn_detection": None},
34 }))
35 await receive(socket, "session.configured")
36
37 async def send_audio():
38 for offset in range(0, len(pcm), 3200): # 100 ms
39 chunk = pcm[offset:offset + 3200]
40 await socket.send(json.dumps({
41 "type": "input_audio_buffer.append",
42 "audio": base64.b64encode(chunk).decode("ascii"),
43 }))
44 await asyncio.sleep(len(chunk) / 32000)
45 await socket.send(json.dumps({"type": "input_audio_buffer.commit"}))
46
47 async def read_results():
48 while True:
49 event = await receive(socket)
50 if event["type"] == "transcript.partial":
51 print("Partial:", event["transcript"])
52 elif event["type"] == "transcript.completed":
53 print("Final:", event["transcript"])
54 return
55
56 await asyncio.gather(send_audio(), read_results())
57
58
59asyncio.run(main())

5. Run the script

Run the script using the API key you exported in step 1:

$python transcribe.py

The final output should resemble:

Final: Hello, welcome to Nari Labs.

Partial results may appear first; wording and punctuation can vary.

Why this model

Qwen3-ASR is best suited to streaming transcription applications where low time-to-final-segment (TTFS) matters.

Next steps