---
title: Models endpoints
description: Reference for the LLM endpoints in Anthropic, OpenAI and Gemini format, with the Lovely Router headers each one accepts and returns.
layout: reference
---

The model endpoints are the vendors' own APIs, served at `https://lovelyrouter.com`. Request and response bodies follow the vendor schema and pass through unchanged, so this page lists the parameters you use most and links to the vendor reference for the rest. Behavior common to all of them is described in [How the gateway works](/docs/models).

Every endpoint accepts the API key as `Authorization: Bearer`, `x-api-key`, `x-goog-api-key` or a `?key=` query parameter. Errors are returned in the format of the API you called; see [Errors](/docs/api/errors).

:::params Response headers
- `x-lovely-request-id` string
  Id of the call, for example `req_6f1c0b9e2d4a47a1b8c3d5e7`. Shown in [Logs](/app/logs). Present on successful responses and on `429` capacity and `502` errors.
- `x-lovely-group` string
  The [channel group](/docs/models/channel-groups) that served the call.
- `x-lovely-channel` string
  The group's channel label, URL-encoded.
- `x-lovely-queued-ms` integer
  Milliseconds the request waited for capacity. Only present when it waited.
- `retry-after` integer
  Seconds to wait. Sent with `429` responses caused by key limits or busy capacity.
:::

## Create a message

::endpoint POST /v1/messages

Generates a response from a model using the [Anthropic Messages API](https://platform.claude.com/docs/en/api/messages). Set `stream` to receive server-sent events. Thinking, tools, images and prompt caching pass through as Anthropic documents them. Guide: [Anthropic Messages](/docs/models/anthropic).

:::params Headers
- `x-api-key` string, required
  Your Lovely Router key. `Authorization: Bearer` works too.
- `anthropic-version` string, default `2023-06-01`
  Forwarded to the model.
- `anthropic-beta` string
  Forwarded as sent.
- `x-lovely-group` string
  Channel group for this request, such as `claude-value`. Ignored when the key is bound to a group.
- `x-session-id` string
  Stable id for the conversation, used to route every turn to the channel that holds its cache.
:::

:::params Body parameters
- `model` string, required
  Model id from `GET /v1/models`, such as `claude-sonnet-5.5`. Other spellings like `claude-sonnet-5-5` resolve to the same model.
- `messages` array, required
  The conversation so far, alternating `user` and `assistant` turns.
- `max_tokens` integer, required
  Maximum number of tokens to generate.
- `system` string or array
  System prompt. Text blocks accept `cache_control`.
- `stream` boolean, default `false`
  Stream the response as server-sent events.
- `thinking` object
  Extended thinking settings for the model.
- `output_config` object
  Output settings such as `effort` and `format`.
- `tools` array
  Tools the model may call.
- `tool_choice` object
  How the model chooses tools.
- `metadata` object
  Request metadata.
  - `user_id` string
    Also used as the conversation id for routing when no `x-session-id` header is sent.
:::

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/messages \
  -H "x-api-key: $LOVELY_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-5.5",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Hello, Claude"}]
  }'
```

```python title="Python" aside
import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://lovelyrouter.com",
    api_key=os.environ["LOVELY_API_KEY"],
)
message = client.messages.create(
    model="claude-sonnet-5.5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello, Claude"}],
)
```

```js title="Node.js" aside
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({
  baseURL: 'https://lovelyrouter.com',
  apiKey: process.env.LOVELY_API_KEY,
});
const message = await client.messages.create({
  model: 'claude-sonnet-5.5',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Hello, Claude' }],
});
```

:::aside
```json title="Response"
{
  "id": "msg_01XFDUDYJgAACzvnptvVoYEL",
  "type": "message",
  "role": "assistant",
  "model": "claude-sonnet-5-5",
  "content": [
    {"type": "text", "text": "Hello! How can I help you today?"}
  ],
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": {
    "input_tokens": 10,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0,
    "output_tokens": 12
  }
}
```
:::

## Count message tokens

::endpoint POST /v1/messages/count_tokens

Estimates the input tokens of a message request without running it. Free, and available with a zero balance. The count is approximate: use it for context-window checks and budgeting; the charge for a real call comes from its `usage`. [Anthropic's token counting guide](https://platform.claude.com/docs/en/build-with-claude/token-counting).

:::params Headers
- `x-api-key` string, required
  Your Lovely Router key. `Authorization: Bearer` works too.
- `x-lovely-group` string
  Channel group to check the model against. Ignored when the key is bound to a group.
:::

:::params Body parameters
- `model` string, required
  Model id, as for `POST /v1/messages`.
- `messages` array, required
  The messages to count.
- `system` string or array
  System prompt to include in the count.
- `tools` array
  Tool definitions to include in the count.
:::

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/messages/count_tokens \
  -H "x-api-key: $LOVELY_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model": "claude-sonnet-5.5",
       "messages": [{"role": "user", "content": "Hello, Claude"}]}'
```

