Alert when a new TTS model id lands in the Sume router catalog
September and October brought new voice models. A 20-line script diffs GET /v1/tts-router/models against yesterday's ids and tells you when a row is added.

Read the Sume router catalog on a schedule and compare it with the ids you saw last time. A new row is the signal that a model you asked about is now routable. The catalog is GET /v1/tts-router/models, and it returns data.models, each with an id, capabilities.max_characters and list pricing. Today the ids are sonic-3.6, sonic-3.5, sonic-3, sonic-latest and sonic-preview; the docs say later vendors arrive as new rows, not as a new surface.
Why a diff beats checking by hand
The past few weeks brought new speech models from several vendors: Microsoft published MAI-Voice-2.1 and a Flash variant, and Google moved Gemini 3.8 Flash TTS to general availability on Sep 22, 2026. None of those is in the Sume router today, and the docs list non-Sonic families as later rows. A daily diff means you learn about a new id from the catalog itself instead of from a forum post, and it works for ids you have not heard of.
| Item | In the Sume router catalog? | Source |
|---|---|---|
| sonic-3.6, sonic-3.5, sonic-3 | Yes | Sume router docs |
| sonic-latest (alias for sonic-3.6) | Yes | Sume router docs |
| sonic-preview (beta) | Yes | Sume router docs |
| MAI-Voice-2.1 and Flash | Not listed | Router docs list Sonic only |
| Gemini 3.8 Flash TTS | Not listed | Router docs list Sonic only |
The script
It stores the id list in a JSON file, fetches the catalog, prints any additions or removals and saves the new list. Run it from cron or a scheduled agent. It exits with code 1 when something changed so a scheduler can alert on the exit status.
import json, os, pathlib, requests
STATE = pathlib.Path("tts_router_ids.json")
r = requests.get("https://api.sume.com/v1/tts-router/models",
headers={"x-api-key": os.environ["SUME_API_KEY"]}, timeout=30)
r.raise_for_status()
rows = {m["id"]: m for m in r.json()["data"]["models"]}
old = set(json.loads(STATE.read_text())) if STATE.exists() else set()
new = set(rows)
for i in sorted(new - old):
print("ADDED", i, rows[i]["capabilities"]["max_characters"], "chars max")
for i in sorted(old - new):
print("REMOVED", i)
STATE.write_text(json.dumps(sorted(new)))
raise SystemExit(1 if (new != old and old) else 0)What to do with an ADDED line
First fetch the single row with GET /v1/tts-router/models/{model_id} and read its constraints. Then run the 1-cent language and number test from your own script before you move any production job to it. A new id might be a beta channel, like sonic-preview, which the docs say can change without notice and rejects pro voice clones with voice_model_mismatch.
A REMOVED line is more urgent than an added one. Search your own code for the id and move those jobs to a named replacement before the next batch.
Where to run it
A cron entry once a day is plenty, since catalog changes are rare. Send the exit status to your alerting channel. Keep the JSON file under version control if you want an audit trail of when each id first appeared. The script uses only the standard library plus requests, and it needs the same API key you already use for TTS jobs, passed in the x-api-key header; never send both that header and a Bearer token on one request.
Edge cases the script handles
On the first run there is no state file, so the script prints every id as ADDED and exits 0, because it treats an empty baseline as a setup run, not an alert. It also compares sets, so a reordered catalog does not trigger anything. If the request fails, raise_for_status stops the run before the state file is touched, so a network blip never looks like a removal.
Add one more guard if you run it unattended: check that the response has at least one model before saving, so an empty body from a proxy cannot wipe your baseline.
Sources
Related posts
More in Developers
- GitHub Actions: submit a 30 s video, jobs watch, upload the artifact
A workflow that posts to Sume.s /v1/videos, runs sume jobs watch with a timeout, downloads the clip and uploads it as a build artifact.
- GitHub wants a 2XX in 10 seconds, Sume times out at 10: dedupe both
GitHub and Sume both give a webhook receiver 10 seconds. GitHub reuses X-GitHub-Delivery on redelivery; with Sume, use job_id as the dedupe key.
- Gitleaks custom rule for sume_live_ API keys, with pre-commit
Add a gitleaks rule that matches sume_live_ keys, run it as a pre-commit hook, and know what to do within minutes if a Sume key does leak.
- Go: decode a Sume WebP image with golang.org/x/image/webp
Go's image package decodes PNG and JPEG out of the box; WebP needs golang.org/x/image/webp. Decode a Sume image URL and read its size without a re-encode.
Written by Sume