# Audio Generation API

Text-to-speech and sound-effect generation with POST /api/v1/speech and POST /api/v1/sound-effects, plus the public voice catalog.

Speech and sound effects render before the call returns: the response usually already has `status: "success"` and the audio URL in `data.taskUrls`. Keep the connection open until it responds. The returned `data.id` also works with [`GET /api/v1/tasks/{taskId}`](https://viewmax.studio/docs/api/tasks) if you prefer one polling flow.

## List voices

`GET /api/v1/voices` is public (no API key) and cached for up to an hour.

```bash
curl https://viewmax.studio/api/v1/voices
```

```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "default_model": "eleven_v3",
    "characters_per_credit": 20,
    "minimum_credits": 1,
    "voices": [
      {
        "id": "EXAVITQu4vr4xnSDxMaL",
        "name": "Sarah",
        "gender": "female",
        "language": "en",
        "languages": ["en", "fr", "ar", "zh", "es", "hi"],
        "accent": "american",
        "use_case": "entertainment_tv",
        "description": "Young adult woman with a confident and warm, mature quality and a reassuring, professional tone.",
        "preview_url": "https://…/preview.mp3",
        "recommended_model": "eleven_v3"
      }
    ]
  }
}
```

| Field                   | Description                                                                                                                                   |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `default_model`         | Model used when a voice has no `recommended_model`                                                                                            |
| `characters_per_credit` | Speech costs `max(minimum_credits, ceil(text characters / characters_per_credit))`                                                            |
| `minimum_credits`       | Lowest price of one speech request                                                                                                            |
| `voices[]`              | `id` (send as `voice_id`), `name`, `gender`, `language`, `languages`, `accent`, `use_case`, `description`, `preview_url`, `recommended_model` |

## Text to speech

`POST /api/v1/speech` converts text to speech with a selected voice. It costs `max(1, ceil(characters / 20))` credits, counted on the trimmed text: 19 characters cost 1 credit, 1,000 characters cost 50. Plan allowances do not apply to speech on the API.

| Field      | Required | Type   | Description                            |
| ---------- | -------- | ------ | -------------------------------------- |
| `text`     | Yes      | string | Non-empty text to speak; trimmed       |
| `voice_id` | Yes      | string | A voice `id` from `GET /api/v1/voices` |
| `options`  | No       | object | See below                              |

| Option             | Type   | Description                                                                                                          |
| ------------------ | ------ | -------------------------------------------------------------------------------------------------------------------- |
| `model_id`         | string | Defaults to the voice's `recommended_model`. An unknown value fails at the provider (the task fails and is refunded) |
| `speed`            | number | 0.25–4; values outside the range are clamped                                                                         |
| `stability`        | number | 0–1; values outside the range are clamped                                                                            |
| `similarity_boost` | number | 0–1; values outside the range are clamped                                                                            |

```bash
curl -X POST https://viewmax.studio/api/v1/speech \
  -H "Authorization: Bearer $VIEWMAX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: welcome-001" \
  -d '{
    "text": "Welcome to ViewMax.",
    "voice_id": "EXAVITQu4vr4xnSDxMaL",
    "options": { "speed": 1.0 }
  }'
```

## Sound effects

`POST /api/v1/sound-effects` generates a sound effect from a text prompt. Each sound effect costs 5 credits.

| Field     | Required | Type   | Description                        |
| --------- | -------- | ------ | ---------------------------------- |
| `prompt`  | Yes      | string | Non-empty description of the sound |
| `options` | No       | object | See below                          |

| Option             | Type   | Description                |
| ------------------ | ------ | -------------------------- |
| `duration_seconds` | number | JSON number from 0.5 to 22 |
| `prompt_influence` | number | JSON number from 0 to 1    |

Values outside these ranges return 400 before any charge.

```bash
curl -X POST https://viewmax.studio/api/v1/sound-effects \
  -H "Authorization: Bearer $VIEWMAX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: thunder-001" \
  -d '{ "prompt": "A distant rolling thunder clap", "options": { "duration_seconds": 5 } }'
```

## Successful response

Speech example:

```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "9d1f3b5c-7e9a-4c2d-8f1b-3a5c7e9b1d24",
    "userId": "usr_123",
    "mediaType": "audio",
    "model": "eleven_v3",
    "prompt": "Welcome to ViewMax.",
    "options": "{\"voice_id\":\"EXAVITQu4vr4xnSDxMaL\",\"speed\":1}",
    "status": "success",
    "createdAt": "2026-09-29T08:00:00.000Z",
    "updatedAt": "2026-09-29T08:00:03.000Z",
    "deletedAt": null,
    "taskId": "sync:9d1f3b5c-7e9a-4c2d-8f1b-3a5c7e9b1d24",
    "taskInfo": null,
    "taskResult": "{\"output\":[\"https://cdn.example.com/audio/speech.mp3\"]}",
    "taskUrls": ["https://cdn.example.com/audio/speech.mp3"],
    "costCredits": 1,
    "scene": "text-to-speech",
    "visibility": "user",
    "creditId": "cr_246",
    "waivedCredits": 0,
    "fairUseKey": null
  }
}
```

A sound effect returns the same shape with `scene: "text-to-sound-effects"` and `costCredits: 5`. The fields are described under **Task fields** in the [Video Generation API](https://viewmax.studio/docs/api/video-generation). `taskUrls` is always an array.

## Idempotency and refunds

- Send `Idempotency-Key` (1–200 visible ASCII characters, no spaces). Repeating a key returns the original task and its `costCredits` without charging again; the body is not compared, and keys are shared by all create endpoints of the account. Retry a timed-out request with the same key; use a new key for a deliberate new attempt. Without the header, every POST creates and charges a new task.
- Credits are deducted when the task is created. If the balance is lower than the cost, the call returns 402 and creates no task. A task that ends `failed` or `canceled` is refunded automatically.

## Errors

All errors use the [error envelope](https://viewmax.studio/docs/api/errors).

| HTTP | `error.type`           | Example `message`                                                                         | What to do                                                                                                                                     |
| ---- | ---------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `invalid_request`      | `text is required` / `voice_id is required` / `prompt is required`                        | Send the required field                                                                                                                        |
| 400  | `invalid_request`      | `unknown voice_id: abc. Call GET /api/v1/voices (MCP: list_voices) for available voices.` | Use a voice `id` from `GET /api/v1/voices`                                                                                                     |
| 400  | `invalid_request`      | `options.duration_seconds must be a number from 0.5 to 22`                                | Send a JSON number in range (sound effects)                                                                                                    |
| 400  | `invalid_request`      | `options.prompt_influence must be a number from 0 to 1`                                   | Send a JSON number in range (sound effects)                                                                                                    |
| 400  | `invalid_request`      | `the generation request uses unsupported settings`, with `error.task_id`                  | The provider rejected the settings; the task failed and was refunded. Read its `taskInfo.errorMessage`, adjust, and retry with a new key       |
| 400  | `content_rejected`     | `input content was rejected by safety filters`                                            | Change the text or prompt; do not retry unchanged                                                                                              |
| 402  | `insufficient_credits` | `insufficient credits`                                                                    | No task was created. Add credits; do not retry until then                                                                                      |
| 500  | `internal_error`       | `the generation service is temporarily unavailable`                                       | With `error.task_id` the task failed and was refunded: retry once. If the hint says the outcome is unknown, poll that task instead of retrying |
| 503  | `internal_error`       | —                                                                                         | Sound effects only: the content-safety check is temporarily unavailable; retry after a short delay                                             |
