# GET /v1/models

Lists every model id this gateway can serve — one row per distinct id. This is
the endpoint SDK `models.list()` calls resolve to, so it answers in **two
shapes**:

| Request | Response shape |
|---|---|
| No `anthropic-version` header | OpenAI list object |
| `anthropic-version` header present (any value) | Anthropic models list |

The Anthropic SDK sends `anthropic-version` on every request, so each SDK gets
its native shape with zero configuration.

Authentication is required, in either surface's convention — both work on
every gateway route:

```
Authorization: Bearer $TM_API_KEY
# or
x-api-key: $TM_API_KEY
```

A missing or invalid key is a 401 with `error_type: "auth"` (see
[Errors](/docs/errors)).

A key bound to your own provider connection lists that connection's models; a
key under a routing policy lists the policy's models. Deployed endpoints from
[Optimize](/docs/model-search) (`tm/<name>-v<n>`) are not listed here. Your
organization's active [dedicated endpoints](/docs/dedicated-endpoints)
(`<your-namespace>/<name>`) are, with `owned_by` `routerplus`, on the base URL they are
served from.

## OpenAI shape (default)

```bash
curl -s https://api.routerplus.com/v1/models \
  -H "Authorization: Bearer $TM_API_KEY"
```

```json
{
  "object": "list",
  "data": [
    { "id": "claude-haiku-4-5", "object": "model", "created": 1756900000, "owned_by": "anthropic",
      "architecture": { "input_modalities": ["text", "image", "file"], "output_modalities": ["text"] } },
    { "id": "gpt-image-1", "object": "model", "created": 1756900000, "owned_by": "openai",
      "architecture": { "input_modalities": ["text"], "output_modalities": ["image"] } },
    { "id": "wan-3.0", "object": "model", "created": 1756900000, "owned_by": "openrouter-media",
      "architecture": { "input_modalities": ["text"], "output_modalities": ["video"] } }
  ]
}
```

