Model Discovery (/v1/models)
/v1/models publishes every public model on the platform in machine-readable form. The endpoint requires no authentication: you can call it without an API key.
The response matches OpenAI's /v1/models schema exactly and adds the fields model selection actually needs: context window, pricing, modalities and accepted request parameters. Agent frameworks and model-routing libraries can read these fields and consume the catalog without a custom adapter.
Listing every model
Section titled “Listing every model”curl https://llmtr.com/v1/models{ "object": "list", "data": [ { "id": "aion-labs/aion-3.0", "object": "model", "created": 1783601983, "owned_by": "aion-labs", "name": "AionLabs: Aion 3.0", "description": "AionLabs model for long-form fiction, roleplay and character-driven dialogue.", "context_length": 131072, "architecture": { "modality": "text->text", "input_modalities": ["text"], "output_modalities": ["text"], "tokenizer": null, "instruct_type": null }, "pricing": { "prompt": "0.000003", "completion": "0.000006", "input_cache_read": "0.00000075" }, "top_provider": { "context_length": 131072, "max_completion_tokens": 131072, "is_moderated": false }, "per_request_limits": null, "supported_parameters": [ "include_reasoning", "max_completion_tokens", "max_tokens", "parallel_tool_calls", "reasoning", "temperature", "tool_choice", "tools", "top_p" ], "supported_operations": ["CHAT_COMPLETIONS"], "supported_endpoints": ["/v1/chat/completions"] } ]}Fields
Section titled “Fields”| Field | Meaning |
|---|---|
id | Canonical identifier to send as model in a request. |
context_length | Total window: input and output combined. |
top_provider.context_length | Same value as context_length. |
top_provider.max_completion_tokens | Maximum tokens in a single response. Not the same as context_length, and usually much smaller. |
architecture.modality | input->output form. Example: text+image->text. |
pricing.* | USD per single unit (per token, per image, per call). Not per million tokens. |
supported_parameters | Request parameters this model accepts. |
reasoning | Present only on reasoning models. An absent field means "no reasoning". |
supported_operations | Operations the model can be invoked with. |
supported_endpoints | Endpoints the model can be called on. |
Pricing unit
Section titled “Pricing unit”pricing values are per token. Multiply by 1,000,000 for the per-million-token rate:
"prompt": "0.000003" -> 0.000003 * 1_000_000 = $3.00 / 1M tokensValues are published as plain decimal strings, never in scientific notation.
For tiered models — where the rate depends on input length — pricing publishes the base tier. For models with a peak-hour surcharge it publishes the off-peak base rate. See the model detail page for the full rate table.
An absent field means the metric does not apply to that model; it does not mean "0". A video model billed per second, for example, carries no prompt field.
How max_completion_tokens is determined
Section titled “How max_completion_tokens is determined”This field is never null.
Where the upstream enforces an output ceiling below the context window, the measured ceiling is published. The number is not inferred: each model is sent an oversized max_tokens and the limit is read out of the provider's own rejection, then recorded in the catalog.
Where no such ceiling has been measured, context_length is published instead. A response is part of the context, so that is always a valid upper bound — it is simply looser than the real ceiling for some models.
Publishing one of those two rather than null is deliberate: clients that see null substitute their own default, and 4096 is a common one. That advertised a model capable of 128K tokens as a 4K model.
For models with a measured ceiling the gateway enforces it before the request reaches the upstream; anything above it returns 400 with max_output_tokens_exceeded.
Filtering for tool-capable models
Section titled “Filtering for tool-capable models”Filter with the supported_parameters query parameter. Multiple values are comma-separated and a model must support all of them to match.
curl "https://llmtr.com/v1/models?supported_parameters=tools"Models supporting both reasoning and tool calling:
curl "https://llmtr.com/v1/models?supported_parameters=tools,reasoning"Agent frameworks generally cannot drive a model without tools, so this filter is the recommended way to build a catalog.
Fetching a single model
Section titled “Fetching a single model”curl https://llmtr.com/v1/models/openai/gpt-4o-miniReturns the same schema as an entry in the list. An unknown identifier returns 404.
Caching and limits
Section titled “Caching and limits”Because the endpoint is unauthenticated, resource use is governed by caching and a rate limit rather than a key:
- Responses carry
Cache-Control: public, max-age=300, s-maxage=300, stale-while-revalidate=3600, stale-if-error=86400. A shared cache keeps serving the response past the TTL while it refreshes behind the request. - Every response carries an
ETag. Send it back asIf-None-Matchand you get304 Not Modifiedwith no body transferred when the catalog has not changed. - 120 requests per minute per IP. Exceeding it returns
429withRetry-After.
A tool that polls the catalog regularly should store the ETag and send If-None-Match:
ETAG=$(curl -sI https://llmtr.com/v1/models | grep -i '^etag:' | cut -d' ' -f2- | tr -d '\r')curl -s -o /dev/null -w "%{http_code}\n" \ -H "If-None-Match: $ETAG" \ https://llmtr.com/v1/modelsBrowser access
Section titled “Browser access”The endpoint is open to every origin (Access-Control-Allow-Origin: *) and carries no credentials, so browser-based tools can read it with a plain fetch:
const response = await fetch("https://llmtr.com/v1/models");const { data } = await response.json();
const toolCapable = data.filter((model) => model.supported_parameters?.includes("tools"));