Realtime speech to text is in preview and is enabled per team. Contact
support@fish.audio to turn it on for your team.
Until then, connecting returns
403.Prerequisites
Create a Fish Audio account
Create a Fish Audio account
Sign up for a free Fish Audio account to get started with our API.
- Go to fish.audio/auth/signup
- Fill in your details to create an account, complete steps to verify your account.
- Log in to your account and navigate to the API section
Get your API key
Get your API key
Once you have an account, you’ll need an API key to authenticate your requests.
- Log in to your Fish Audio Dashboard
- Navigate to the API Keys section
- Click “Create New Key” and give it a descriptive name, set a expiration if desired
- Copy your key and store it securely
ffmpeg -i input.mp3 -ac 1 -ar 16000 -c:a pcm_s16le speech.wav.
Recipe
The script connects towss://api.fish.audio/v1/asr/realtime, sends a config message, waits for session_started, and then streams the file in 100 ms messages at real-time speed, as a live microphone would. It prints every message the server sends until the session ends.
data as the raw PCM bytes, which must travel as MessagePack bin, not as base64 text: msgpack for Python packs bytes as bin by default (do not set use_bin_type=False), and @msgpack/msgpack packs a Buffer or Uint8Array as bin.
The server closes a session after 30 seconds without any client message, but a WebSocket ping counts as one, and Python’s websockets sends a ping every 20 seconds by default, so the session stays open even if your audio stops; see Capture audio well for a live source that can stall.
Run it with your API key in FISH_API_KEY:
... marks drafts left out):
audio_done. Drafts can hold partial words, such as deli-; the committed text is final.
Read the events
speech_startopens a turn when voice activity begins. In the defaultautomode, noise and other voices open turns too.draftis the whole turn so far, and replaces the previous draft. Drafts come only withresults.drafts: true; use them for live captions.user_turn_takensays the server judged that the user is taking their turn, rather than noise or someone else nearby.eager_end_of_turncomes withturn_detection.eager: truewhen the turn has probably ended. Its text equals the final transcript unlessturn_resumedfollows, so an agent can start its reply early.committedis the final transcript of the turn.user_turn: falsemarks a non-user turn, such as background noise or a short acknowledgement while your agent is speaking. Speaker tags such as<|speaker:0|>are stable within the session; strip them and trim the text, or sendresults.diarize: false. Text can also contain sound tags such as[laughter].language_codeis omitted when the text is too short to tell, as for “Uh-huh.” above.session_donefollowsaudio_done, and the server then closes the connection with code 1000.
Capture from a browser microphone
The API key travels in a request header, and browsers cannot set headers on a WebSocket. Never put your API key in a web page: run a small relay on your server that adds theAuthorization header and passes MessagePack frames through unchanged. The page then speaks the same protocol as the scripts above.
aiohttp, put both files in one folder, run python relay.py, and open http://localhost:8080.
- The page captures audio at the device’s own sample rate and sends that rate in
config; the server converts it, so the page does not resample. The rate must be one of the supported rates. - An
AudioWorkletconverts the samples to 16-bit PCM and posts about 100 ms per message. - Stop sends
audio_done, so the server commits the last turn before it closes the connection. - The relay forwards the API’s close code to the page. In production, add your own user authentication, per-user session limits and input checks to the relay.

