curl --request GET \
--url https://api.opper.ai/v3/compat/models \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.opper.ai/v3/compat/models"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.opper.ai/v3/compat/models', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"data": [
{
"created": 123,
"id": "<string>",
"object": "<string>",
"owned_by": "<string>",
"architecture": {
"input_modalities": [
"<string>"
],
"modality": "<string>",
"output_modalities": [
"<string>"
]
},
"context_length": 123,
"opper": {
"kind": "<string>",
"capabilities": [
"<string>"
],
"country": "<string>",
"description": "<string>",
"display_name": "<string>",
"gdpr_residency": "<string>",
"maker": "<string>",
"maker_country": "<string>",
"max_output_tokens": 123,
"members": [
"<string>"
],
"pricing": {
"kind": "variable"
},
"reasoning": {
"supported": [
"<string>"
],
"default": "<string>"
},
"region": "<string>",
"type": "<string>",
"verification": "<string>",
"version": 123,
"zdr": {
"caching": true,
"logging": true,
"moderation": true,
"subprocessors": true,
"training": true
}
},
"pricing": {
"completion": "<string>",
"prompt": "<string>",
"input_cache_read": "<string>",
"input_cache_write": "<string>",
"input_cache_write_1h": "<string>",
"internal_reasoning": "<string>",
"web_search": "<string>"
}
}
],
"object": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"meta": {}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"meta": {}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"meta": {}
}List Models
OpenAI Models API compatible endpoint. Returns everything the caller can put in model — in OpenAI’s models.list shape — so tools that point an OpenAI-compatible base URL at /v3/compat (gateways, n8n, LangChain, etc.) can discover the live catalogue without hardcoding model IDs.
Human labels are optional opper.display_name metadata; id remains the callable identifier. Dynamic routes additionally carry opper.pricing.kind=variable, never a fixed numeric price. Every entry carries opper.kind, which names what it is:
model— a concrete catalog row (LLM or embedding), e.g.anthropic/claude-sonnet-4.5. Pinning this id pins that provider.pool— a bare model name that load-balances across providers, e.g.claude-sonnet-4.5.opper.memberslists the catalog rows it routes over. Listed only for names that genuinely fan out (two or more routable members).dynamic_route— one of the calling org’s deployed routing graphs, addressed asdynamic/<name>.opper.versionis the deployed version a call would execute. Org-scoped to the API key; draft-only routes are not listed. No numeric pricing is reported — the graph picks the model per request — butcontext_lengthandopper.max_output_tokensARE, as the smallest any reachable candidate offers: a floor is a guarantee rather than a guess, and clients that must size a context before calling would otherwise have to invent one. Omitted when any candidate cannot be resolved here (e.g. an org-scoped BYOK model).
Narrow with ?type= (comma-separated, e.g. ?type=pool,dynamic_route); omitting it returns all three kinds, with concrete models first (their long-standing order is unchanged), then pools, then routes.
Entries additionally carry OpenRouter-shaped pricing (USD per token) and context_length, plus an opper block holding the entry’s type (llm or embedding — this list carries both, and capabilities cannot tell them apart), the model’s capabilities (text, tools, vision, pdf, structured_output, … — the same vocabulary as /v3/models), its max_output_tokens, selectable effort controls under opper.reasoning (supported and optional default), and a compliance summary (region, country, zdr, gdpr_residency, verification) so a client can declare what a model can do and filter on residency/ZDR criteria in one call; zdr is the tri-state retention facts object (logging, moderation, caching, training, subprocessors; null = not established), not a summary word — derive what you need from it; full data-handling detail lives on /v3/models?include=route. A pool reports a capability or compliance property only when EVERY member has it, takes the smallest context_length / max_output_tokens across members, and prices at the worst case. Pool effort controls are the intersection of all allowed members’ declared ladders; supported: [] means no portable selectable effort, even if members think internally. The default is present only if all members agree on a supported value. Concrete rows without declared effort controls and dynamic routes omit opper.reasoning. none is an explicit off-switch only when listed; omitting an effort leaves provider defaults in control. These fields do not change routing. This means a capability check, a residency filter or a pre-call cost estimate can never be flattered by one lucky member. Dynamic routes report only capabilities shared by every resolvable allowed candidate; an unresolved candidate leaves capabilities unknown. Their standard numeric pricing remains omitted because the graph picks the model per request. The whole listing is scoped to the caller’s comply allowlist: denied models, denied pool members, pools with no permitted member, and routes whose every model node is denied are all absent. Featured models first, then ID-ascending; no pagination, matching OpenAI.
curl --request GET \
--url https://api.opper.ai/v3/compat/models \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.opper.ai/v3/compat/models"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.opper.ai/v3/compat/models', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"data": [
{
"created": 123,
"id": "<string>",
"object": "<string>",
"owned_by": "<string>",
"architecture": {
"input_modalities": [
"<string>"
],
"modality": "<string>",
"output_modalities": [
"<string>"
]
},
"context_length": 123,
"opper": {
"kind": "<string>",
"capabilities": [
"<string>"
],
"country": "<string>",
"description": "<string>",
"display_name": "<string>",
"gdpr_residency": "<string>",
"maker": "<string>",
"maker_country": "<string>",
"max_output_tokens": 123,
"members": [
"<string>"
],
"pricing": {
"kind": "variable"
},
"reasoning": {
"supported": [
"<string>"
],
"default": "<string>"
},
"region": "<string>",
"type": "<string>",
"verification": "<string>",
"version": 123,
"zdr": {
"caching": true,
"logging": true,
"moderation": true,
"subprocessors": true,
"training": true
}
},
"pricing": {
"completion": "<string>",
"prompt": "<string>",
"input_cache_read": "<string>",
"input_cache_write": "<string>",
"input_cache_write_1h": "<string>",
"internal_reasoning": "<string>",
"web_search": "<string>"
}
}
],
"object": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"meta": {}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"meta": {}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"meta": {}
}Authorizations
API key authentication. Pass your API key as a Bearer token.
Query Parameters
Comma-separated entry kinds to return: model, pool, dynamic_route. Omit for all three.
"pool,dynamic_route"
Set to codex for the native authenticated Codex remote catalog (models, with tool-capable LLMs and routes only). Absent or empty returns the unchanged OpenAI models.list shape (data). Other values return 400. Native catalogs apply the same caller policy and entitlement checks and fail with 503 if route discovery is unavailable. Responses exceeding Codex's 1 MiB limit return 400; use a narrower type filter.
codex, For format=codex, retain this exact authorized tool-capable model even outside the type filter and place it first. Unknown, denied, empty and unsupported selections uniformly return 400. Does not change authorization or inference routing.
Codex appends its version to native catalog requests. Accepted for compatibility; does not change the catalog.