Eight market beds in parallel: asyncio, one idempotency key each

Eight Music Router jobs with one Idempotency-Key per market and a metadata tag cost $1.00 and survive a retry without a second charge. A runnable Python script.

4 min readSume
All posts

Eight market-specific beds cost 8 x $0.125 = $1.00 on Sume, and you can submit them in parallel from Python with one Idempotency-Key per market, so a retried request does not create a second paid job. The script below uses only the standard library, runs the blocking HTTP calls in threads under asyncio, polls each job with backoff, and prints the job id per market.

The idempotency key is the part that protects the money. The Jobs docs say not to submit the original paid request again only because a local process timed out; with a stable key (bed-20261008-us) a re-run of the same script reuses the key for the same market.

The script

Set SUME_API_KEY first. The script fails at start if the variable is missing. Each market gets a different tempo so the beds differ, and metadata carries the market code. The Music docs say Sume stores metadata on the job and does not send it to the provider.

import asyncio, json, os, urllib.request

KEY = os.environ["SUME_API_KEY"]
MARKETS = ["us", "uk", "de", "fr", "kr", "jp", "br", "mx"]

def call(method, path, body=None, idem=None):
    headers = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
    if idem:
        headers["Idempotency-Key"] = idem
    data = json.dumps(body).encode() if body else None
    req = urllib.request.Request("https://api.sume.com" + path, data, headers, method=method)
    with urllib.request.urlopen(req, timeout=60) as resp:
        return json.load(resp)

async def bed(market):
    bpm = 92 + 4 * MARKETS.index(market)
    prompt = f"Warm pop, {bpm} BPM, A minor. A 59-second track. Instrumental, no vocals."
    job = await asyncio.to_thread(call, "POST", "/v1/music-router/generate",
        {"prompt": prompt, "metadata": {"market": market}}, f"bed-20261008-{market}")
    job_id, delay = job.get("id") or job["request_id"], 2
    while (await asyncio.to_thread(call, "GET", f"/v1/jobs/{job_id}/status"))["status"] not in (
            "completed", "failed", "canceled"):
        await asyncio.sleep(delay)
        delay = min(delay * 2, 20)
    return market, job_id

async def main():
    print(await asyncio.gather(*(bed(m) for m in MARKETS)))

asyncio.run(main())

Notes on the shape

The job id is read from id or request_id, since the docs call the submit response a job and use request_id for the job id on some surfaces; read the real response once and keep the one you see. Status values are queued, processing, completed, failed and canceled, and the loop stops on the last three. The result sits at GET /v1/jobs/{id}/result, where the audio artifact has type: "audio" in result.artifacts[].

Eight-bed batch, Sume list prices as of 2026-10-08
ItemValue
Beds8 market tags
Price per bed$0.125 flat (Music 1.0 and Music Router)
Batch total8 x $0.125 = $1.00
Tempo spread92 to 120 BPM in steps of 4
Retry guardIdempotency-Key bed-20261008-<market>

When one market fails

The script prints ids and does not read results, so a failed job is only visible as the last status. In a real pipeline, branch on that status and log the market. Do not resubmit with the same key hoping for a different outcome: the same key and body is the same request. If a brief was flagged, change the flagged content and keep the musical brief, as the Music docs advise, and use a new key such as bed-20261008-us-2.

Budget for it. Read the failed job's public error metadata (category, retryability, next action) before you decide to retry; the queue category, for example, says to retry later with the same idempotency key. In the worst case, three re-tries on two markets add 6 x $0.125 = $0.75, and the batch is $1.75 instead of $1.00.

Limits to expect

The Generation admission page says paid generation jobs can wait as queued, and that when the queue is full, submits fail with 429 queue_full. It lists the accepted-job capacity by plan (Free 6, Pro 24, Startup 48). If you submit eight on a plan with a capacity of 6 and get a 429, wait and run the script again with the same keys; the docs say to retry with backoff and an idempotency key. The page describes this for paid generation jobs, so confirm on your own workspace whether music counts.

After the batch finishes, fetch each result and store the media.sume.com URL, not a provider URL. The Music docs say raw provider URLs are not public outputs.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume