Skip to content

Speech

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

Access: Keys with the media:generate scope, on an account with media access granted by Albastar Corp.

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

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_…; without model, or with null, the voice's default_model runs
response_formatstringmp3 (default), wav or opus
speednumber0.5 to 2.0, default 1.0. Used by kokoro and omnivoice
instructionsstringA description of the voice or the delivery, 500 characters at most, sent to the model as instruct. Only breeze2 uses it on this route: omnivoice drops instruct when given a voice, and this route always sends one. Other models drop the field
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 such as Narrator A, vo_tBeDCRb2byGFjB6HGyQq6a
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 out or nullThe 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 of the same key with the same Idempotency-Key and body returns the same job's file, or the same job's error. Without the header, an identical request of the same key in the same 10-minute window returns the first request's job, unless that job failed, expired or was cancelled: then the request runs as a new job. The windows start at :00, :10, :20, :30, :40 and :50 past the hour, UTC
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-Trace-IdResponse, with X-Job-Id. The job's trace_id, 32 lowercase hex characters
Server-TimingResponse, with X-Job-Id. One entry <name>;dur=<ms>: whole milliseconds from the request's arrival to its job being queued
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 $ALBASTAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "kokoro", "voice": "af_heart", "response_format": "mp3",
       "input": "The river bends past the old mill."}' \
  --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["ALBASTAR_API_KEY"],
)

with client.audio.speech.with_streaming_response.create(
    model="kokoro",
    voice="af_heart",
    response_format="mp3",
    input="The river bends past the old mill.",
) 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.ALBASTAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "kokoro",
    voice: "af_heart",
    response_format: "mp3",
    input: "The river bends past the old mill.",
  }),
});
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_ZVBEyxyXPUJ7H2VMWtS8Xs
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. A limit of the model's schema names the job parameter: text for input, instruct for instructions
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 generation time is used up: That would go over today's generation time. It starts over at midnight UTC.; resets_at is the next 00:00 UTC
429too-many-openThe key has 1,000 jobs queued or running, the limit per key: You have 1000 jobs queued or running; that's the limit.
502The job's error codeThe job failed or was cancelled; job_id names the job
503queuedThe job is still queued: The job is still queued. It stays in /v1/media/jobs; poll GET /v1/media/jobs/<id>?wait=30 for it.; job_id names the job
503queue-timeoutThe job waited in the queue past its queue timeout and has expired.
503voice_model_unavailableNo model that speaks saved voices is in the model list, for a request without model

Key, scope and rate-limit errors: Errors.