Get started
Quickstart
Create an API key, turn text into speech, then queue an image and poll it until it is ready.
Three steps: a key, a request that answers with audio, and a request that is queued and polled.
1. Get an API key
Section titled “1. Get an API key”Sign in to the console at https://app.lumavox.ai and open API keys. Your organisation’s master key is there, and you can create keys limited to the capabilities they need. A scoped key is what you want in production — see authentication.
Keep the key on your server and out of source control. The examples read it from an environment variable:
export API_KEY="paste-your-key-here"2. Turn text into speech
Section titled “2. Turn text into speech”POST /v1/tts answers with the audio itself — a WAV file unless you ask for something else.
curl https://api.lumavox.ai/v1/tts \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "Hello from the API."}' \ --output hello.wav// Node 18 or later: fetch is built in.import { writeFile } from "node:fs/promises";
const response = await fetch("https://api.lumavox.ai/v1/tts", { method: "POST", headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ text: "Hello from the API." }),});if (!response.ok) { const { error } = await response.json(); throw new Error(`${response.status} ${error.code}: ${error.message}`);}await writeFile("hello.wav", Buffer.from(await response.arrayBuffer()));console.log("saved hello.wav, request", response.headers.get("X-Request-Id"));import os
import requests
response = requests.post( "https://api.lumavox.ai/v1/tts", headers={"Authorization": f"Bearer {os.environ['API_KEY']}"}, json={"text": "Hello from the API."},)response.raise_for_status()with open("hello.wav", "wb") as f: f.write(response.content)print("saved hello.wav, request", response.headers["X-Request-Id"])Play hello.wav and you have your first generation.
With no voice_id you get the built-in voice. GET /v1/voices lists every voice your key may use; pass one’s id as voice_id:
curl https://api.lumavox.ai/v1/voices -H "Authorization: Bearer $API_KEY"Every response carries an X-Request-Id header. Keep it: it is what to quote when something goes wrong. Speech responses also say how long the audio is (X-Audio-Seconds), which model spoke (X-Model) and the rate it was produced at (X-Sample-Rate). Text to speech covers models, formats and streaming.
3. Queue an image and poll it
Section titled “3. Queue an image and poll it”Images, video, stored audio, skyboxes and long transcriptions take longer than a request should wait. They answer 202 Accepted at once with a gen_… id, and you poll that id until it finishes.
curl https://api.lumavox.ai/v1/images \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "A weathered brass diving helmet on wet slate, low winter light"}'The response describes the job — what you asked for, what it cost, and where it stands:
{ "id": "gen_a1B2c3D4e5F6g7H8", "status": "queued", "capability": "image", "model": "flux-2-max", "prompt": "A weathered brass diving helmet on wet slate, low winter light", "aspect_ratio": "1:1", "width": 992, "height": 992, "megapixels": 1, "output_format": "jpeg", "credits_micros": 4000000, "image": null, "error": null, "created_at": "2026-10-24T09:30:00.000Z", "finished_at": null}Every response writes values the same way: times in UTC with milliseconds, credits as integers of micro-credits (4000000 is four credits), and null for a field with nothing in it. Conventions has the rules.
Poll it every couple of seconds:
curl https://api.lumavox.ai/v1/images/gen_a1B2c3D4e5F6g7H8 \ -H "Authorization: Bearer $API_KEY"queued, submitted and running mean keep polling. succeeded, failed and moderated are final. Once it has succeeded, the image is on the response:
{ "id": "gen_a1B2c3D4e5F6g7H8", "status": "succeeded", "capability": "image", "image": { "asset_id": "ast_x9Y8z7W6v5U4t3S2", "url": "https://…", "width": 992, "height": 992, "content_type": "image/jpeg", "size_bytes": 284113 }, "error": null, "created_at": "2026-10-24T09:30:00.000Z", "finished_at": "2026-10-24T09:30:07.412Z"}image.url is a short-lived link — download the file promptly. The image is also kept in your library under image.asset_id, which is where to fetch it from later.
The same loop in code:
const headers = { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json",};
const submitted = await fetch("https://api.lumavox.ai/v1/images", { method: "POST", headers, body: JSON.stringify({ prompt: "A lighthouse in fog, oil painting" }),}).then((r) => r.json());
let job = submitted;while (["queued", "submitted", "running"].includes(job.status)) { await new Promise((resolve) => setTimeout(resolve, 2000)); job = await fetch(`https://api.lumavox.ai/v1/images/${submitted.id}`, { headers }).then( (r) => r.json(), );}console.log(job.status, job.image?.url ?? job.error);import osimport time
import requests
headers = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
job = requests.post( "https://api.lumavox.ai/v1/images", headers=headers, json={"prompt": "A lighthouse in fog, oil painting"},).json()
while job["status"] in ("queued", "submitted", "running"): time.sleep(2) job = requests.get(f"https://api.lumavox.ai/v1/images/{job['id']}", headers=headers).json()
print(job["status"], job["image"]["url"] if job["image"] else job["error"])A queued job is charged when it is accepted and refunded in full if it fails. Queued jobs has the details.
Next steps
Section titled “Next steps”- Authentication — scoped keys, and ephemeral keys for your users’ devices.
- Conventions — how every endpoint writes times, credits and lists, and how it pages.
- Errors and limits — the error envelope and what each status means.
- Text to speech — models, formats, sample rates and streaming.
- Agents — characters that hold a spoken conversation.
- API reference — every endpoint and field.