```python title="Python" aside
count = client.messages.count_tokens(
    model="claude-sonnet-5.5",
    messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(count.input_tokens)
```

```js title="Node.js" aside
const count = await client.messages.countTokens({
  model: 'claude-sonnet-5.5',
  messages: [{ role: 'user', content: 'Hello, Claude' }],
});
console.log(count.input_tokens);
```

:::aside
```json title="Response"
{
  "input_tokens": 24
}
```
:::

## Create a chat completion

::endpoint POST /v1/chat/completions

Generates a response using the [OpenAI Chat Completions API](https://platform.openai.com/docs/api-reference/chat/create). Every model in `GET /v1/models` accepts this format. Streaming requests always get `stream_options.include_usage: true`, so the last chunk carries `usage`. Guide: [OpenAI Chat Completions](/docs/models/openai).

:::params Headers
- `Authorization` string, required
  `Bearer` followed by your Lovely Router key.
- `openai-beta` string
  Forwarded as sent.
- `x-lovely-group` string
  Channel group for this request, such as `gpt-value`. Ignored when the key is bound to a group.
- `x-session-id` string
  Stable id for the conversation, used to route every turn to the channel that holds its cache.
:::

:::params Body parameters
- `model` string, required
  Model id from `GET /v1/models`, such as `gpt-6.1-sol`.
- `messages` array, required
  The conversation so far: `system` or `developer`, `user`, `assistant` and `tool` messages.
- `stream` boolean, default `false`
  Stream the response as server-sent events, ending with `data: [DONE]`.
- `max_completion_tokens` integer
  Upper bound on generated tokens, including reasoning tokens.
- `reasoning_effort` string
  How much a reasoning model thinks. Values depend on the model.
- `tools` array
  Functions the model may call.
- `tool_choice` string or object
  How the model chooses tools.
- `response_format` object
  Request JSON output or a JSON Schema.
- `prompt_cache_key` string
  Improves cache hit rates. Also used as the conversation id for routing.
- `user` string
  End-user id. Used as the conversation id for routing when nothing more specific is sent.
:::

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/chat/completions \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "gpt-6.1-sol",
    "messages": [{"role": "user", "content": "Hello!"}]
  }'
