Sume music job in Python: 30-second wait, then poll, one paid job

Submit a Lyria track to Sume Music Router with a 30-second sync wait, fall back to polling on timeout, and reuse one Idempotency-Key. Runnable Python.

5 min readSume
All posts

Submit with mode: sync and wait_timeout_seconds: 30, check result_ready on the response, and if it is false keep polling status_url with the same job. Never post a second request with a new key: a song takes longer than 30 seconds to render often enough that a retry would buy a second $0.125 generation. The script below handles both paths.

How the sync wait works

On the Music Router, mode is async, sync, subscribe or webhook. sync and subscribe are the same bounded wait. wait_timeout_seconds runs 0 to 30. If the job is still queued or the waiter budget is full, the response is still 2xx and carries sync.timed_out or sync.capacity_exhausted. That limit bounds the HTTP wait, not the job.

Music Router submit modes - Sume docs (read 2026-10-07)
ModeReturnsUse when
asyncJob id and poll URLs at onceDefault for new work
sync / subscribeWaits up to 30 s, then current stateA short script that may finish fast
webhookJob id, signed terminal callback laterA server that should not poll

The script

Set SUME_API_KEY. The same Idempotency-Key makes an accidental double run return the existing job.

import asyncio, json, os, urllib.request

API = "https://api.sume.com"
KEY = os.environ["SUME_API_KEY"]

def call(method, url, body=None, idem=None):
    h = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
    if idem:
        h["Idempotency-Key"] = idem
    data = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(url, data=data, headers=h, method=method)
    with urllib.request.urlopen(req) as r:
        return json.load(r)

async def main():
    body = {"model": "lyria-3.5", "mode": "sync", "wait_timeout_seconds": 30,
            "prompt": "Warm lo-fi hip hop, 84 BPM, C minor. A 30-second track. Instrumental, no vocals."}
    out = await asyncio.to_thread(call, "POST", f"{API}/v1/music-router/generate", body, "lofi-bed-001")
    d = out.get("data", out)
    while not d.get("result_ready"):
        await asyncio.sleep(d.get("next_poll_after_seconds") or 2)
        d = (await asyncio.to_thread(call, "GET", d["status_url"])).get("data", d)
    res = await asyncio.to_thread(call, "GET", d["result_url"])
    res = res.get("data", res)
    res = res.get("result", res)
    for a in res.get("artifacts", []):
        if a.get("type") == "audio":
            print(a["url"])

asyncio.run(main())

Cost and retries

Music 1.0 documents a fixed $0.125 per accepted generation, independent of prompt length. Google lists Lyria 3.5 at $0.08 per full song on its own API (read 2026-10-07); Sume charges its own fixed Music price per accepted generation. A failed submit with the same key is safe to retry. A new key for a new take is a new charge by design.

  • Read the audio from result.artifacts[] where type is audio; the URL is on media.sume.com.
  • Do not send duration_seconds to Music; the field is rejected. Put the length in the prompt.
  • Use a distinct, meaningful key per intended take, such as scene and take number.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume