# Music generation

`GET | POST /v1/music/generations` — Make a song with vocals and instruments, from one sentence or from your own lyrics — async, poll each take.

- Base URL: https://eroq.ai/v1
- Auth: `Authorization: Bearer eroq_sk_…` (create keys at https://eroq.ai/dashboard/keys)
- Credits: 3 per started 30 s of song, per take (Music One)

**Two ways in.** *Simple*: send `prompt`, the song in one sentence ("a melancholy synth-pop song about leaving a small town") — the songwriter writes the style, the lyrics and a title for you, included in the price. *Custom*: send your own `style` (language, vocals, genre, tempo, instruments) and `lyrics`, with section tags on their own lines — `[Intro]`, `[Verse]`, `[Pre-Chorus]`, `[Chorus]`, `[Bridge]`, `[Outro]`. A `style` without `lyrics` gets lyrics written for it. `instrumental: true` drops the vocals (lyrics are ignored). The 202 answer echoes the `title`, `style` and `lyrics` the song renders from, so you see what the songwriter wrote.

A song takes minutes of GPU, so this endpoint is **asynchronous**: the call charges, starts one job per take and answers `202` with `jobs` (one `id` + `poll` path per take). Poll `GET /v1/music/generations/{id}` every few seconds until `status` is `succeeded` (the MP3 is in `data`) or `failed`. Or subscribe a webhook to `music.generation.succeeded` / `music.generation.failed`.

**Billing.** 3 credits per started 30 seconds of song, per take — on the REQUESTED length, snapped to the engine's grid (30 seconds to 4 minutes, in steps of 30): it is the time the engine reserves, even when a song finds its ending a little earlier (the finished take reports its real `seconds`). `n` takes (1 or 2, default 2) are rendered with their own seeds so you can keep the better one. A take that fails, or never finishes within the engine's window, is refunded on its own — the others still deliver.

While the engine is not live on this deployment the call answers `503 model_unavailable` before any debit (`GET /v1/engines` reports `models["eroq-music-one"]`). Our content policy applies to every word you send AND to what the songwriter writes: nothing involving minors, no real identifiable people without consent — a refused song answers `400 content_blocked` and bills nothing (or is refunded when the written lyrics are the problem).

Every finished take lands in your Library (the Music tab) with its recipe, so the studio can remix it later. `GET /v1/music/generations/{id}` is free to call; job ids expire 24 hours after creation.

## Operations

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/v1/music/generations` | Start a song — one sentence (Simple) or style + lyrics (Custom). Answers 202 with one job per take. |
| `GET` | `/v1/music/generations/{id}` | Poll one take until the MP3 is ready — free. |
| `GET` | `/v1/music/generations` | List the workspace's song takes of the last 24 hours, newest first — free. Entries carry the title, never the lyrics: fetch a take by id for its recipe. |

## POST /v1/music/generations

Start a song — one sentence (Simple) or style + lyrics (Custom). Answers 202 with one job per take.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `prompt` | string | no | Simple mode: the song in one sentence, up to 600 characters. The songwriter writes style, lyrics and title. One of `prompt`, `style` or `lyrics` is required. |
| `style` | string | no | Custom mode: how it sounds — language, vocals, genre, tempo, instruments (comma-separated words work best), up to 600 characters. |
| `lyrics` | string | no | Custom mode: the sung words, with section tags on their own lines (`[Verse]`, `[Chorus]`, `[Bridge]`…), up to 3,000 characters. Omitted with a `style` = written for you. |
| `title` | string | no | The song's title, up to 80 characters. Omitted = the songwriter names it. |
| `instrumental` | boolean | no | No vocals: lyrics are ignored. Default `false`. |
| `seconds` | integer | no | Requested length, snapped to the grid: 30 to 240 in steps of 30. Billed on this length. |
| `n` | integer | no | Takes, 1 or 2 (default 2) — each its own job, seed and charge. |
| `seed` | integer | no | Reproducible takes: take N renders with `seed + N - 1`. |
| `model` | string | no | `eroq-music-one` (default, the only music engine today). |
| `folder_id` | string | no | Library folder the takes land in — one of yours (GET /v1/folders). Omitted = the Library root; an unknown id answers 404 `folder_not_found` before anything is billed. |
| `flow_id` | string | no | A flow to run on each take once it lands — one that starts « After a render » of audio (GET /v1/flows). Checked before anything is billed. See Flows. |

### Example request

```bash
curl https://eroq.ai/v1/music/generations \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "eroq-music-one",
  "style": "english, female vocals, synth-pop, 104 bpm, warm analog synths, gated drums",
  "lyrics": "[Verse]\nStreetlights fade behind the bus\nThe town I knew is shrinking fast\n\n[Chorus]\nI'\''m leaving, leaving, nothing left to prove\nThe road is long and I'\''m in love with the move",
  "title": "Leaving Light",
  "seconds": 120,
  "n": 2
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/music/generations', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "model": "eroq-music-one",
    "style": "english, female vocals, synth-pop, 104 bpm, warm analog synths, gated drums",
    "lyrics": "[Verse]\nStreetlights fade behind the bus\nThe town I knew is shrinking fast\n\n[Chorus]\nI'm leaving, leaving, nothing left to prove\nThe road is long and I'm in love with the move",
    "title": "Leaving Light",
    "seconds": 120,
    "n": 2
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/music/generations",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "model": "eroq-music-one",
        "style": "english, female vocals, synth-pop, 104 bpm, warm analog synths, gated drums",
        "lyrics": "[Verse]\nStreetlights fade behind the bus\nThe town I knew is shrinking fast\n\n[Chorus]\nI'm leaving, leaving, nothing left to prove\nThe road is long and I'm in love with the move",
        "title": "Leaving Light",
        "seconds": 120,
        "n": 2
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "model": "eroq-music-one",
  "style": "english, female vocals, synth-pop, 104 bpm, warm analog synths, gated drums",
  "lyrics": "[Verse]\nStreetlights fade behind the bus\nThe town I knew is shrinking fast\n\n[Chorus]\nI'm leaving, leaving, nothing left to prove\nThe road is long and I'm in love with the move",
  "title": "Leaving Light",
  "seconds": 120,
  "n": 2
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/music/generations", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "id": "4d2a91c0-…",
  "object": "music.generation",
  "status": "processing",
  "created": 1760400000,
  "model": "eroq-music-one",
  "seconds": 120,
  "title": "Leaving Light",
  "style": "english, female vocals, synth-pop, 104 bpm, warm analog synths, gated drums",
  "lyrics": "[Verse]\nStreetlights fade behind the bus\n…",
  "instrumental": false,
  "jobs": [
    { "id": "4d2a91c0-…", "poll": "/v1/music/generations/4d2a91c0-…", "take": 1, "seed": 182736 },
    { "id": "9b07e3f1-…", "poll": "/v1/music/generations/9b07e3f1-…", "take": 2, "seed": 182737 }
  ],
  "usage": { "credits_spent": 24, "credits_remaining": 852 }
}
```

## GET /v1/music/generations/{id}

Poll one take until the MP3 is ready — free.

### Example request

```bash
curl https://eroq.ai/v1/music/generations/GENERATION_ID \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/music/generations/GENERATION_ID', {
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.get("https://eroq.ai/v1/music/generations/GENERATION_ID", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://eroq.ai/v1/music/generations/GENERATION_ID", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "id": "4d2a91c0-…",
  "object": "music.generation",
  "status": "succeeded",
  "created": 1760400000,
  "model": "eroq-music-one",
  "duration": "120s",
  "title": "Leaving Light",
  "style": "english, female vocals, synth-pop, 104 bpm, warm analog synths, gated drums",
  "lyrics": "[Verse]\nStreetlights fade behind the bus\n…",
  "instrumental": false,
  "take": 1,
  "seed": 182736,
  "data": [{ "url": "https://cdn.eroq.ai/…/song.mp3?token=…&expires=…", "content_type": "audio/mpeg", "seconds": 117, "library_id": "7e3b…" }],
  "generation_ms": 162000,
  "usage": { "credits_spent": 12, "credits_remaining": 852 }
}
```

## GET /v1/music/generations

List the workspace's song takes of the last 24 hours, newest first — free. Entries carry the title, never the lyrics: fetch a take by id for its recipe.

### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | integer | no | Takes to return, 1–50. Default 20. |
| `status` | string | no | Filter: `processing`, `succeeded` or `failed`. |

### Example request

```bash
curl "https://eroq.ai/v1/music/generations?limit=20&status=processing" \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/music/generations?limit=20&status=processing', {
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.get("https://eroq.ai/v1/music/generations?limit=20&status=processing", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://eroq.ai/v1/music/generations?limit=20&status=processing", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "object": "list",
  "data": [
    {
      "id": "4d2a91c0-…",
      "object": "music.generation",
      "status": "processing",
      "created": 1760400000,
      "model": "eroq-music-one",
      "duration": "120s",
      "title": "Leaving Light",
      "take": 1,
      "prompt": "a melancholy synth-pop song about leaving a small town",
      "poll": "/v1/music/generations/4d2a91c0-…"
    }
  ]
}
```

---
Canonical: https://eroq.ai/docs/music · Index for agents: https://eroq.ai/llms.txt · OpenAPI: https://eroq.ai/openapi.json
