Lovely RouterDocs
Get a key
Models API

OpenAI Chat Completions

Call GPT, Claude and Gemini models through the OpenAI Chat Completions API with the official OpenAI SDKs. Covers streaming with usage, tools, reasoning models and what is not supported.

Lovely Router serves POST /v1/chat/completions as OpenAI defines it. Point the OpenAI SDK at https://lovelyrouter.com/v1 and use your Lovely Router key. The request body passes through unchanged, so OpenAI's API reference applies to every parameter.

Set up the SDK#

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": "system", "content": "Answer in one sentence."},
      {"role": "user", "content": "What does a load balancer do?"}
    ]
  }'

The SDKs also read OPENAI_BASE_URL and OPENAI_API_KEY, so most OpenAI-compatible apps only need those two settings.

Available models#

Every text model in GET /v1/models currently accepts Chat Completions, including Claude and Gemini models. Image models (gpt-image-*) use the Images API. That makes this API the simplest way to switch between vendors with one client.

FamilyExample idsNotes
GPTgpt-6.1-sol, gpt-6-astra, gpt-5.6-terra, gpt-5.6-lunaNative API.
Claudeclaude-sonnet-5.5, claude-opus-5.5Use Anthropic Messages for cache_control and thinking settings.
Geminigemini-3.1-pro, gemini-3-flashUse the Gemini API for thinkingConfig and other Gemini-only options.

Vendor-specific options are only certain to work in the model's native API. See Which API each model speaks.

Stream with usage#

Set stream: true. The gateway always adds stream_options.include_usage: true, so the last chunk before data: [DONE] has an empty choices array and the call's usage. Check that choices is non-empty before reading a delta.

stream = client.chat.completions.create(
    model="gpt-6.1-sol",
    messages=[{"role": "user", "content": "Name five prime numbers."}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
    if chunk.usage:
        print("\nTokens:", chunk.usage.prompt_tokens, "in,", chunk.usage.completion_tokens, "out")

See Streaming for the wire format and how errors appear mid-stream.

Call functions#

Describe functions in tools. When the model calls one, finish_reason is tool_calls; run the function and reply with a tool message that carries the same tool_call_id.

import json

tools = [{
    "type": "function",
    "function": {
        "name": "get_order_status",
        "description": "Look up an order by id.",
        "parameters": {
            "type": "object",
            "properties": {"order_id": {"type": "string"}},
            "required": ["order_id"],
        },
    },
}]
messages = [{"role": "user", "content": "Where is order A-1042?"}]

response = client.chat.completions.create(model="gpt-6.1-sol", messages=messages, tools=tools)
msg = response.choices[0].message
if msg.tool_calls:
    messages.append(msg)
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = {"order_id": args["order_id"], "status": "shipped"}  # your code here
        messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
    response = client.chat.completions.create(model="gpt-6.1-sol", messages=messages, tools=tools)
print(response.choices[0].message.content)

tool_choice, parallel_tool_calls and response_format pass through unchanged.

Use reasoning models#

GPT-5 and GPT-6 models reason before they answer. Control how much with reasoning_effort, and cap total output with max_completion_tokens, which includes reasoning tokens. Accepted effort values depend on the model; see OpenAI's reasoning guide.

response = client.chat.completions.create(
    model="gpt-6.1-sol",
    reasoning_effort="high",
    max_completion_tokens=8000,
    messages=[{"role": "user", "content": "Find the bug: for i in range(len(a)): a.pop(i)"}],
)
usage = response.usage
print(response.choices[0].message.content)
print("reasoning tokens:", usage.completion_tokens_details.reasoning_tokens)

The reasoning itself is not returned in Chat Completions. Its tokens are counted in usage.completion_tokens and billed at the output rate.

Use the Responses API#

GPT models are also served over the OpenAI Responses API at POST /v1/responses, the API that Codex CLI and the Agents SDK use. Use the same base URL and key; the SDK's client.responses.create() works as is.

curl https://lovelyrouter.com/v1/responses \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "gpt-6.1-sol",
    "instructions": "Answer in one sentence.",
    "input": "What is a router?"
  }'

With stream: true you get the API's typed events (response.created, response.output_text.delta, …, response.completed); usage arrives in response.completed and is billed like any other call. Things to know:

  • Send the whole conversation. Lovely Router doesn't store responses for you: GET, DELETE and cancel on /v1/responses/{id} return 404, and a previous_response_id only works while the conversation stays on the same channel. Send the full history in input with "store": false, as Codex does.
  • GPT models only. Other models return 404 with not served in responses format; call them with Chat Completions or their own API.
  • instructions in the response shows what you sent, or null if you sent none.
  • prompt_cache_key keeps a conversation on one channel, exactly as with Chat Completions.

Prompt caching#

OpenAI models cache long prompt prefixes automatically. Cached tokens show up in usage.prompt_tokens_details.cached_tokens and are billed at the cheaper cache-read rate. Setting prompt_cache_key improves hit rates and also tells the gateway which requests belong to one conversation. See Prompt caching.

What is not supported#

Chat Completions, the Responses API and the Images API are available in OpenAI format. These endpoints return 404:

  • Stored responses (GET, DELETE or cancel on /v1/responses/{id}).
  • Embeddings (/v1/embeddings).
  • Audio (/v1/audio/*) and image variations (/v1/images/variations).
  • Legacy Completions, Assistants, files, batches, fine-tuning and moderation.

GET /v1/models works with the OpenAI SDK's client.models.list(). Retrieving a single model by id does not.

Headers#

HeaderNotes
Authorization: BearerYour Lovely Router key. Required.
openai-betaForwarded as sent.
x-lovely-groupOptional. Pins the request to a channel group when your key is not bound to one.
x-session-idOptional. Groups requests into one conversation for cache-aware routing. prompt_cache_key or user in the body does the same.

Errors use OpenAI's shape, {"error": {"message": "...", "type": "...", "code": "..."}}; see Errors.