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:
curl https://api.zukko.pro/v1/modelsThe response is OpenAI-compatible: an object with the list in the data field:
{
"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:
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:
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;503withoutmodel_disabled: a temporary provider issue, retrying after a pause makes sense.
import openai
def is_model_disabled(err: openai.APIStatusError) -> bool:
return err.status_code == 503 and "model_disabled" in err.response.texterr.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.
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:
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 chainOn 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
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:
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.
Related articles
- xAI Grok API: Connecting Grok 4.7, Price and the 256K ContextHow to call grok-4.7 through an OpenAI-compatible API, what to do about the 256K context limit and what typical requests cost. Plus a comparison with Grok 4.6.
- Grok API from Russia: connecting without a foreign cardStep by step connection to Grok 4.7, 4.6 and 4.5 through a gateway: key, address, first request in curl and Python, streaming, images and payment by SBP or crypto.
- Claude Sonnet 5.5 via API: connection, prices and cache at October 18, 2026How to call claude-sonnet-5-5 from Python and curl, what a typical request costs with and without cache, and how it differs from Sonnet 5 and Sonnet 4.6.
- Claude on Android in Russia: what to do if the app won't installIf the official Claude app won't install on Android in Russia, you can still use the model from your phone: through the gateway's web chat or a client with your own key.