# Stored audio

> Queue speech with POST /v1/audio for mp3, Opus or WAV files up to 5,000 characters, kept in your asset library.

`POST /v1/audio` turns text into an audio **file**: it is queued, spoken, stored in your [asset library](https://docs.lumavox.ai/library/assets.md), and you poll for it. Use it when you want mp3 or Opus, longer text, or a file to keep.

```bash title="curl"
curl https://api.lumavox.ai/v1/audio \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Welcome to the tour. Let us begin in the east wing.", "format": "mp3"}'
```

It answers **202** straight away with a generation to poll:

```json title="Response — 202 (abridged)"
{
  "id": "gen_a1B2c3D4e5F6g7H8",
  "status": "queued",
  "capability": "tts",
  "model": "eleven_flash_v2_5",
  "prompt": "Welcome to the tour. Let us begin in the east wing.",
  "output_format": "mp3",
  "voice_id": "default",
  "credits_micros": 125000,
  "audio": null,
  "error": null,
  "created_at": "2026-10-24T09:30:00.000Z",
  "finished_at": null
}
```

The text you sent is the generation's `prompt`, and the format its `output_format`. Every other field of a generation is there too, `null` where it does not apply to speech; the full shape is in the [reference](https://docs.lumavox.ai/reference/audio.md).

## When to use it instead of `/v1/tts`

| | `POST /v1/tts` | `POST /v1/audio` |
|---|---|---|
| Answer | the audio, in the response | a generation id, then poll |
| Formats | `wav`, `pcm` | `mp3` (default), `opus`, `wav` |
| Text | up to 2,000 characters | up to 5,000 characters |
| Streaming | yes, `pcm` | no |
| Stored | no | yes, in your asset library |

Use [text to speech](https://docs.lumavox.ai/speech/text-to-speech.md) for anything someone is waiting to hear. Use this for files: narration, downloads, anything you would otherwise have to encode and upload yourself.

## The request

It takes the same voice fields as `POST /v1/tts`, plus a destination folder.

| Field | Default | What it does |
|---|---|---|
| `text` | — | What to say. 1 to 5,000 characters. |
| `voice_id` | `default` | An `id` from `GET /v1/voices`. |
| `model` | `eleven_flash_v2_5` | One of the voice's `models`. |
| `format` | `mp3` | `mp3`, `opus` or `wav`. |
| `provider_options` | `null` | Voice settings for this request — see [tuning the voice](https://docs.lumavox.ai/speech/text-to-speech.md#tuning-the-voice). |
| `folder_id` | root | `fld_…`, the library folder the file is filed under. A folder that is not yours is a 422. |

What each format is:

| `format` | File |
|---|---|
| `mp3` | MPEG audio, 44.1 kHz, 128 kbps (`audio/mpeg`) |
| `opus` | Opus in an Ogg container, 48 kHz, 64 kbps (`audio/ogg`) |
| `wav` | 16-bit mono PCM at 24 kHz, with a WAV header (`audio/wav`) |

`sample_rate` has no effect on this endpoint: each format is produced at the rate in the table.

The key needs the `tts` scope — the same scope as `/v1/tts`.

## Polling

Poll `GET /v1/audio/{generation_id}` every couple of seconds until `status` is terminal.

```bash title="curl"
curl https://api.lumavox.ai/v1/audio/gen_a1B2c3D4e5F6g7H8 \
  -H "Authorization: Bearer $API_KEY"
```

| `status` | Meaning |
|---|---|
| `queued`, `submitted`, `running` | Still working. Keep polling. |
| `succeeded` | Done. The file is on `audio`. |
| `failed` | Nothing was produced. `error` says why. |
| `moderated` | Refused by the provider's safety filter. |

When it has succeeded, `audio` describes the file:

```json title="Response — 200 (abridged)"
{
  "id": "gen_a1B2c3D4e5F6g7H8",
  "status": "succeeded",
  "capability": "tts",
  "audio": {
    "asset_id": "ast_x9Y8z7W6v5U4t3S2",
    "url": "https://…",
    "duration_seconds": 3.9,
    "content_type": "audio/mpeg",
    "size_bytes": 62853,
    "peaks": [12, 40, 87, 63]
  },
  "error": null,
  "created_at": "2026-10-24T09:30:00.000Z",
  "finished_at": "2026-10-24T09:30:03.218Z"
}
```

- `url` is a short-lived link. It is minted for each response and not stored: fetch the generation, or the asset, again for a fresh one.
- `asset_id` is the file's permanent home in your [asset library](https://docs.lumavox.ai/library/assets.md).
- `duration_seconds` and `peaks` (a waveform for drawing) are measured after the file is stored, and can be `null` when a file could not be measured.

Polling is free and does not count against your rate limit — it is the intended pattern. See [Queued jobs](https://docs.lumavox.ai/jobs.md) for the general rules, and [Generations](https://docs.lumavox.ai/library/generations.md) to find an id you lost.

## Cost

**Charged when accepted, not when finished**, per thousand characters at the rate of the model you chose. The charge is on the generation as `credits_micros`, an integer of micro-credits ([Conventions](https://docs.lumavox.ai/conventions.md#credits)). A generation that fails is refunded in full. See [Credits and usage](https://docs.lumavox.ai/usage.md).

## Errors

The common envelope is described in [Errors and limits](https://docs.lumavox.ai/errors.md). The ones specific to this endpoint:

| Status | When |
|---|---|
| 400 | The body is not valid JSON. |
| 422 | `folder_id` is not one of your folders. |
| 402 | The organisation is out of credits, or this job's estimate would take its balance past the grace buffer. Nothing is charged. |
| 404 | `voice_id` is unknown or not usable; or, when polling, the generation id is unknown, not yours, or not a speech generation. |
| 422 | The body failed validation: `text` empty or over 5,000 characters, an unknown `format` or field, or a `model` that is not one of the voice's `models`. `issues` names the field. |
| 429 | Over your organisation's rate limit. Nothing was charged. |
| 503 | Speech is not set up on this deployment. |

## Related

- [Text to speech](https://docs.lumavox.ai/speech/text-to-speech.md) — speech in the response, and streaming.
- [Queued jobs](https://docs.lumavox.ai/jobs.md) — how every queued capability works.
- [Reference: audio](https://docs.lumavox.ai/reference/audio.md)
