allowed_models: restricting a key to only the models it needs
A key that can call any model in the catalogue is more freedom than an integration needing exactly one model should have. Here is the allowed_models setting.
3 min · · Updated · API keys
By default, a fetch_... key can call any model in the catalogue — from the cheap glm-5.3-flash to claude-opus-5-5. For a lot of use cases that's more freedom than needed: if a key is issued to a narrow service, it might need exactly one model, not the whole catalogue.
What this setting does
On a specific key's page in the dashboard (https://zukko.pro/app/api-keys), you can set a list of models allowed for that key. A request using that key against any model outside the list gets rejected before it ever reaches the provider.
This setting sits next to the daily/monthly spend limit on the same key page — two independent but complementary ways to constrain one key: the limit controls "how much," the model list controls "with what."
What the rejection looks like
{
"error": {
"message": "Model 'claude-opus-5-5' is not allowed for this API key",
"code": "model_not_allowed_for_key"
}
}The status is 403. Worth distinguishing from 404 model_not_found: the model exists in the catalogue and the request is syntactically valid — this specific key's policy just forbids calling it.
curl https://api.zukko.pro/v1/chat/completions \
-H "Authorization: Bearer fetch_restricted_key" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5-5",
"messages": [{"role": "user", "content": "..."}]
}'
# -> 403 model_not_allowed_for_key, if claude-opus-5-5 isn't in this key's allowed listWhy this matters
Guarding against accidentally calling an expensive model. claude-opus-5-5 costs $0.30/$0.30 per 1M tokens, while glm-5.3-flash is $0.04/$0.04 — 7.5 times cheaper. If a service is only supposed to run on a fast, cheap model, and a bug or a copy-pasted example leaves a different model in the code, the allowlist stops the call before it's billed, not after.
A key for a third-party service or partner. If you hand a key to a contractor for one specific integration — say, only a support chatbot running on glm-5.3-flash — there's no reason that same key should also be able to call other models or switch to a pricier model without your knowledge.
Separate keys per component of one product. A backend service that needs claude-sonnet-5 for core logic and a separate ticket-classification module that only needs glm-5.3-flash can hold two keys with non-overlapping allowed-model lists — a leak of one key doesn't open access to the whole catalogue.
An explicit record of what a service actually uses. The allowed-models list in the dashboard is, in effect, a declaration: this key is meant for exactly these models. If six months later model_not_allowed_for_key shows up in the logs, that's a hint either the wrong model snuck into the code, or the list needs deliberate expansion rather than a silent workaround.
Handling it in code
import requests
ALLOWED_FALLBACK = "glm-5.3-flash"
def call_model(model_id, messages, key):
resp = requests.post(
"https://api.zukko.pro/v1/chat/completions",
headers={"Authorization": f"Bearer {key}"},
json={"model": model_id, "messages": messages},
)
if resp.status_code == 403:
data = resp.json()
if data.get("error", {}).get("code") == "model_not_allowed_for_key":
# Don't try to guess a replacement here — unlike model_retired,
# the server doesn't suggest one. Log it and decide deliberately.
raise PermissionError(
f"Model '{model_id}' is not allowed for this key. "
f"Check allowed_models in the dashboard or use a different key."
)
resp.raise_for_status()
return resp.json()A key difference from model_retired: there, the server names the replacement in the message text because the substitution is objective — the model is retired for everyone. Here the restriction is specific to your own key and reflects your own policy, so the server doesn't suggest an alternative — deciding what to do (expand the list or use a different key) is on you.
A common configuration mistake
If a service alternates between models — say, primary claude-sonnet-5 with a fallback to gpt-5.6-sol for when the first returns 503 provider_unavailable — both models need to be in the allowlist up front. Otherwise the fallback logic itself runs into a 403 when the primary provider is down, and instead of switching to the backup model, the service gets a second error stacked on the first.
Bottom line
allowed_models is about scoping a key to a specific job, not spend protection on its own — that's what the separate spend limit is for. It's configured on the key's page in the dashboard, it triggers as 403 model_not_allowed_for_key, and unlike model_retired, it doesn't offer a replacement — because the restriction is yours, not a consequence of catalogue changes.
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
- OpenAI API Keys in the Open: Why You Should Not Use ThemWhere free keys on GitHub come from, why using them is dangerous for you and for the owner, and how to protect your own key from the same leak.
- GLM API Errors: Why the Request Fails and How to Fix ItA wrong model id, an overflowing 128K context, the key, the balance, limits: the typical causes of GLM API errors and a quick way to find yours.
- 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.
- This organization has been disabled in the Claude API: What to DoAn Anthropic key suddenly answers "organization has been disabled". What the error is, what to check, and how to move the SDK and Claude Code to another provider.