# GET /v1/models

`GET /v1/models` lists every model id RouterPlus can serve on your key, one row per id.
`GET /v1/models/<id>` returns one row of that list. SDK `models.list()` and `models.retrieve()`
calls resolve to these endpoints, so both answer in two shapes:

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

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

Send your key as `Authorization: Bearer $TM_API_KEY` or as `x-api-key: $TM_API_KEY`. A missing
or invalid key is a 401 with `error_type: "auth"` (see [Errors](/docs/errors)).

## List models: OpenAI shape (default)

```bash
curl -s https://demo-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",
      "context_length": 200000,
      "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": "typesafe/jev-1.13", "object": "model", "created": 1756900000, "owned_by": "typesafe",
      "architecture": { "input_modalities": ["text"], "output_modalities": ["decisions"] } }
  ]
}
```

| Field | Type | Meaning |
|---|---|---|
| `id` | string | The exact string for the `model` field of your request. |
| `object` | `"model"` | Constant. |
| `created` | integer | Unix seconds. See the note under [Anthropic shape](#anthropic-shape). |
| `owned_by` | string | The provider id of the first deployment routing would try for this model. `routerplus` for a dedicated endpoint. |
| `context_length` | integer | Chat models only. The largest context window, in tokens, among the routes of your key for this model. Absent when no route declares a window. |
| `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 and decision models take `["text"]`. A route on your own provider key declares no inputs, so it adds none here. RouterPlus sends it every part as you sent it. |
| `architecture.output_modalities` | array | `["text"]`, `["image"]`, `["video"]`, `["decisions"]`, `["embeddings"]`, `["transcription"]` or `["audio"]`. A transcription model takes `["audio"]` and a speech model takes `["text"]`. |

OpenAI SDKs ignore `context_length` and `architecture`. Clients that size their context window
or pick a route by modality read them.

## What the list contains

- A key bound to your own provider connection lists the models of that connection.
- A key under a routing policy lists the models of the policy.
- Your organization's active [dedicated endpoints](/docs/dedicated-endpoints)
  (`<your-namespace>/<name>`) are listed with `owned_by` `routerplus`, on the base URL they are
  served from.
- Deployed endpoints from [Optimize](/docs/model-search) (`tm/<name>-v<n>`) are not listed.
- The OpenAI shape lists the `rp-` name of each Claude model (`rp-sonnet-5-5` for
  `claude-sonnet-5-5`) right after its model. That row has the same `owned_by`,
  `context_length` and `architecture`, and a `canonical_slug` that names the model. Model
  pickers that read this list, such as Cursor's, show these names. See
  [Claude names for Cursor](/docs/models#claude-names-for-cursor). The Anthropic shape does
  not list them.

## Filtering

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

`output_modalities` takes a comma-separated list of these values:

| Value | Models for |
|---|---|
| `text` | Chat |
| `image` | Image generation |
| `video` | Video generation |
| `decisions` | `POST /v1/decisions` |
| `embeddings` | `POST /v1/embeddings` |
| `transcription` | `POST /v1/audio/transcriptions` |
| `audio` | `POST /v1/audio/speech` |

- The list is a filter. `?output_modalities=text,image,video,decisions,embeddings,transcription,audio`
  is the same as no filter.
- A kind with no model in the catalog gives an empty list.
- Any other value is a 400 `invalid_request` ("invalid output_modalities filter").
- The Anthropic shape lists chat models only. The Anthropic Messages API has no route for image
  or decision models.

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

## Anthropic shape

```bash
curl -s https://demo-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 full list arrives in one response, with no pagination.

> [!NOTE]
> `created` and `created_at` are the time the gateway process loaded its catalog. They are not
> the release date of the model, so do not build logic on them. For prices and retention
> policy, use the public [`/api/models.json`](https://demo.routerplus.com/api/models.json), documented in
> [Models & catalog](/docs/models).

## Retrieve one model

`GET /v1/models/<id>` returns the row that `GET /v1/models` gives your key for that id, in the
same shape rules. An id with a slash, such as `typesafe/jev-1.13`, works as it is or
URL-encoded.

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

```json
{ "id": "claude-haiku-4-5", "object": "model", "created": 1756900000, "owned_by": "anthropic",
  "context_length": 200000,
  "architecture": { "input_modalities": ["text", "image", "file"], "output_modalities": ["text"] } }
```

With `anthropic-version`, the answer is one Anthropic row, for example
`{"type": "model", "id": "claude-haiku-4-5", "display_name": "claude-haiku-4-5", "created_at": "2026-09-03T12:00:00.000Z"}`.

An id that the list does not give your key is a 404 `model_unavailable`, with the same body as
in [Unknown models](#unknown-models-on-completion-calls). In the Anthropic shape, this includes
every model that is not a chat model.

`GET /v1/models/<id>/endpoints` is a separate route. It lists each provider of one model with
its price, data policy, status and measurements. See [Provider routing](/docs/provider-routing).

## SDK usage

```python
import os
from openai import OpenAI

client = OpenAI(base_url="https://demo-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://demo-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://demo-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

A completion request (`/v1/chat/completions` or `/v1/messages`) for an id that is not in this
list fails with a 404. The error echoes the string you sent. RouterPlus contacts no provider
and bills nothing. One exception: a dated pin of a listed model (`claude-haiku-4-5-20251001`,
`gpt-4o-2024-08-06`, `-latest`) 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 and `x-request-id`.

> [!WARNING]
> RouterPlus does no fuzzy matching and never falls back to a similar model. A typo in the
> model id is a 404. Every unknown id gets the same 404, so the error does not reveal which
> ids exist.

## Errors

| 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 the request. |
| 400 | `model_not_priced` | Billed key, and the ledger has no price row for this model. RouterPlus does not serve a model it cannot meter. |
| 400 | `invalid_request` | A listed image model sent to a chat route. The message names `POST /v1/images/generations`. A chat model sent to the images route gets the same 400, pointing at the chat routes. See [POST /v1/images/generations](/docs/api-images). |

[Errors](/docs/errors) has the full list.