```

```python title="Python" aside
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://lovelyrouter.com/v1",
    api_key=os.environ["LOVELY_API_KEY"],
)
completion = client.chat.completions.create(
    model="gpt-6.1-sol",
    messages=[{"role": "user", "content": "Hello!"}],
)
```

```js title="Node.js" aside
import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://lovelyrouter.com/v1',
  apiKey: process.env.LOVELY_API_KEY,
});
const completion = await client.chat.completions.create({
  model: 'gpt-6.1-sol',
  messages: [{ role: 'user', content: 'Hello!' }],
});
```

:::aside
```json title="Response"
{
  "id": "chatcmpl-B9MHDbslfkBeAs8l4bebGdFOJ6PeG",
  "object": "chat.completion",
  "created": 1791700000,
  "model": "gpt-6.1-sol",
  "choices": [
    {
      "index": 0,
      "message": {"role": "assistant", "content": "Hello! How can I help?"},
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 12,
    "total_tokens": 21,
    "prompt_tokens_details": {"cached_tokens": 0},
    "completion_tokens_details": {"reasoning_tokens": 0}
  }
}
```
:::

## Create a response

::endpoint POST /v1/responses

Generates a response using the [OpenAI Responses API](https://platform.openai.com/docs/api-reference/responses/create), the API Codex CLI uses. Served for GPT models only; other models return `404`. Responses aren't stored: send the whole conversation in `input`. Guide: [Responses API](/docs/models/openai#responses-api).

:::params Headers
- `Authorization` string, required
  `Bearer` followed by your Lovely Router key.
- `openai-beta` string
  Forwarded as sent.
- `x-lovely-group` string
  Channel group for this request, such as `gpt-value`. Ignored when the key is bound to a group.
- `x-session-id` string
  Stable id for the conversation, used to route every turn to the channel that holds its cache.
:::

:::params Body parameters
- `model` string, required
  A GPT model id from `GET /v1/models`, such as `gpt-6.1-sol`.
- `input` string or array, required
  The prompt, or the conversation as a list of input items.
- `instructions` string
  System instructions. The response echoes what you sent, or `null`.
- `stream` boolean, default `false`
  Stream typed server-sent events, ending with `response.completed` (which carries `usage`).
- `max_output_tokens` integer
  Upper bound on generated tokens, including reasoning tokens.
- `reasoning` object
  Reasoning settings such as `effort`. Values depend on the model.
- `tools` array
  Functions and other tools the model may call.
- `store` boolean
  Send `false`: responses aren't kept for later retrieval.
- `prompt_cache_key` string
  Improves cache hit rates. Also used as the conversation id for routing.
:::

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/responses \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "gpt-6.1-sol",
    "input": "Hello!",
    "store": false
  }'
```

```python title="Python" aside
import os
from openai import OpenAI

client = OpenAI(base_url="https://lovelyrouter.com/v1", api_key=os.environ["LOVELY_API_KEY"])
response = client.responses.create(model="gpt-6.1-sol", input="Hello!", store=False)
print(response.output_text)
```

```js title="Node.js" aside
import OpenAI from 'openai';

const client = new OpenAI({ baseURL: 'https://lovelyrouter.com/v1', apiKey: process.env.LOVELY_API_KEY });
const response = await client.responses.create({ model: 'gpt-6.1-sol', input: 'Hello!', store: false });
console.log(response.output_text);
```

```json title="Response" aside
{
  "id": "resp_06c0301443…",
  "object": "response",
  "status": "completed",
  "model": "gpt-6.1-sol",
  "instructions": null,
  "output": [
    { "type": "message", "role": "assistant", "content": [{ "type": "output_text", "text": "Hi! How can I help?" }] }
  ],
  "usage": {
    "input_tokens": 9,
    "input_tokens_details": { "cached_tokens": 0 },
    "output_tokens": 11,
    "output_tokens_details": { "reasoning_tokens": 0 },
    "total_tokens": 20
  }
}
```

## Generate content

::endpoint POST /v1beta/models/{model}:generateContent

Generates a response using the [Gemini API](https://ai.google.dev/api/generate-content). Guide: [Gemini](/docs/models/gemini).

:::params Path parameters
- `model` string, required
  Model id, such as `gemini-3.1-pro`. Google spellings like `gemini-3.1-pro-preview` resolve to the same model.
:::

:::params Headers
- `x-goog-api-key` string, required
  Your Lovely Router key. `Authorization: Bearer` or a `?key=` query parameter work too.
- `x-lovely-group` string
  Channel group for this request, such as `gemini-value`. Ignored when the key is bound to a group.
- `x-session-id` string
  Stable id for the conversation, used to route every turn to the channel that holds its cache.
:::

:::params Body parameters
- `contents` array, required
  The conversation so far, as `user` and `model` turns made of `parts`.
- `systemInstruction` object
  System instruction, as a content object with text parts.
- `generationConfig` object
  Output settings: `maxOutputTokens`, `temperature`, `responseMimeType`, `thinkingConfig` and more.
- `tools` array
  Function declarations and other tools.
- `toolConfig` object
  How the model chooses functions.
- `safetySettings` array
  Per-category safety thresholds.
:::

```bash title="cURL" aside
curl "https://lovelyrouter.com/v1beta/models/gemini-3.1-pro:generateContent" \
  -H "x-goog-api-key: $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"contents": [{"role": "user", "parts": [{"text": "Hello!"}]}]}'
```

```python title="Python" aside
import os
from google import genai
from google.genai import types

client = genai.Client(
    api_key=os.environ["LOVELY_API_KEY"],
    http_options=types.HttpOptions(base_url="https://lovelyrouter.com"),
)
response = client.models.generate_content(model="gemini-3.1-pro", contents="Hello!")
```

```js title="Node.js" aside
import { GoogleGenAI } from '@google/genai';

const ai = new GoogleGenAI({
  apiKey: process.env.LOVELY_API_KEY,
  httpOptions: { baseUrl: 'https://lovelyrouter.com' },
});
const response = await ai.models.generateContent({ model: 'gemini-3.1-pro', contents: 'Hello!' });
```

:::aside
```json title="Response"
{
  "candidates": [
    {
      "content": {"role": "model", "parts": [{"text": "Hello! How can I help?"}]},
      "finishReason": "STOP",
      "index": 0
    }
  ],
  "usageMetadata": {
    "promptTokenCount": 3,
    "candidatesTokenCount": 7,
    "thoughtsTokenCount": 96,
    "totalTokenCount": 106
  },
  "responseId": "mFHoaNC1Lo3HqtsP"
}
```
:::

## Stream generated content

::endpoint POST /v1beta/models/{model}:streamGenerateContent

Same request as `generateContent`, returned as a stream. Add `alt=sse` to receive server-sent events, one partial response per event; the SDKs do this for you. Without it, the response is a single streamed JSON array. See [Streaming](/docs/models/streaming).

:::params Query parameters
- `alt` string
  Set to `sse` for server-sent events. Recommended.
:::

The path parameter, headers and body are the same as for [Generate content](#generate-content).

```bash title="cURL" aside
curl -N "https://lovelyrouter.com/v1beta/models/gemini-3-flash:streamGenerateContent?alt=sse" \
  -H "x-goog-api-key: $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"contents": [{"role": "user", "parts": [{"text": "Count to three."}]}]}'
```

```python title="Python" aside
for chunk in client.models.generate_content_stream(
    model="gemini-3-flash", contents="Count to three."
):
    print(chunk.text or "", end="")
```

```js title="Node.js" aside
const stream = await ai.models.generateContentStream({
  model: 'gemini-3-flash',
  contents: 'Count to three.',
});
for await (const chunk of stream) process.stdout.write(chunk.text ?? '');
```

:::aside
```sse title="Response"
data: {"candidates":[{"content":{"role":"model","parts":[{"text":"One, two,"}]}}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":4,"totalTokenCount":9}}

data: {"candidates":[{"content":{"role":"model","parts":[{"text":" three."}]},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":7,"totalTokenCount":12}}
```
:::

## Count Gemini tokens

::endpoint POST /v1beta/models/{model}:countTokens

Estimates the tokens in `contents` without running the model. Free, and available with a zero balance. The count is approximate. [Gemini reference](https://ai.google.dev/api/tokens).

:::params Body parameters
- `contents` array, required
  The content to count, in the same shape as for `generateContent`.
:::

The path parameter and headers are the same as for [Generate content](#generate-content).

```bash title="cURL" aside
curl "https://lovelyrouter.com/v1beta/models/gemini-3.1-pro:countTokens" \
  -H "x-goog-api-key: $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"contents": [{"role": "user", "parts": [{"text": "Hello, Gemini"}]}]}'
```

```python title="Python" aside
result = client.models.count_tokens(model="gemini-3.1-pro", contents="Hello, Gemini")
print(result.total_tokens)
```

```js title="Node.js" aside
const result = await ai.models.countTokens({ model: 'gemini-3.1-pro', contents: 'Hello, Gemini' });
console.log(result.totalTokens);
```

:::aside
```json title="Response"
{
  "totalTokens": 18
}
```
:::

## List models

::endpoint GET /v1/models

Lists every model you can call now, with context size, list prices and the channel groups that serve it. No API key is needed. The shape works with `models.list()` in both the OpenAI and Anthropic SDKs. Field meanings are in [Models & pricing](/docs/models/pricing#list-models).

:::params Response fields
- `data` array
  One object per model.
  - `id` string
    Model id to send in requests.
  - `display_name` string
    Human-readable name.
  - `owned_by` string
    Vendor: `Anthropic`, `OpenAI` or `Google`.
  - `context_length` integer
    Context window in tokens.
  - `pricing` object
    `input`, `output`, `cache_read` and `cache_write` in `USD per 1M tokens`. A `null` cache price means that kind of token is billed at the input price.
  - `groups` array
    Channel groups that serve the model.
- `has_more` boolean
  Always `false`: the whole list is returned at once.
:::

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/models
```

```python title="Python" aside
from openai import OpenAI

client = OpenAI(base_url="https://lovelyrouter.com/v1", api_key="unused")
for model in client.models.list():
    print(model.id)
```

```js title="Node.js" aside
const res = await fetch('https://lovelyrouter.com/v1/models');
const { data } = await res.json();
console.log(data.map((m) => m.id));
```

:::aside
```json title="Response"
{
  "object": "list",
  "data": [
    {
      "id": "claude-sonnet-5.5",
      "object": "model",
      "type": "model",
      "display_name": "Claude Sonnet 5.5",
      "created": 1700000000,
      "created_at": "2025-01-01T00:00:00Z",
      "owned_by": "Anthropic",
      "context_length": 1000000,
      "pricing": {
        "input": 2,
        "output": 10,
        "cache_read": 0.1,
        "cache_write": 2.5,
        "unit": "USD per 1M tokens"
      },
      "groups": ["claude-value"]
    }
  ],
  "has_more": false,
  "first_id": "claude-fable-5.1",
  "last_id": "gpt-5.6-luna"
}
```
:::

## List models in Gemini format

::endpoint GET /v1beta/models

Lists the models available through the Gemini endpoints, in the Gemini API's shape. No API key is needed. The Google Gen AI SDKs call it from `models.list()`. Prices are only in `GET /v1/models`.

```bash title="cURL" aside
curl https://lovelyrouter.com/v1beta/models
```

:::aside
```json title="Response"
{
  "models": [
    {
      "name": "models/gemini-3.1-pro",
      "displayName": "Gemini 3.1 Pro",
      "inputTokenLimit": 1048576,
      "supportedGenerationMethods": ["generateContent", "streamGenerateContent", "countTokens"]
    }
  ]
}
```
:::