| Field | Type | Meaning |
|---|---|---|
| `id` | string | The exact string for your request's `model` field |
| `object` | `"model"` | Constant |
| `created` | integer | Unix seconds — see the note below |
| `owned_by` | string | Provider id of the first deployment routing would try for this model; `routerplus` for a dedicated endpoint |
| `architecture.input_modalities` | array | What a message to the model may carry: `"text"`, plus each of `"image"`, `"file"`, `"audio"` and `"video"` that a route of this key takes. A chat request with a part that no route takes is a 400 (see [Wire compatibility](/docs/compat#content-is-never-silently-dropped)). Image, video and decision models take `["text"]`. A route on your own provider key declares no inputs, so it adds none here; the gateway sends it every part as you sent it. A documented extension OpenAI SDKs ignore |
| `architecture.output_modalities` | array | `["text"]`, `["image"]`, `["video"]` or `["decisions"]` — a documented extension OpenAI SDKs ignore |

## Filtering by modality

```bash
curl -s "https://api.routerplus.com/v1/models?output_modalities=image" \
  -H "Authorization: Bearer $TM_API_KEY"
```

`output_modalities` takes a comma-separated list of `text`, `image`, `video` and
`decisions`; the list is a filter, so `?output_modalities=text,image,video,decisions` is
the same as sending nothing. Any other value is a 400 `invalid_request` ("invalid
output_modalities filter"). The Anthropic shape never lists image, video or decision
models — the Anthropic Messages API has no route to call them on.

A `provider` query parameter takes the same JSON object as the request-level
`provider` routing controls (`{"only":["openai"]}`, for example) and lists what
those controls would let through — see [Routing policies](/docs/routing-policies).

## Anthropic shape

```bash
curl -s https://api.routerplus.com/v1/models \
  -H "x-api-key: $TM_API_KEY" \
  -H "anthropic-version: 2023-06-01"
```

```json
{
  "data": [
    { "type": "model", "id": "claude-haiku-4-5", "display_name": "claude-haiku-4-5", "created_at": "2026-09-03T12:00:00.000Z" }
  ],
  "has_more": false,
  "first_id": "claude-haiku-4-5",
  "last_id": "gpt-4o-mini"
}
```

`has_more` is always `false`: the catalog is small and the full list arrives in
one response. There is no pagination to implement.

> [!NOTE]
> `created` / `created_at` is the time the gateway process loaded its catalog,
> not the model's release date. Don't build logic on it. For real metadata —
> context length, prices, retention policy — use the public
> [`/api/models.json`](https://app.routerplus.com/api/models.json), documented in
> [Models & catalog](/docs/models). This endpoint stays SDK-shaped plus one
> documented extension: `architecture`, which SDKs ignore and modality-aware
> clients read.

## SDK usage

```python
import os
from openai import OpenAI

client = OpenAI(base_url="https://api.routerplus.com/v1", api_key=os.environ["TM_API_KEY"])
for m in client.models.list():
    print(m.id, m.owned_by)
```

```python
import os
import anthropic

# The SDK sends anthropic-version itself, so this gets the Anthropic shape.
client = anthropic.Anthropic(base_url="https://api.routerplus.com", api_key=os.environ["TM_API_KEY"])
for m in client.models.list():
    print(m.id)
```

```typescript
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.routerplus.com/v1", apiKey: process.env.TM_API_KEY });
for await (const m of client.models.list()) {
  console.log(m.id, m.owned_by);
}
```

## Unknown models on completion calls

This list is the contract: a completion request (`/v1/chat/completions` or
`/v1/messages`) for any id **not** in it fails immediately with a 404 that
echoes exactly the string you sent — before any provider is contacted and
before anything is billed. The one exception is a dated pin of a listed model
(`claude-haiku-4-5-20251001`, `gpt-4o-2024-08-06`, `-latest`), which routes as
its family — see [Models & catalog](/docs/models#date-pinned-ids).

OpenAI surface:

```json
{
  "error": {
    "code": "model_unavailable",
    "message": "model \"gpt-5o\" is not in the catalog; GET /v1/models lists what this key can serve",
    "type": "invalid_request_error",
    "metadata": { "error_type": "model_unavailable" }
  },
  "request_id": "6f3c…"
}
```

Anthropic surface:

```json
{
  "type": "error",
  "error": {
    "type": "not_found_error",
    "message": "model \"gpt-5o\" is not in the catalog; GET /v1/models lists what this key can serve",
    "error_type": "model_unavailable"
  },
  "request_id": "6f3c…"
}
```

Both carry the `x-tm-error-code: model_unavailable` header alongside
`x-request-id`.

> [!WARNING]
> There is no fuzzy matching and no silent fallback to a "close" model. A typo
> in the model id is a 404, not a quietly different bill. Echoing your own
> requested string back names the problem without turning the error into an
> existence oracle — unknown ids all 404 identically.

Three neighbors of this error are worth telling apart:

| Status | `error_type` | Meaning |
|---|---|---|
| 404 | `model_unavailable` | The id is not in the catalog. Fix the id. |
| 503 | `gateway_error` | The id **is** in the catalog, but every deployment serving it is cooling down after failures. Comes with `retry-after: 5` and `x-tm-limit-kind: health` — retry, don't fix. |
| 400 | `model_not_priced` | Billed key, and the ledger has no price row for this model — the gateway refuses to serve what it cannot meter. |

The full taxonomy is in [Errors](/docs/errors).

One more neighbor: a listed **image** model sent to a chat route is a 400
`invalid_request` naming `POST /v1/images/generations`, not a 404 — the id
exists, you called the wrong route. A video model on a chat route names
`POST /v1/videos` the same way. The mirror case (a chat model on the images or
videos route) is the same 400 pointing back at the chat routes. See
[POST /v1/images/generations](/docs/api-images) and
[POST /v1/videos](/docs/api-videos).
