OpenAI TTS alternative: keep the SDK, change the base URL
Fish Audio serves the same /v1/audio/speech call. Point your OpenAI client at Fish, pick the free model, and your speech code keeps running after OpenAI's TTS models are gone.
No credit card required: fish-audio/s2.1-pro-free costs $0 through November 30, 2026.
Two lines move the client
base_url and api_key point the official OpenAI SDK at Fish. In the call, set a Fish model and voice. The new code runs as-is on a fresh account with a $0 balance.
import os from openai import OpenAI client = OpenAI(Added line: base_url="https://api.fish.audio/compat/v1",Removed line: api_key=os.environ["OPENAI_API_KEY"],Added line: api_key=os.environ["FISH_API_KEY"],) with client.audio.speech.with_streaming_response.create(Removed line: model="tts-1",Removed line: voice="alloy",Added line: model="fish-audio/s2.1-pro-free",Added line: voice="", input="Hello, world!", response_format="mp3",) as response: response.stream_to_file("speech.mp3")import fs from "node:fs";import OpenAI from "openai"; const client = new OpenAI({Added line: baseURL: "https://api.fish.audio/compat/v1",Removed line: apiKey: process.env.OPENAI_API_KEY,Added line: apiKey: process.env.FISH_API_KEY,}); const response = await client.audio.speech.create({Removed line: model: "tts-1",Removed line: voice: "alloy",Added line: model: "fish-audio/s2.1-pro-free",Added line: voice: "", input: "Hello, world!", response_format: "mp3",});fs.writeFileSync("speech.mp3", Buffer.from(await response.arrayBuffer()));Removed line: curl https://api.openai.com/v1/audio/speech \Removed line: -H "Authorization: Bearer $OPENAI_API_KEY" \Added line: curl https://api.fish.audio/compat/v1/audio/speech \Added line: -H "Authorization: Bearer $FISH_API_KEY" \ -H "Content-Type: application/json" \ -d '{Removed line: "model": "tts-1",Removed line: "voice": "alloy",Added line: "model": "fish-audio/s2.1-pro-free",Added line: "voice": "", "input": "Hello, world!", "response_format": "mp3" }' \ --output speech.mp3Needs the official OpenAI SDK (pip install openai or npm install openai) and your Fish API key in FISH_API_KEY.
What else changes
voicetakes a Fish voice ID. OpenAI preset names such asnovareturn a 400; an empty string uses the model's default voice.response_formatdefaults topcmon Fish, notmp3. Set it explicitly.instructionsis not supported and returns a 400. Write delivery cues into the text instead, such as[whispering].tts-1,tts-1-hdandgpt-4o-mini-ttsstill work as model names. They map to the paidfish-audio/s2.1-pro.
Pick a voice
OpenAI's preset voices don't exist on Fish. Pass the ID of any voice in the Fish Voice Library, or of a voice you clone. An empty string uses the model's default voice.
Pricing
S2.1-Pro bills by UTF-8 bytes of input text. In English one character is one byte, so it costs the same as tts-1 and half as much as tts-1-hd. Most Chinese, Japanese and Korean characters take 3 bytes.
Prices from the official pricing pages, checked October 29, 2026:Fish AudioOpenAI
Frequently asked questions
Switch before January 6, 2027
Create a free key, change two lines, and keep shipping speech.