---
title: "Models & protocols"
description: "Every model on pirouter has a vendor/model ID and accepts one or more of three protocols. This page covers how IDs are written, which protocols a model accepts, and what a mismatch returns."
canonical: "https://pirouter.ai/docs/models"
last-updated: "2026-10-13"
lang: "en"
source: "content/docs/en/(start)/models.mdx"
---

# Models & protocols

Every model on pirouter has a vendor/model ID and accepts one or more of three protocols. This page covers how IDs are written, which protocols a model accepts, and what a mismatch returns.

Every model on pirouter has an ID and accepts one or more protocols. A call has to get both right: the ID and the protocol.

## Model IDs

A model ID is written as `vendor/model`: the vendor name and the model name, joined by one slash.

Listing every model needs no key:

```bash
$ curl https://api.pirouter.ai/v1/models
```

You should see a `data` array. The `id` of each entry is the model ID to use in a call.

A name the gateway does not know returns `404` with the error code `model_not_found`.

### Aliases

A model can have aliases, usually the name without the vendor prefix. An alias works the same as the full ID.

The gateway looks up the full ID first, and the alias only when that finds nothing. An alias must match exactly as registered: it is case-sensitive, with no prefix completion and no fuzzy matching. Whichever name you call with, logs and billing record the full ID.

To see a model's aliases, read `aliases` in the response of `GET /v1/models/{vendor}/{model}/endpoints`.

## The three protocols

| Protocol  | Endpoints                                                                                          |
| --------- | -------------------------------------------------------------------------------------------------- |
| OpenAI    | `POST /v1/chat/completions`, `POST /v1/responses`, `POST /v1/images/generations`                   |
| Anthropic | `POST /v1/messages`                                                                                |
| Gemini    | `POST /v1beta/models/{model}:generateContent`, `POST /v1beta/models/{model}:streamGenerateContent` |

All endpoints are under `https://api.pirouter.ai`.

The OpenAI and Anthropic protocols carry the model ID in the `model` field of the request body and turn on streaming with the `stream` field. The Gemini protocol carries the model ID in the path and streams through the `streamGenerateContent` action.

## Which protocols each model accepts

The gateway does not translate between protocols. A model can only be called on a protocol it accepts. Some models accept one protocol; others accept the OpenAI protocol as well as their vendor's own. A few examples:

| Model ID                      | OpenAI | Anthropic | Gemini |
| ----------------------------- | ------ | --------- | ------ |
| `openai/gpt-6-sol`            | Yes    | No        | No     |
| `anthropic/claude-sonnet-5.5` | Yes    | Yes       | No     |
| `google/gemini-3.5-flash`     | Yes    | No        | Yes    |
| `google/gemini-3-pro-image`   | No     | No        | Yes    |
| `bytedance/seed-2.1-pro`      | No     | Yes       | No     |

- [Browse all models](https://pirouter.ai/models): Each model's page shows the protocols it accepts, its prices and its providers.

To check one model through the API, read `protocols` in the response of `GET /v1/models/{vendor}/{model}/endpoints`.

A protocol has several endpoints, and a model does not have to serve all of them. Image models serve `POST /v1/images/generations` and `generateContent` on the Gemini protocol, and no chat endpoint. `supported_endpoints` in the same response lists the endpoints a model serves; an empty list means every endpoint of the protocols it accepts.

## Protocol mismatch

Calling a model on a protocol it does not accept, or on an endpoint it does not serve, returns `422` with the error code `validation_failed`. The `message` lists the endpoints that model serves. The gateway refuses the request, forwards it to no provider and bills nothing.

```json
{
  "error": {
    "message": "This model is not served on the endpoint you called. Call it on POST /v1/images/generations or POST /v1beta/models/{model}:generateContent.",
    "type": "invalid_request_error",
    "code": "validation_failed"
  }
}
```

When you get this error, switch to an endpoint the `message` lists, or pick a model that serves the endpoint you are calling.

## Calling each protocol

Each of the three samples uses a model that accepts its protocol, so you can run them as they are.

### OpenAI protocol

Set the SDK's base URL to `https://api.pirouter.ai/v1`.

cURL:

```bash
curl https://api.pirouter.ai/v1/chat/completions \
  -H "Authorization: Bearer $PIROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-6-sol",
    "messages": [{ "role": "user", "content": "Hello!" }]
  }'
```

Python:

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.pirouter.ai/v1",
    api_key=os.environ["PIROUTER_API_KEY"],
)

completion = client.chat.completions.create(
    model="openai/gpt-6-sol",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(completion.choices[0].message.content)
```

TypeScript:

```ts
import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'https://api.pirouter.ai/v1',
  apiKey: process.env.PIROUTER_API_KEY,
})

