---
url: https://docs.freetheai.org/api/chat-completions.md
description: >-
  POST /v1/chat/completions - the OpenAI Chat Completions format on FreeTheAI.
  Every parameter, request and response examples, streaming, tools, and errors.
---

# Chat Completions

Creates a reply to a conversation, in the OpenAI Chat Completions format. This is the endpoint most apps and SDKs use.

`POST https://api.freetheai.org/v1/chat/completions`

## Request

::: code-group

```bash [curl]
curl https://api.freetheai.org/v1/chat/completions \
  -H "Authorization: Bearer $FTAI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "fta/zai/glm-5.3",
    "messages": [
      {"role": "system", "content": "You are a concise assistant."},
      {"role": "user", "content": "Name three uses for a paperclip."}
    ],
    "max_tokens": 300,
    "temperature": 0.7
  }'
```

```python [Python]
import os
from openai import OpenAI

client = OpenAI(base_url="https://api.freetheai.org/v1", api_key=os.environ["FTAI_KEY"])

reply = client.chat.completions.create(
    model="fta/zai/glm-5.3",
    messages=[
        {"role": "system", "content": "You are a concise assistant."},
        {"role": "user", "content": "Name three uses for a paperclip."},
    ],
    max_tokens=300,
    temperature=0.7,
)
print(reply.choices[0].message.content)
```

```ts [TypeScript]
import OpenAI from 'openai'

const client = new OpenAI({ baseURL: 'https://api.freetheai.org/v1', apiKey: process.env.FTAI_KEY })

const reply = await client.chat.completions.create({
  model: 'fta/zai/glm-5.3',
  messages: [
    { role: 'system', content: 'You are a concise assistant.' },
    { role: 'user', content: 'Name three uses for a paperclip.' },
  ],
  max_tokens: 300,
  temperature: 0.7,
})
console.log(reply.choices[0].message.content)
```

```js [fetch]
const response = await fetch('https://api.freetheai.org/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.FTAI_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'fta/zai/glm-5.3',
    messages: [
      { role: 'system', content: 'You are a concise assistant.' },
      { role: 'user', content: 'Name three uses for a paperclip.' },
    ],
    max_tokens: 300,
  }),
})
const data = await response.json()
if (!response.ok) throw new Error(data.error?.message ?? response.statusText)
console.log(data.choices[0].message.content)
```

:::

## Body parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `model` | string, **required** | A model ID from [`GET /v1/models`](/api/models), such as `fta/zai/glm-5.3`. |
| `messages` | array, **required** | The conversation so far. Each message has a `role` (`system`, `developer`, `user`, `assistant`, or `tool`) and `content`. See [Messages](#messages). |
| `stream` | boolean | Return server-sent events while the reply is written. Default `false`. See [Streaming](/concepts/streaming). |
| `stream_options` | object | `{"include_usage": true}` as in the OpenAI API. Usage is in the final chunk either way. |
| `max_tokens` | integer | The most tokens to generate, when the model supports it. `max_completion_tokens` is accepted too. Send `0` to ask for the model's full output limit. |
| `temperature` | number | Sampling temperature. Some models use fixed values and ignore it. |
| `top_p` | number | Nucleus sampling. Some models use fixed values and ignore it. |
| `frequency_penalty`, `presence_penalty` | number | Repetition penalties, where the model supports them. |
| `stop` | string or array | Text that ends the reply. |
| `seed` | integer | Best-effort repeatable sampling, where supported. |
| `tools` | array | Functions the model may call. See [Tool calls](/concepts/tool-calls). |
| `tool_choice` | string or object | `auto`, `none`, `required`, or a specific function. |
| `parallel_tool_calls` | boolean | Allow several tool calls in one turn, where supported. |
| `response_format` | object | `{"type": "json_object"}` or a JSON schema, where the model supports it. |
| `thinking` | object | `{"type": "disabled"}` turns a thinking model's reasoning off. See [Thinking](/concepts/reasoning). |
| `reasoning_effort` | string | `none` turns reasoning off; other values follow the OpenAI meaning where the model supports them. |
| `web_search_options` | object | Send `{}` to let `fta/dog/` and `fta/lion/` models search the web first. Sources come back as `url_citation` annotations. See [Web search](/concepts/web-search). |

Other OpenAI-compatible fields are passed to the model when it supports them. A value the model refuses answers `400` with `the model rejected one of the request's settings`.

### Messages {#messages}

```json
[
  { "role": "system", "content": "You are a helpful assistant." },
  { "role": "user", "content": "Hi!" },
  { "role": "assistant", "content": "Hello! How can I help?" },
  { "role": "user", "content": [
      { "type": "text", "text": "What is in this picture?" },
      { "type": "image_url", "image_url": { "url": "data:image/png;base64,..." } }
  ] }
]
```

* `content` is a string, or an array of `text` and `image_url` parts for models that read [images](/concepts/images).
* Include at least one `user` message. A request with only system or character messages answers `400` with `the model needs at least one message from you in the request`.
* Assistant messages with `tool_calls` and `tool` messages with a `tool_call_id` carry [tool calls](/concepts/tool-calls).

## Response

```json
{
  "id": "chatcmpl-8f3a...",
  "object": "chat.completion",
  "created": 1793145600,
  "model": "fta/zai/glm-5.3",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "1. Holding papers together\n2. Resetting small devices\n3. A makeshift hook"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 27,
    "completion_tokens": 21,
    "total_tokens": 48
  }
}
```

| Field | Description |
| --- | --- |
| `id` | The completion ID. |
| `object` | `chat.completion`, or `chat.completion.chunk` for stream chunks. |
| `model` | The model that answered. |
| `choices[].message.content` | The reply text. |
| `choices[].message.reasoning_content` | The model's reasoning, for models that think. Not part of the answer. |
| `choices[].message.tool_calls` | Function calls the model asks for. |
| `choices[].message.annotations` | `url_citation` entries for the web sources a search used. |
| `choices[].finish_reason` | `stop`, `length` (hit `max_tokens`), `tool_calls`, or `content_filter`. |
| `usage` | Token counts: `prompt_tokens`, `completion_tokens`, `total_tokens`, and details where the model reports them. |

With `"stream": true` the response is a stream of `chat.completion.chunk` events ending with `data: [DONE]`; see [Streaming](/concepts/streaming).

## Errors

| Status | Common causes |
| --- | --- |
| `400` | Invalid JSON, missing `model`, images sent to a text-only model, a refused setting, no user message, a chat longer than the model's context, or the model's content filter. |
| `401` | Missing, incomplete, or revoked key. |
| `403` | No check-in today, email not confirmed, the key's model or IP list, or an account hold. |
| `404` | Unknown model ID. |
| `413` | Body over 8 MB. |
| `429` | A reply is still running, or a daily or window limit was reached. |
| `502` | The model failed this time, or the stream broke. |
| `503` | The model is busy or paused. |

Every message and its fix: [Errors](/errors).
