How to list available models via the API and survive model changes

The model catalog changes: new models arrive, old ones are switched off. How to fetch the current list and write code that survives a model change.

3 min · · models · API keys

The Zukko model catalog changes: new models are added, old ones are switched off. If a model name is hardcoded, one day requests will start failing. Here is how to get the list of available models via the API, how to handle a disabled model, and how to move the model choice into config.

Model list: GET /v1/models

The endpoint is public; no key is needed:

bash
curl https://api.zukko.pro/v1/models

The response is OpenAI-compatible: an object with the list in the data field:

bash
{
  "object": "list",
  "data": [
    {"id": "claude-sonnet-5", "object": "model", "created": 1700000000, "owned_by": "zukko"},
    {"id": "gpt-5.6-sol", "object": "model", "created": 1700000000, "owned_by": "zukko"},
    {"id": "gpt-5.6-luna", "object": "model", "created": 1700000000, "owned_by": "zukko"},
    {"id": "glm-5.3", "object": "model", "created": 1700000000, "owned_by": "zukko"},
    {"id": "glm-5.3-flash", "object": "model", "created": 1700000000, "owned_by": "zukko"},
    {"id": "claude-opus-5-5", "object": "model", "created": 1700000000, "owned_by": "zukko"}
  ]
}

This is an example at the time of writing; contents and order may change. For requests you only need the id field; pass it as the model parameter. The same list is on the "Models" page in the dashboard, and the endpoint is described in the docs.

Note that the list is the same for everyone. If your key has an allowed-models list (allowed_models), a request to a model outside it returns 403 model_not_allowed_for_key, even if the model is in /v1/models.

Python: get the list

With requests:

bash
import requests

def get_available_models(timeout: float = 5.0) -> list[str]:
    r = requests.get("https://api.zukko.pro/v1/models", timeout=timeout)
    r.raise_for_status()
    return [m["id"] for m in r.json()["data"]]

print(get_available_models())

With the OpenAI SDK, if your project already uses it:

bash
from openai import OpenAI

client = OpenAI(base_url="https://api.zukko.pro/v1", api_key="fetch_your_key")
models = [m.id for m in client.models.list()]
print(models)

What happens when you call a disabled model

If a model is switched off, the gateway responds with HTTP 503 and the error code model_disabled in the response body. Important: 503 is also used when a provider is temporarily unavailable. So look at the code in the body, not just the status:

  • 503 + model_disabled: the model is off, retrying is pointless, pick another one;
  • 503 without model_disabled: a temporary provider issue, retrying after a pause makes sense.
bash
import openai

def is_model_disabled(err: openai.APIStatusError) -> bool:
    return err.status_code == 503 and "model_disabled" in err.response.text

err.response is an httpx response object, so the check works with both the OpenAI SDK and the Anthropic SDK (anthropic.APIStatusError has the same status_code and response fields).

Resilient calls: a chain of fallback models

Set a preferred model and fallbacks. On model_disabled the code moves to the next one; on a temporary 503 it pauses and retries.

bash
import time
import openai
from openai import OpenAI

client = OpenAI(base_url="https://api.zukko.pro/v1", api_key="fetch_your_key")

MODEL_CHAIN = ["claude-opus-5-5", "claude-sonnet-5", "gpt-5.6-sol"]

def chat(messages: list, chain: list[str] = MODEL_CHAIN, retries: int = 2):
    for model in chain:
        for attempt in range(retries + 1):
            try:
                return client.chat.completions.create(model=model, messages=messages)
            except openai.APIStatusError as e:

To drop disabled models right at startup, filter the chain by the list from /v1/models:

bash
def usable_chain(chain: list[str]) -> list[str]:
    try:
        available = set(get_available_models())
    except requests.RequestException:
        return chain
    return [m for m in chain if m in available] or chain

On a network error the function returns the original chain: it is better to try the model and handle the error than to stop the app because the list was unavailable.

Model in config, not in code

Keep the model name in an environment variable so changing the model does not need a release:

FETCH_KEY=fetch_... LLM_MODEL=claude-sonnet-5
bash
import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://api.zukko.pro",
    api_key=os.environ["FETCH_KEY"],
)
MODEL = os.getenv("LLM_MODEL", "claude-sonnet-5")

response = client.messages.create(
    model=MODEL,
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}],
)
print("".join(b.text for b in response.content if b.type == "text"))

This is the Anthropic SDK, so the address has no /v1; the SDK adds it. For the OpenAI SDK the address is https://api.zukko.pro/v1.

Caching the list

There is no need to call /v1/models before every request. Refresh the list at startup and then periodically, for example once an hour:

bash
import time

_cache = {"models": None, "ts": 0.0}

def get_models_cached(ttl: int = 3600) -> list[str]:
    now = time.time()
    if _cache["models"] is None or now - _cache["ts"] > ttl:
        _cache["models"] = get_available_models()
        _cache["ts"] = now
    return _cache["models"]

Even with a cache, keep model_disabled handling in the calling code: a model can be switched off between list refreshes.

Why the list changes

  • Vendors release new versions, and they appear in the catalog.
  • Old versions are switched off, by the vendor or in the gateway.
  • A model may be disabled temporarily.

Catalog news is posted in the Zukko Telegram channel. But your code should not depend on whether you read the news: rely on /v1/models and model_disabled handling. If you do not have a key for model requests yet, create one under API keys.

FAQ

Do I need a key for GET /v1/models?

No, the endpoint is public. A key is needed for requests to the models themselves: /v1/chat/completions, /v1/messages and others.

Does the list show only models allowed for my key?

No, the list is shared. Key restrictions (allowed_models) are checked when you call a model: if it is not allowed, you get 403 model_not_allowed_for_key.

What does a request to a disabled model return?

HTTP 503 with the model_disabled code in the response body. Do not retry it; switch to another model from the chain or from the current list.

How often does the list change?

There is no fixed schedule. That is why code should check the list at startup and handle model_disabled correctly at any moment.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

API model list: GET /v1/models with curl and Python · Zukko