# Chat Completions API

> Call POST /v1/chat/completions on OpenCheese: curl, Python and JavaScript examples for plain requests, streaming, tool calls, image input and reasoning effort.
>
> Web page: https://opencheese.dev/en/docs/chat-completions/ · Every docs page: https://opencheese.dev/llms.txt

`POST https://api.opencheese.dev/v1/chat/completions` is the OpenAI Chat Completions API. The
OpenAI SDKs, OpenCode, Zed, Harness AI and most chat apps use it.

## A first request

```bash
curl https://api.opencheese.dev/v1/chat/completions \
  -H "Authorization: Bearer $OPENCHEESE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_ID", "messages": [{"role": "system", "content": "Answer in one sentence."}, {"role": "user", "content": "What is a gateway?"}]}'
```

`model` and `messages` are required. Take the model ID from [`GET /v1/models`](https://opencheese.dev/en/docs/models.md).

The answer has the usual shape:

```json
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "model": "MODEL_ID",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "A gateway passes your requests on to the models behind it." },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 24, "completion_tokens": 13, "total_tokens": 37 }
}
```

## Parameters

| Field | Notes |
| --- | --- |
| `model` | Required. An `id` from `/v1/models`. |
| `messages` | Required. `system`, `user`, `assistant` and `tool` messages. |
| `stream` | `true` for server-sent events. |
| `tools`, `tool_choice` | Function calling, where the model supports tools. |
| `reasoning_effort` | A level from the model's `supported_reasoning_levels`. |
| `response_format` | Structured output, where the model supports it. |
| `temperature`, `top_p`, `max_tokens` | Optional. If you don't set them, the model's defaults are used. |

## Streaming

```bash
curl -N https://api.opencheese.dev/v1/chat/completions \
  -H "Authorization: Bearer $OPENCHEESE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_ID", "stream": true, "messages": [{"role": "user", "content": "Count to three."}]}'
```

Each event is a `chat.completion.chunk`; the text arrives in `choices[0].delta.content`. Near
the end come the `finish_reason` and the `usage`, and the stream ends with `data: [DONE]`:

```text
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"One"},"finish_reason":null}]}

data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":", two, three."},"finish_reason":null}]}

data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":6,"total_tokens":18}}

data: [DONE]
```

## Tool calls

```bash
curl https://api.opencheese.dev/v1/chat/completions \
  -H "Authorization: Bearer $OPENCHEESE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_ID", "messages": [{"role": "user", "content": "What is the weather in Paris?"}], "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Current weather for a city", "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}}]}'
```

When the model calls a tool, the reply's `finish_reason` is `tool_calls` and
`message.tool_calls` holds the call. Run the tool, then send the history back with a `tool`
message carrying the result:

```json
{
  "model": "MODEL_ID",
  "messages": [
    { "role": "user", "content": "What is the weather in Paris?" },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [{ "id": "call_1", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" } }]
    },
    { "role": "tool", "tool_call_id": "call_1", "content": "18 degrees, sunny" }
  ]
}
```

A reply cut off by the output limit in the middle of a tool call ends with
`finish_reason: "length"`: treat its tool call as incomplete and do not run it.

## Images in the prompt

Models whose `modalities.input` includes `image` read pictures sent as `image_url` parts, either
an `https://` address or a `data:` URL:

```json
{
  "model": "MODEL_ID",
  "messages": [
    {
      "role": "user",
      "content": [
        { "type": "text", "text": "What is in this picture?" },
        { "type": "image_url", "image_url": { "url": "data:image/png;base64,iVBORw0KGgo..." } }
      ]
    }
  ]
}
```

## Reasoning effort

Set `reasoning_effort` to one of the levels the model lists in `supported_reasoning_levels`
(`reasoning.effort` and Anthropic's `output_config.effort` are read too). If you don't set
one, the model's default is used.

```bash
curl https://api.opencheese.dev/v1/chat/completions \
  -H "Authorization: Bearer $OPENCHEESE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_ID", "reasoning_effort": "high", "messages": [{"role": "user", "content": "Plan a three-day trip to Lyon."}]}'
```

A level the model doesn't offer is refused with `unsupported_reasoning_effort`, and the error
lists the levels it does offer. Reasoning text, where the model returns it, arrives in
`message.reasoning_content` (`delta.reasoning_content` when streaming).

## Image models in a chat

Choose an image model (its `modalities.output` is `image`) and the latest user message becomes
the image prompt. The picture arrives in `message.images` as a `data:` URL, and an image sent
in the conversation is edited where the model can edit. Image models answer one picture per
request; for every image parameter use the [Image API](https://opencheese.dev/en/docs/images.md).

## Errors that arrive as a reply

Problems you need to act on, such as an invalid key, an exhausted plan or an empty balance,
come back as the assistant's message in a `200` response streamed as `text/event-stream`, even
when `stream` is `false`, so chat apps show them to you. Every other error is a normal HTTP
status with a JSON body. Check the response's `Content-Type` before parsing it as JSON; see
[Errors and rate limits](https://opencheese.dev/en/docs/errors.md).

## SDKs

Python:

```python
from openai import OpenAI

client = OpenAI(base_url="https://api.opencheese.dev/v1", api_key="YOUR_OPENCHEESE_API_KEY")
stream = client.chat.completions.create(
    model="MODEL_ID",
    messages=[{"role": "user", "content": "Write a haiku about cheese."}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")
```

JavaScript:

```javascript
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.opencheese.dev/v1", apiKey: process.env.OPENCHEESE_API_KEY });
const reply = await client.chat.completions.create({
  model: "MODEL_ID",
  messages: [{ role: "user", content: "Write a haiku about cheese." }]
});
console.log(reply.choices[0].message.content);
```

To keep a conversation's requests together in the dashboard, add the
[`opencheese-session-uuid`](https://opencheese.dev/en/docs/sessions.md) header.
