> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat completions

Call the node's model with the OpenAI Chat Completions API, including tool calls, JSON Schema output, streaming and opting into the B3IQ gateway.

`POST /v1/infer/chat/completions` is the OpenAI Chat Completions API. Set `B3OS_MODEL` to a chat model's ID from `GET /v1/infer/models` first ([how](/compat/openai)).

## A completion

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/infer/chat/completions \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d "{\"model\": \"$B3OS_MODEL\", \"messages\": [{\"role\": \"user\", \"content\": \"Name one river in Oregon.\"}]}"
    ```
  **TypeScript**

```ts
    import OpenAI from "openai";

    const client = new OpenAI({ apiKey: process.env.B3OS_API_KEY, baseURL: "https://beta-api.b3os.org/v1/infer" });
    const completion = await client.chat.completions.create({
      model: process.env.B3OS_MODEL!,
      messages: [{ role: "user", content: "Name one river in Oregon." }],
    });
    console.log(completion.choices[0].message.content, completion.usage);
    ```

    The B3OS SDK has the same call as `b3os.infer.createChatCompletion(...)`.
  **Python**

```python
    import os
    from openai import OpenAI

    client = OpenAI(api_key=os.environ["B3OS_API_KEY"], base_url="https://beta-api.b3os.org/v1/infer")
    completion = client.chat.completions.create(
        model=os.environ["B3OS_MODEL"],
        messages=[{"role": "user", "content": "Name one river in Oregon."}],
    )
    print(completion.choices[0].message.content, completion.usage)
    ```
  **MCP**

```text
    Use the B3OS model to draft three subject lines for this release note: …
    ```

    The remote MCP server's `infer_chat` tool sends a chat completion to the node's model, with the same fields as the API body. The local server has no infer tool; there, publish a routine with an `infer/chat@1` step and have the agent run it with `routine_run`.

## Tool calls

Pass `tools` (up to 128) and the model answers with `tool_calls` when it wants one; send the result back as a `tool` message:

```json
{
  "model": "b3os/your-chat-model",
  "messages": [
    { "role": "system", "content": "You are a concise assistant." },
    { "role": "user", "content": "What is the weather in Eugene?" }
  ],
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Current weather for a city.",
      "parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] }
    }
  }]
}
```

`tool_choice` takes `none`, `auto`, `required` or a named function, and `parallel_tool_calls` is accepted. The [sandbox agent quickstart](/quickstart/sandbox-agent) runs a full tool loop against the node.

## JSON Schema output

`response_format` with `type: "json_schema"` constrains the answer to your schema, on models whose `b3os.json_schema` is true:

```json
{
  "model": "b3os/your-chat-model",
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "city",
      "schema": { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"] }
    }
  },
  "messages": [{ "role": "user", "content": "Name a city in Oregon as JSON." }]
}
```

## Streaming

With `"stream": true` the response is server-sent `chat.completion.chunk` objects ending with `data: [DONE]`, exactly as OpenAI sends them. `"stream_options": {"include_usage": true}` adds a final chunk with the token counts. A stream may run up to 10 minutes.

## Images

On models whose `b3os.image_input` is true, user messages can carry images, as in the OpenAI API.

## Beyond the node's models

A model ID the node does not serve fails with [`model_not_available`](/errors#model_not_available). Add `"route": "gateway"` to send the request to the B3IQ gateway instead. `b3os.route` in the response and `infer_route` in `Server-Timing` say which way it went.

## When it fails

Errors use OpenAI's shape, with the B3OS code in `error.code`:

```json
{ "error": { "message": "n must be 1 on this model", "type": "invalid_request_error", "param": "n", "code": "unsupported_parameter" } }
```

| Code | Status | Meaning |
|---|---|---|
| [`model_not_available`](/errors#model_not_available) | 404 | The node does not serve this model, and the request did not ask for the gateway |
| [`context_length_exceeded`](/errors#context_length_exceeded) | 400 | The prompt and `max_tokens` do not fit the model's context |
| [`unsupported_parameter`](/errors#unsupported_parameter) | 400 | The model's runtime does not support a parameter you sent; `param` names it |
| [`infer_backend_unavailable`](/errors#infer_backend_unavailable) | 503 | The model is restarting or failing; retry after `Retry-After` |
| [`rate_limited`](/errors#rate_limited) | 429 | Over your tokens or requests per minute for the model |