TTS voice list returns 503: provider_unavailable vs not_configured

GET /v1/tts-router/voices can return 503 two ways on Sume. provider_unavailable is a retry; provider_not_configured is not. Cache, timeout and a retry loop.

4 min readSume
All posts

If GET /v1/tts-router/voices answers 503, read the error code before you retry. provider_unavailable means the voice list could not be read from the provider right now, so wait a few seconds and ask again. provider_not_configured means that voice family is not set up in the environment you are calling, and retrying will not change it.

The route takes one required query parameter, family, which is sonic (for the sonic-* models) or eleven (for the eleven-* models). Each model in GET /v1/tts-router/models names its list in voice_family, so read that field instead of guessing.

What do the two 503 errors mean?

The two failures come from different places in the route, and they carry different bodies.

Treat the status code alone as ambiguous. Both errors are 503, so a generic gateway rule such as retry every 5xx will hammer a configuration problem that no amount of waiting fixes. Branch on code.

Voice list 503 errors in the Sume API source, read 2026-10-11
CodeMeaningRetry?Extra detail
provider_unavailableThe provider list call failed, timed out, or returned something that was not JSONYes, after a short waitfamily in the error details
provider_not_configuredThe voice provider has no key in this environmentNoA missing list naming the component

How long is a voice list cached?

A successful read is cached for ten minutes per family, so repeated calls inside that window return the same list without another provider call. A failed read is not cached: the next request tries the provider again. That is why a plain retry is safe and why one bad moment does not stick for ten minutes.

The provider call itself has an 8-second timeout. A slow provider therefore turns into provider_unavailable rather than a hung request. Your own HTTP client timeout should sit above 8 seconds, or you will give up before the API does.

Which list did I get back?

A 200 response carries source. For family=sonic it is live, and only public voices are listed; your own cloned voices stay in the Voices library under voi_* ids and work with sonic-* models. For family=eleven it is live when an ElevenLabs account is connected to the environment, and premade otherwise. A premade list is a fixed set of 21 stock voices, not the whole ElevenLabs library, so do not treat it as an error when a voice you expect is missing.

A retry loop that stops on the right error

This sketch retries only the transient case, with a growing wait, and fails fast on anything else. It reads the code from the error body, so it never retries a configuration problem by accident.

import os
import time
import requests

URL = "https://api.sume.com/v1/tts-router/voices"
HEADERS = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}


def list_voices(family: str, tries: int = 4) -> dict:
    for attempt in range(tries):
        r = requests.get(URL, headers=HEADERS,
                         params={"family": family}, timeout=15)
        if r.status_code == 200:
            return r.json()["data"]
        body = r.json() if r.content else {}
        code = str(body.get("error", {}).get("code", ""))
        if r.status_code == 503 and code == "provider_unavailable":
            time.sleep(2 * (attempt + 1))
            continue
        r.raise_for_status()
    raise RuntimeError(f"{family} voices still unavailable")


if __name__ == "__main__":
    result = list_voices("sonic")
    print(result["source"], len(result["voices"]))

Does a failed list block generation?

No. The list is a lookup, not a step in a job. If you already hold a voice id, POST /v1/tts-router/generate does not need the list call first, so a cached id in your own config keeps working while the list is down.

The same goes for the models catalog: GET /v1/tts-router/models is a separate call, and its entries tell you each model's character limit, languages and output formats. Cache both lookups at startup and refresh them on a timer, with the list refresh no more often than the ten-minute cache would give you anything new.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume