# Music Generation API

Create music tracks with POST /api/v1/music, including options, pricing, and errors.

## Create a music task

`POST /api/v1/music` generates an original track from a text prompt. The track renders before the call returns, so the response usually already has `status: "success"` and the track URL in `data.taskUrls`; keep the connection open and use a generous client timeout. The returned `data.id` also works with [`GET /api/v1/tasks/{taskId}`](https://viewmax.studio/docs/api/tasks) if you prefer one polling flow.

```http
POST https://viewmax.studio/api/v1/music
Authorization: Bearer sk-your-api-key
Content-Type: application/json
Idempotency-Key: lofi-001
```

## Request body

| Field     | Required | Type   | Description                        |
| --------- | -------- | ------ | ---------------------------------- |
| `prompt`  | Yes      | string | Non-empty description of the track |
| `options` | No       | object | Generation controls                |

### `options`

| Field              | Type    | Description                                                      |
| ------------------ | ------- | ---------------------------------------------------------------- |
| `duration_seconds` | number  | Track length in seconds: a JSON number from 3 to 300. Default 60 |
| `instrumental`     | boolean | `true` for no vocals                                             |
| `style`            | string  | Genre or mood. Changes how `prompt` is read; see below           |
| `title`            | string  | Song title. Changes how `prompt` is read; see below              |
| `lyrics`           | string  | Lyrics to sing                                                   |

`options.duration` and `options.music_length_ms` are rejected; use `duration_seconds`.

When `style` or `title` is set, the model receives `prompt` as lyrics. To describe a track, put the whole description in `prompt` and leave `style` and `title` unset. To give a song a style, put the lyrics in `prompt` and do not also send `lyrics`.

## Cost

Music costs `max(60, ceil(duration_seconds))` credits: 60 credits up to 60 seconds, then 1 credit per second (300 seconds cost 300). Omitting `duration_seconds` renders 60 seconds for 60 credits. Plan allowances do not apply to music on the API. `GET /api/v1/models?type=music` lists the base price as `credits`.

## Example

```bash
curl -X POST https://viewmax.studio/api/v1/music \
  -H "Authorization: Bearer $VIEWMAX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: lofi-001" \
  -d '{
    "prompt": "Warm lo-fi hip-hop with a mellow piano loop",
    "options": { "duration_seconds": 30, "instrumental": true }
  }'
```

This costs 60 credits.

## Successful response

```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "5c7e9a1b-3d5f-4b6c-8e0a-2f4b6d8e0a12",
    "userId": "usr_123",
    "mediaType": "music",
    "model": "music_v2",
    "prompt": "Warm lo-fi hip-hop with a mellow piano loop",
    "options": "{\"duration_seconds\":30,\"instrumental\":true}",
    "status": "success",
    "createdAt": "2026-09-29T08:00:00.000Z",
    "updatedAt": "2026-09-29T08:00:42.000Z",
    "deletedAt": null,
    "taskId": "sync:5c7e9a1b-3d5f-4b6c-8e0a-2f4b6d8e0a12",
    "taskInfo": null,
    "taskResult": "{\"output\":[\"https://cdn.example.com/music/track.mp3\"]}",
    "taskUrls": ["https://cdn.example.com/music/track.mp3"],
    "costCredits": 60,
    "scene": "text-to-music",
    "visibility": "user",
    "creditId": "cr_789",
    "waivedCredits": 0,
    "fairUseKey": null
  }
}
```

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. 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`      | `prompt is required`                                                     | Send a non-empty prompt                                                                                                                        |
| 400  | `invalid_request`      | `options.duration_seconds must be a number from 3 to 300`                | Send a JSON number in range                                                                                                                    |
| 400  | `invalid_request`      | `options.duration is not supported; use options.duration_seconds`        | Rename the option                                                                                                                              |
| 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 prompt or lyrics; 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`       | —                                                                        | The content-safety check is temporarily unavailable; retry after a short delay                                                                 |