const completion = await client.chat.completions.create({
  model: 'openai/gpt-6-sol',
  messages: [{ role: 'user', content: 'Hello!' }],
})
console.log(completion.choices[0].message.content)
```

To stream, add `"stream": true` to the body. [Stream the response](https://pirouter.ai/docs/quickstart.md#stream-the-response) shows the request and what comes back.

### Anthropic protocol

Set the SDK's base URL to `https://api.pirouter.ai`, without `/v1`. The SDK adds it.

cURL:

```bash
curl https://api.pirouter.ai/v1/messages \
  -H "x-api-key: $PIROUTER_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5.5",
    "max_tokens": 1024,
    "messages": [{ "role": "user", "content": "Hello!" }]
  }'
```

Python:

```python
import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://api.pirouter.ai",
    api_key=os.environ["PIROUTER_API_KEY"],
)

message = client.messages.create(
    model="anthropic/claude-sonnet-5.5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello!"}],
)
print(message.content[0].text)
```

TypeScript:

```ts
import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic({
  baseURL: 'https://api.pirouter.ai',
  apiKey: process.env.PIROUTER_API_KEY,
})

const message = await client.messages.create({
  model: 'anthropic/claude-sonnet-5.5',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Hello!' }],
})
console.log(message.content[0].text)
```

To stream, add `"stream": true` to the body and pass `-N` to curl:

```bash
curl -N https://api.pirouter.ai/v1/messages \
  -H "x-api-key: $PIROUTER_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5.5",
    "stream": true,
    "max_tokens": 1024,
    "messages": [{ "role": "user", "content": "Hello!" }]
  }'
```

You should see server-sent events arrive as pairs of lines, `event: ` then `data: `. The first event is `message_start` and the last is `message_stop`.

### Gemini protocol

Set the SDK's base URL to `https://api.pirouter.ai`. The SDK adds `/v1beta`. When you create the client, also add an `Authorization: Bearer` header through the `headers` of `http_options` (`httpOptions` in TypeScript), or requests return `401`. [Authentication & API keys](https://pirouter.ai/docs/authentication.md#send-the-key-with-a-request) explains why.

cURL:

```bash
curl "https://api.pirouter.ai/v1beta/models/google/gemini-3.5-flash:generateContent" \
  -H "Authorization: Bearer $PIROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": [{ "parts": [{ "text": "Hello!" }] }]
  }'
```

Python:

```python
import os
from google import genai
from google.genai import types

client = genai.Client(
    api_key=os.environ["PIROUTER_API_KEY"],
    http_options=types.HttpOptions(
        base_url="https://api.pirouter.ai",
        headers={"Authorization": f"Bearer {os.environ['PIROUTER_API_KEY']}"},
    ),
)

response = client.models.generate_content(
    model="google/gemini-3.5-flash",
    contents="Hello!",
)
print(response.text)
```

TypeScript:

```ts
import { GoogleGenAI } from '@google/genai'

const ai = new GoogleGenAI({
  apiKey: process.env.PIROUTER_API_KEY,
  httpOptions: {
    baseUrl: 'https://api.pirouter.ai',
    headers: { Authorization: `Bearer ${process.env.PIROUTER_API_KEY}` },
  },
})

const response = await ai.models.generateContent({
  model: 'google/gemini-3.5-flash',
  contents: 'Hello!',
})
console.log(response.text)
```

To stream, call `streamGenerateContent` in place of `generateContent`. The body stays the same, with no `stream` field:

```bash
curl -N "https://api.pirouter.ai/v1beta/models/google/gemini-3.5-flash:streamGenerateContent" \
  -H "Authorization: Bearer $PIROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": [{ "parts": [{ "text": "Hello!" }] }]
  }'
```

You should see server-sent events, each a `data: ` line that carries one piece of the answer in `candidates`. The last one carries `finishReason` and the token counts in `usageMetadata`. There is no closing marker after it.

## Capability flags

Each model carries a set of capability flags that say what it supports. The common ones:

| Capability        | Flag                                                      | Meaning                                                       |
| ----------------- | --------------------------------------------------------- | ------------------------------------------------------------- |
| Tool calling      | `supports_function_calling`                               | Can call tools and functions                                  |
| Structured output | `supports_structured_outputs`, `supports_response_schema` | Output constrained by a JSON schema; a JSON `response_format` |
| Vision            | `supports_vision`                                         | Accepts image input                                           |
| Caching           | `supports_prompt_caching`                                 | Supports prompt caching                                       |
| Reasoning         | `supports_reasoning`                                      | Supports extended thinking and produces reasoning tokens      |

To see a model's flags, read `capabilities` in the response of `GET /v1/models/{vendor}/{model}/endpoints`.

## Next steps

- [Pricing & billing](https://pirouter.ai/docs/billing.md): How each kind of token is priced and how the wallet is charged.
- [API reference](https://pirouter.ai/docs/api.md): The endpoints behind each protocol.
