Models
Discovering models and choosing between them.
AOCore routes requests to the model your API key is allowed to use. This page covers three things:
- How to discover what models you can call.
- How model allowlists work on minted keys.
- The 404 anti-enumeration security property — why
/v1/models/{model}and chat-style endpoints return404, not403, for models you can’t access.
Discovering available models
GET /v1/models — list
Returns the full set of models your API key is allowed to call. The list is
intersected with your key’s models[] allowlist — out-of-allowlist models
are filtered out of the response (not returned with a “forbidden” marker; simply
absent).
curl -H "Authorization: Bearer sk-…" \
https://gateway.core.aocyber.ai/v1/models
{
"object": "list",
"data": [
{ "id": "gpt-4o-mini", "object": "model", "owned_by": "openai" },
{ "id": "claude-3-5-sonnet-20241022", "object": "model", "owned_by": "anthropic" }
]
}
If your key has an empty models[] (the default for keys minted without an
explicit allowlist), the list shows your developer’s full allowlist. If your
developer has no allowlist either, the list shows every registered model on the
gateway. Empty allowlist == inherit upward.
GET /v1/models/{model} — point-lookup
Returns a single model’s metadata if you can call it; returns 404 if you can’t
(see the next section for why this is 404 rather than 403).
curl -i -H "Authorization: Bearer sk-…" \
https://gateway.core.aocyber.ai/v1/models/gpt-4o-mini
GET /developer/v1/models/available — what you can grant
This is a developer-side management endpoint (cookie auth, not API-key auth).
It returns the models your developer role is allowed to grant when minting new
keys — the intersection of your developer’s users.models[] and the registry.
Use it to populate the model picker in your key-mint UI. The endpoint never includes models outside your developer’s allowlist, so an SDK / portal built on top of it cannot expose restricted model names even by accident.
How allowlists work
There are three allowlists in the chain, each narrowing the one above it:
- Registry. The gateway’s
modelstable — every model the deployment knows about. Set at deploy time by the administrator. - Developer allowlist —
users.models[]on your developer user. The administrator sets this when they grant you developer access. Empty = inherit the full registry. - API-key allowlist —
api_keys.models[]on each minted key. You choose this at mint time. Empty = inherit your developer allowlist.
A /v1/chat/completions request must satisfy all three: the model exists in the
registry and is in your developer allowlist and is in your key allowlist.
Mint-time intersections are enforced server-side:
- You can’t mint a key with a
models[]that includes a model outside your developer allowlist — the request returns400with a clear error. - You can’t pass
models: []to mean “any model” — the empty array defaults to your developer allowlist (TestCreate_DefaultsModels). Explicitnullis rejected.
This means a developer with ["gpt-4o-mini"] cannot mint a key for gpt-4,
even if the registry contains gpt-4 — the intersection at mint time is the
only place where this is enforced cleanly, before the key is created.
The 404 anti-enumeration property
When you call GET /v1/models/{model} for a model your key is not allowed
to access, the response is 404 Not Found, not 403 Forbidden. The spec
documents this verbatim:
Returned both when the model genuinely does not exist AND when the model exists but is not in the caller’s allowlist.
This is intentional. Distinguishing the two cases — “model doesn’t exist” vs
“model exists but you can’t use it” — would let a caller enumerate the gateway’s
full model catalog by probing each name and recording which return 403 vs 404.
Treating both cases identically as 404 makes the model namespace opaque to
unauthorized callers.
The same property extends to:
/developer/v1/models/available— restricted developers never see out-of-allowlist model names in the response. This is proven byTestAvailable_RestrictedDeveloper_NeverLeaksRestrictedModelNamewhich scans the raw response body withstrings.Containsfor any restricted model name.- Body endpoints (
/v1/chat/completions,/v1/embeddings, …) that mention an unauthorized model in the request body. These return400(validation error) or404(model-not-found), not403. Same anti-enumeration logic.
Why not 403?
403 would semantically mean “the resource exists, but you can’t access it.”
That answer leaks the resource’s existence. AOCore’s threat model treats the
model catalog as confidential to non-privileged developers — listing the full
gateway catalog should require explicit allowlisting, not negative inference.
The trade-off: an SDK author calling /v1/models/{model} cannot distinguish
“this model name was typo’d” from “I don’t have permission to this model” in
isolation. The remediation is to call /v1/models first (which lists exactly
what you can call) before any point-lookup. Modern SDKs already do this for
unrelated reasons (caching, model-feature detection).
Choosing a model
/v1/chat/completions is OpenAI-compatible. To call:
- An OpenAI model — pass its OpenAI model id (e.g.
"gpt-4o-mini"). - An Anthropic model — use
/v1/messages(native Anthropic shape) or pass the model id directly through/v1/chat/completions(gateway translates the request shape). - A Google AI model — use
/v1beta/models/{model}:generateContent(native Google shape).
The gateway routes based on the model id’s prefix or registry entry. See examples/curl/chat-completion.sh and the API reference for the full list of registered models.
Common pitfall — empty models[] at mint time
Minting a key with models: [] (empty array) does not mean “no models
allowed.” It means “inherit my developer’s allowlist.” If you want a key that
genuinely can’t call anything, mint it with a single allowlist entry and revoke
that single entry’s grant — the more pragmatic answer is to just not mint the
key.
This invariant is locked by
TestCreate_DefaultsModels:
when api_keys.models is empty/null at INSERT,
the developer's users.models[] is treated as the effective allowlist
on every /v1/* request.
Related
- the authentication guide — how to send the API key for /v1/* calls
- the rate-limits guide — quota headers (allowlist is a separate gate from rate-limits)
- the errors guide —
APIErrorshape;404vs403response codes - the Quickstart — choosing a
models[]at mint time - API reference —
/v1/models+/v1/models/{model}specs