Skip to content

Speech

POST /v1/audio/speech returns an audio file of the text in a built-in or saved voice.

POST /v1/audio/speech returns the audio file of a text spoken in a voice. The request and response shapes are OpenAI's. The key needs the media:generate scope.

Parameters

A JSON body. A field outside the list fails with 400 bad-param. A parameter the model has no use for is dropped, as on OpenAI.

NameTypeDescription
input requiredstringThe text to speak. At most 5,000 characters; 20,000 on kokoro
voice requiredstringA built-in voice of the model, or a voice reference (table below)
modelstringA speech model id, such as kokoro, omnivoice or qwen3-tts, or OpenAI's tts-1 or tts-1-hd. Required unless voice is a saved voice vo_…, whose default_model then runs
response_formatstringmp3 (default), wav or opus
speednumber0.5 to 2.0, default 1.0. Used by kokoro and omnivoice
instructionsstringA description of the delivery, passed to models with an instruct parameter (breeze2; omnivoice ignores the field when voice is given)
stream_formatstringaudio only: the whole file in one response
voice formValue
Built-in nameA voice of the model, such as af_heart on kokoro; OpenAI's names (alloy, nova, ...) map to Kokoro voices
vo_…A saved voice of the account, or a shared voice
up_…An upload of the account (POST /v1/media/uploads)
job:<id>/<i>Result i of a finished job of the account
https://…A clip fetched when the request arrives, 20 MB at most, from public hosts only
modelRuns as
tts-1kokoro; omnivoice when voice is a voice reference
tts-1-hdkokoro; qwen3-tts when voice is a voice reference
Left outThe saved voice's default_model

Speech models and each model's parameters: GET /v1/media/models?kind=speech (Media jobs reference).

Headers

NameDescription
Idempotency-KeyRequest. A request with the same key and body returns the same job's file. Without the header, an identical request in the same 10-minute clock window returns the first request's job; a job that failed, expired or was cancelled runs again
X-Queue-TimeoutRequest. Seconds the job may wait in the queue, a whole number of 1 or more; a value above the kind's queue timeout is lowered to the queue timeout
Content-TypeResponse. audio/mpeg, audio/wav or audio/ogg
X-Job-IdResponse. The media job mj_… that made the file
X-Queue-TimeoutResponse. The job's queue timeout in seconds
X-Duration-SResponse. The length of the audio in seconds
Retry-AfterResponse, on 503. 5
X-Should-RetryResponse, on 503 queued and 503 queue-timeout. false: OpenAI SDKs do not repeat the request

Request

curl

curl https://api.albastov.com/v1/audio/speech \
  -H "Authorization: Bearer $ALBASTOV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "kokoro", "voice": "af_heart", "input": "The river bends past the old mill.", "response_format": "mp3"}' \
  --dump-header - --output river.mp3

Python

import os
from openai import OpenAI

client = OpenAI(base_url="https://api.albastov.com/v1", api_key=os.environ["ALBASTOV_API_KEY"])

with client.audio.speech.with_streaming_response.create(
    model="kokoro", voice="af_heart", input="The river bends past the old mill.", response_format="mp3"
) as res:
    print(res.headers["x-job-id"], res.headers["x-duration-s"])
    res.stream_to_file("river.mp3")

JavaScript

import { writeFile } from "node:fs/promises";

const res = await fetch("https://api.albastov.com/v1/audio/speech", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ALBASTOV_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "kokoro", voice: "af_heart", input: "The river bends past the old mill.", response_format: "mp3",
  }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(res.headers.get("x-job-id"), res.headers.get("x-duration-s"));
await writeFile("river.mp3", Buffer.from(await res.arrayBuffer()));

Response

200 with the audio file as the body.

Response
HTTP/2 200
content-type: audio/mpeg
x-job-id: mj_HSRJJxSiza6v7xZULYpwgh
x-queue-timeout: 300
x-duration-s: 2.360

The request waits for its job up to the job's queue timeout while the job is queued, and at most 85 s from arrival. An account holds 16 waiting requests at most; past that, the route answers 503 queued at once. The job stays in GET /v1/media/jobs/{id} after a 503.

Errors

Errors use OpenAI's shape, {"error": {"message", "type", "param", "code"}}, plus job_id when a job exists. Branch on code.

StatusCodeMeaning
400bad-paramA field is missing, unknown or out of range; param names the field (input, voice, model, response_format, stream_format, speed, X-Queue-Timeout)
400bad-jsonThe body is not a JSON object
404voice_not_foundNo saved voice with the id, or a voice the key may not use
409voice_not_readyA designed voice still pending, or failed
413too-largeThe body is over 4 MB
422voice_not_supportedThe model does not speak saved voices; models lists the models that do
429budget-exhaustedThe account's daily media time is used up; resets_at is the next 00:00 UTC
429too-many-openThe key has 1,000 jobs queued or running
502The job's error codeThe job failed or was cancelled; job_id names the job
503queuedThe job is still queued; job_id names the job
503queue-timeoutThe job waited past its queue timeout and expired
503voice_model_unavailableNo model that speaks saved voices is online for a request without model

Key, scope and rate-limit errors: Errors.