Music generations
Songs from a prompt and lyrics, the wait for a result, the music time budget, and the model list.
Access: Keys with the music:read or music:generate scope, on an account with music access granted by Albastar Corp.
A music generation makes one song from a prompt, with lyrics or as an instrumental, and keeps the song in the
account's library at https://music.albastov.com. POST /v1/music/generations and
DELETE /v1/music/generations/{id} need the music:generate scope, which includes music:read; the other
generation routes need music:read. GET /v1/music/models needs no key.
Errors on these routes have the shape {"ok": false, "error": "bad-param", "code": "bad-param", "message": "...", "param": "prompt"}; only bad-param carries param. Branch on code. Key, scope and rate-limit errors:
Errors.
The generation object
| Field | Type | Value |
|---|---|---|
id | string | j-, the UTC date as YYMMDD, - and 6 random characters, such as j-261104-k3v9qa |
object | string | music.generation |
model | string | The model id, ace or yue2 |
state | string | queued, running, done, failed or cancelled |
params | object | The parameters as the generation runs them: prompt, lyrics, instrumental, duration_s (null when the model picks the length), seed, format and title |
position | integer or null | Place in the queue while queued, from 1 |
expected_s | number | Seconds of music time the generation is expected to take |
progress | number or null | 0 to 1 while running, when the model reports progress |
song_id | string or null | The song in the account's library once done |
results | array | The audio file once done; empty before |
error | object or null | code and message of a generation that failed |
created_at, started_at, finished_at | string or null | RFC 3339 times in UTC, with milliseconds |
A result entry has url, content_type (audio/mpeg or audio/wav), duration_s, bytes and format. url is
https://api.albastov.com/v1/music/generations/{id}/files/audio.mp3, or audio.wav for format wav, and needs
a key with music:read. A generation runs in one queue with the songs made in the studio at
https://music.albastov.com; a job of another kind made in the studio is no generation and answers 404.
Waiting
?wait=<s> on POST /v1/music/generations and GET /v1/music/generations/{id} holds the request until the
generation settles. s is a number of seconds, 0 or more; a value over 85 counts as 85, and the hold ends 85 s after
the request arrives at the latest. The answer is 200 once the generation is done, failed or cancelled, else
202 with the generation as it is. A song often takes longer than one hold: read the generation again with ?wait
until state is done. An account holds 16 waiting requests at most; a further ?wait answers at once with 202
and the generation as it is.
Music time
Each account has a music time budget per UTC day and per UTC month, set by Albastar Corp. A generation reserves its
expected_s when queued. A done generation counts the time it took, a failed generation counts nothing, and a
generation cancelled while running counts the time it ran. A generation that would go over the day's or the month's
remaining time fails with the one usage-limit error, 429 allowance_used with meter music.gpu_seconds, window
day or month and previous_code over-daily or over-monthly; an account with no music time gets the same 429
with limit 0 and previous_code no-quota (The 429).
The day resets at 00:00 UTC and the month on the 1st at 00:00 UTC. Songs made in the studio spend the same budget.
Create a generation
POST /v1/music/generations queues one song.
Retries
An Idempotency-Key header, or the idempotency_key field, makes a retry safe: 1 to 200 printable ASCII
characters, such as a UUID. A second request of the same account with the same key and the same body answers the
generation the first one queued, 202 or 200 as its state, and spends no more music time. The same key with
another body fails with 409 idempotency-conflict. A key belongs to its account, without expiry; another account's
key of the same value is unrelated.
Parameters
A JSON body. A field outside the list fails with 400 bad-param, param the field and message
<field>: not a parameter.
| Name | Type | Description |
|---|---|---|
prompt required | string | The song's style: genre, mood, instruments, voice; 1 to 1,000 characters |
lyrics | string | The words to sing, 6,000 characters at most; default "" |
instrumental | boolean | true makes a song without vocals and requires empty lyrics; default false |
duration_s | number | The length in seconds, 10 to 300; on yue2, the longest the song may be. Without duration_s, the model picks the length |
model | string | ace (default) or yue2, from GET /v1/music/models |
seed | integer | 1 to 2147483647; the same seed and parameters give a similar song. Without seed, a random seed, returned in params |
format | string | The file results lists: mp3 (default) or wav |
title | string | The song's title in the library, 80 characters at most; default "" |
wait | number | Query parameter: seconds to hold the request, 0 or more; 85 s at most from the request's arrival |
idempotency_key | string | 1 to 200 printable ASCII characters; the same as the Idempotency-Key header when both are sent (Retries) |
Request
curl
curl "https://api.albastov.com/v1/music/generations?wait=60" \
-H "Authorization: Bearer $ALBASTAR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: folk-ballad-0001" \
-d '{"prompt": "folk ballad, fingerpicked guitar, soft female voice",
"lyrics": "[verse]\nThe river bends past the old mill",
"duration_s": 60, "seed": 7}'Python
import json
import os
import urllib.request
body = {
"prompt": "folk ballad, fingerpicked guitar, soft female voice",
"lyrics": "[verse]\nThe river bends past the old mill",
"duration_s": 60,
"seed": 7,
}
req = urllib.request.Request(
"https://api.albastov.com/v1/music/generations?wait=60",
data=json.dumps(body).encode(),
headers={
"User-Agent": "my-app/1.0",
"Authorization": f"Bearer {os.environ['ALBASTAR_API_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": "folk-ballad-0001",
},
)
with urllib.request.urlopen(req, timeout=120) as res:
gen = json.load(res)
print(gen["id"], gen["state"], gen["position"])JavaScript
const res = await fetch(
"https://api.albastov.com/v1/music/generations?wait=60",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ALBASTAR_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "folk-ballad-0001",
},
body: JSON.stringify({
prompt: "folk ballad, fingerpicked guitar, soft female voice",
lyrics: "[verse]\nThe river bends past the old mill",
duration_s: 60,
seed: 7,
}),
},
);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const gen = await res.json();
console.log(gen.id, gen.state, gen.position);Response
202 with the generation while it is queued or running; 200 once the generation settled within wait.
{
"id": "j-261104-r7w2bd",
"object": "music.generation",
"model": "ace",
"state": "queued",
"params": {
"prompt": "folk ballad, fingerpicked guitar, soft female voice",
"lyrics": "[verse]\nThe river bends past the old mill",
"instrumental": false,
"duration_s": 60,
"seed": 7,
"format": "mp3",
"title": ""
},
"position": 2,
"expected_s": 24,
"progress": null,
"song_id": null,
"results": [],
"error": null,
"created_at": "2026-11-04T10:00:00.120Z",
"started_at": null,
"finished_at": null
}The ids, positions and times differ on every generation.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | bad-param | A field is unknown or out of range, lyrics is not empty with instrumental true, model is not in the model list, wait is not a number of seconds, or the idempotency key is empty, too long, not printable ASCII or differs between the header and the field; param names the field |
| 400 | bad-json | The body is not a JSON object |
| 403 | no-access | The key's account has no music access |
| 403 | storage-full | The account's music library is full; deleting songs at https://music.albastov.com makes room |
| 409 | idempotency-conflict | The account sent the same idempotency key earlier with another body |
| 413 | too-large | The body is over 1 MiB |
| 429 | allowance_used | The generation would go over the day's or the month's music time, or the account has none; previous_code is over-daily, over-monthly or no-quota |
| 429 | too-many-jobs | The account has 50 generations and studio jobs queued or running |
Get a generation
GET /v1/music/generations/{id} returns a generation of the key's account.
Parameters
| Name | Type | Description |
|---|---|---|
id required | string | The generation id j-…, in the path |
wait | number | Query parameter: seconds to hold the request until the generation settles, 0 or more; 85 s at most from the request's arrival |
Request
curl
curl "https://api.albastov.com/v1/music/generations/$GEN?wait=60" \
-H "Authorization: Bearer $ALBASTAR_API_KEY"Python
import json
import os
import urllib.request
req = urllib.request.Request(
f"https://api.albastov.com/v1/music/generations/{os.environ['GEN']}"
"?wait=60",
headers={
"User-Agent": "my-app/1.0",
"Authorization": f"Bearer {os.environ['ALBASTAR_API_KEY']}",
},
)
with urllib.request.urlopen(req, timeout=120) as res:
gen = json.load(res)
print(gen["state"], [r["url"] for r in gen["results"]])JavaScript
const res = await fetch(
`https://api.albastov.com/v1/music/generations/${process.env.GEN}?wait=60`,
{ headers: { Authorization: `Bearer ${process.env.ALBASTAR_API_KEY}` } },
);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const gen = await res.json();
console.log(gen.state, gen.results.map((r) => r.url));Response
200 with the generation object once settled, as in the
Music overview; 202 when wait passes before the
generation settles.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | bad-param | wait is not a number, or is below 0 |
| 404 | not-found | No generation with the id on the key's account |
List generations
GET /v1/music/generations returns the account's generations, newest first.
Parameters
| Name | Type | Description |
|---|---|---|
limit | integer | Query parameter: generations per page, 1 to 100; default 20; a value over 100 counts as 100 |
cursor | string | Query parameter: the next_cursor of the previous page |
Request
curl
curl "https://api.albastov.com/v1/music/generations?limit=10" \
-H "Authorization: Bearer $ALBASTAR_API_KEY"Python
import json
import os
import urllib.request
req = urllib.request.Request(
"https://api.albastov.com/v1/music/generations?limit=10",
headers={
"User-Agent": "my-app/1.0",
"Authorization": f"Bearer {os.environ['ALBASTAR_API_KEY']}",
},
)
with urllib.request.urlopen(req, timeout=30) as res:
page = json.load(res)
for gen in page["generations"]:
print(gen["id"], gen["state"], gen["params"]["prompt"])
print(page["next_cursor"])JavaScript
const res = await fetch(
"https://api.albastov.com/v1/music/generations?limit=10",
{ headers: { Authorization: `Bearer ${process.env.ALBASTAR_API_KEY}` } },
);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const page = await res.json();
for (const gen of page.generations) {
console.log(gen.id, gen.state, gen.params.prompt);
}
console.log(page.next_cursor);Response
{
"generations": [
{
"id": "j-261104-r7w2bd",
"object": "music.generation",
"model": "ace",
"state": "running"
}
],
"next_cursor": null
}The response above shortens the generation to four fields. next_cursor is the id of the last generation on the
page when another page follows, else null.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | bad-param | limit is not a whole number of 1 or more, or cursor is no generation of the account |
Cancel a generation
DELETE /v1/music/generations/{id} cancels a queued or running generation. A generation cancelled while running
counts the music time it ran.
Parameters
| Name | Type | Description |
|---|---|---|
id required | string | The generation id j-…, in the path |
Request
curl
curl -X DELETE "https://api.albastov.com/v1/music/generations/$GEN" \
-H "Authorization: Bearer $ALBASTAR_API_KEY"Python
import json
import os
import urllib.request
req = urllib.request.Request(
f"https://api.albastov.com/v1/music/generations/{os.environ['GEN']}",
method="DELETE",
headers={
"User-Agent": "my-app/1.0",
"Authorization": f"Bearer {os.environ['ALBASTAR_API_KEY']}",
},
)
with urllib.request.urlopen(req, timeout=30) as res:
print(json.load(res)["state"])JavaScript
const res = await fetch(
`https://api.albastov.com/v1/music/generations/${process.env.GEN}`,
{
method: "DELETE",
headers: { Authorization: `Bearer ${process.env.ALBASTAR_API_KEY}` },
},
);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log((await res.json()).state);Response
200 with the generation object, state cancelled and finished_at set.
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | not-found | No generation with the id on the key's account |
| 409 | not-cancellable | The generation is already done, failed or cancelled |
Download a file
GET /v1/music/generations/{id}/files/{name} returns the audio file of a done generation. The route answers
Range requests with 206 and sends an ETag.
Parameters
| Name | Type | Description |
|---|---|---|
id required | string | The generation id j-…, in the path |
name required | string | audio.mp3 or audio.wav, in the path; the url of a result names the file of the generation's format |
download | string | Query parameter: 1 adds Content-Disposition: attachment with the song's title as the file name |
Request
curl
curl -o song.mp3 \
"https://api.albastov.com/v1/music/generations/$GEN/files/audio.mp3" \
-H "Authorization: Bearer $ALBASTAR_API_KEY"Python
import os
import urllib.request
req = urllib.request.Request(
"https://api.albastov.com/v1/music/generations/"
f"{os.environ['GEN']}/files/audio.mp3",
headers={
"User-Agent": "my-app/1.0",
"Authorization": f"Bearer {os.environ['ALBASTAR_API_KEY']}",
},
)
with urllib.request.urlopen(req, timeout=60) as res:
open("song.mp3", "wb").write(res.read())JavaScript
import { writeFile } from "node:fs/promises";
const res = await fetch(
"https://api.albastov.com/v1/music/generations/" +
`${process.env.GEN}/files/audio.mp3`,
{ headers: { Authorization: `Bearer ${process.env.ALBASTAR_API_KEY}` } },
);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
await writeFile("song.mp3", Buffer.from(await res.arrayBuffer()));Response
200 with the audio file, Content-Type audio/mpeg or audio/wav; 206 with the requested range.
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | not-found | No generation with the id on the key's account, a generation not done, or a name other than audio.mp3 and audio.wav |
List models
GET /v1/music/models returns the models a generation can run, without a key.
Request
curl
curl https://api.albastov.com/v1/music/modelsPython
import json
import urllib.request
req = urllib.request.Request(
"https://api.albastov.com/v1/music/models",
headers={"User-Agent": "my-app/1.0"},
)
with urllib.request.urlopen(req, timeout=30) as res:
for m in json.load(res)["models"]:
print(m["id"], m["default"], m["available"], m["duration_s"])JavaScript
const res = await fetch("https://api.albastov.com/v1/music/models");
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
for (const m of (await res.json()).models) {
console.log(m.id, m.default, m.available, m.duration_s);
}Response
{
"models": [
{
"id": "ace",
"label": "ACE-Step",
"default": true,
"available": true,
"duration_s": {"min": 10, "max": 300},
"lyrics": true,
"instrumental": true,
"formats": ["mp3", "wav"],
"typical_s": 45
},
{
"id": "yue2",
"label": "YuE2",
"default": false,
"available": true,
"duration_s": {"min": 10, "max": 300},
"lyrics": true,
"instrumental": true,
"formats": ["mp3", "wav"],
"typical_s": 124
}
]
}| Field | Value |
|---|---|
id, label | The model id model takes, and its display name |
default | true on the model a generation without model runs |
available | true when the music service is online; a generation queued while available is false waits in the queue |
duration_s | The range of duration_s, in seconds |
lyrics, instrumental | true when the model sings lyrics and makes instrumentals |
formats | The values format takes |
typical_s | Seconds of music time a 120 s song takes on the model |