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

# Chat completions

> POST /v1/chat/completions: request fields, messages, tool calling, structured output and the response.

`POST https://api.fortytwo.network/v1/chat/completions` generates a response to a conversation. Authentication is described on [Get access](/docs/access).

## Send a request

<CodeGroup>
  ```bash cURL theme={null}
  MODEL_ID="qwen/qwen3.8-27b"

  curl https://api.fortytwo.network/v1/chat/completions \
    -H "Authorization: Bearer $FORTYTWO_API_KEY" \
    -H "Content-Type: application/json" \
    -d @- <<EOF
  {
    "model": "$MODEL_ID",
    "messages": [
      { "role": "system", "content": "Answer in one sentence." },
      { "role": "user", "content": "Why is the sea salty?" }
    ],
    "reasoning_effort": "medium"
  }
  EOF
  ```

  ```python Python theme={null}
  import os
  from openai import OpenAI

  client = OpenAI(
      api_key=os.environ["FORTYTWO_API_KEY"],
      base_url="https://api.fortytwo.network/v1",
  )

  model_id = "qwen/qwen3.8-27b"

  completion = client.chat.completions.create(
      model=model_id,
      messages=[
          {"role": "system", "content": "Answer in one sentence."},
          {"role": "user", "content": "Why is the sea salty?"},
      ],
      reasoning_effort="medium",
  )

  print(completion.choices[0].message.content)
  print(completion.usage)
  ```

  ```typescript TypeScript theme={null}
  import OpenAI from "openai";

  const client = new OpenAI({
    apiKey: process.env.FORTYTWO_API_KEY,
    baseURL: "https://api.fortytwo.network/v1",
  });

  const modelId = "qwen/qwen3.8-27b";

  const completion = await client.chat.completions.create({
    model: modelId,
    messages: [
      { role: "system", content: "Answer in one sentence." },
      { role: "user", content: "Why is the sea salty?" },
    ],
    reasoning_effort: "medium",
  });

  console.log(completion.choices[0].message.content);
  console.log(completion.usage);
  ```
</CodeGroup>

## Request fields

<ParamField body="model" type="string" required>
  A model ID from [Models](/docs/models) or `GET /v1/models`. An unknown ID returns `404 model_not_found`.
</ParamField>

<ParamField body="messages" type="array" required>
  The conversation, in order. Each entry has a `role` and `content`, and optionally a `name` (1–64 letters, digits, `_` or `-`) — see [Message roles](#message-roles). An `assistant` entry can also carry `tool_calls`, and a `tool` entry carries `tool_call_id`. Other fields return `400 invalid_request`.
</ParamField>

<ParamField body="max_tokens" type="integer">
  The maximum number of tokens to generate. From 1 to the model's max output; the default is the model's max output. `max_completion_tokens` is accepted as an alternative name. Send only one of the two.
</ParamField>

<ParamField body="reasoning_effort" type="string" default="medium">
  How much the model reasons before it answers: `none`, `minimal`, `low`, `medium`, `high`, `xhigh` or `max`. `none` turns reasoning off.
</ParamField>

<ParamField body="stream" type="boolean" default="false">
  `true` returns the response as server-sent events — see [Streaming](/docs/streaming).
</ParamField>

<ParamField body="stream_options.include_usage" type="boolean">
  With `stream: true`, adds a final chunk with `usage` — see [Streaming](/docs/streaming#usage-on-the-final-chunk).
</ParamField>

<ParamField body="n" type="integer" default="1">
  Only `1` is accepted.
</ParamField>

<ParamField body="store" type="boolean" default="false">
  Only `false` is accepted. `true` returns `400 zdr_storage_not_supported` — see [Data handling](/docs/data-handling).
</ParamField>

The fields for [tool calling](#tool-calling) and [structured output](#structured-output) are described below.

### Message roles

| Role | Carries |
| - | - |
| `developer`, `system` | Instructions for the model. |
| `user` | The input to respond to: text, and images where the model accepts them. |
| `assistant` | An earlier reply from the model, including any `tool_calls` it made. |
| `tool` | The result of a tool call, with the `tool_call_id` of the call it answers. |

<Warning>
  Do not send `reasoning`, `reasoning_details` or `refusal` back in an `assistant` message. The request fails with `400 invalid_request`. Remove these fields before you add a reply to the conversation.
</Warning>

### Message content

`content` is a string or an array of content parts. A `user` message can also carry `image_url` parts — see [Image input](/docs/images). Other part types, such as audio, video or file, return `400 unsupported_content_type`.

### Sampling controls

A sampling parameter works only with a model that supports it; [Models](/docs/models) lists them. `GET /v1/models` does not report them. A parameter that is off, a value outside the range below, or a parameter sent twice returns `400 invalid_request`. Values are never clamped. Leave a parameter out to use the model's default.

| Parameter | Accepted values |
| - | - |
| `temperature` | 0 to 2 |
| `top_p` | 0 to 1 |
| `top_k` | an integer, 1 to 2,147,483,647 |
| `min_p` | 0 to 1 |
| `presence_penalty` | −2 to 2 |
| `repetition_penalty` | a finite number greater than 0 |
| `stop` | a string, a non-empty array of strings, or `null` |
| `seed` | an integer from −9,007,199,254,740,991 to 9,007,199,254,740,991 |

## Tool calling

Describe your functions in `tools`. The model returns the calls it wants to make. Your application runs them and sends the results back in `tool` messages. Fortytwo does not call tools and does not store tool definitions, arguments or results.

<ParamField body="tools" type="array">
  The functions the model may call, up to 128. Each entry is `{ "type": "function", "function": { ... } }`, with a `name` (1–64 letters, digits, `_` or `-`), an optional `description`, the `parameters` as a JSON Schema object, and an optional `strict` boolean.
</ParamField>

<ParamField body="tool_choice" type="string or object">
  `auto` lets the model decide, `required` makes it call at least one tool, and `none` stops it calling any. To force one function, send `{ "type": "function", "function": { "name": "<name>" } }`.
</ParamField>

<ParamField body="parallel_tool_calls" type="boolean">
  Allow more than one call in one message.
</ParamField>

<CodeGroup>
  ```bash cURL theme={null}
  # MODEL_ID as in "Send a request" above
  curl https://api.fortytwo.network/v1/chat/completions \
    -H "Authorization: Bearer $FORTYTWO_API_KEY" \
    -H "Content-Type: application/json" \
    -d @- <<EOF
  {
    "model": "$MODEL_ID",
    "messages": [
      { "role": "user", "content": "When is high tide in Dover today?" }
    ],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "get_high_tide",
          "description": "Look up the next high tide for a harbour.",
          "parameters": {
            "type": "object",
            "properties": { "harbour": { "type": "string" } },
            "required": ["harbour"]
          }
        }
      }
    ],
    "tool_choice": "auto"
  }
  EOF
  ```

  ```python Python theme={null}
  import json

  # client and model_id as in "Send a request" above
  completion = client.chat.completions.create(
      model=model_id,
      messages=[{"role": "user", "content": "When is high tide in Dover today?"}],
      tools=[
          {
              "type": "function",
              "function": {
                  "name": "get_high_tide",
                  "description": "Look up the next high tide for a harbour.",
                  "parameters": {
                      "type": "object",
                      "properties": {"harbour": {"type": "string"}},
                      "required": ["harbour"],
                  },
              },
          }
      ],
      tool_choice="auto",
  )

  for call in completion.choices[0].message.tool_calls or []:
      print(call.id, call.function.name, json.loads(call.function.arguments))
  ```

  ```typescript TypeScript theme={null}
  // client and modelId as in "Send a request" above
  const completion = await client.chat.completions.create({
    model: modelId,
    messages: [{ role: "user", content: "When is high tide in Dover today?" }],
    tools: [
      {
        type: "function",
        function: {
          name: "get_high_tide",
          description: "Look up the next high tide for a harbour.",
          parameters: {
            type: "object",
            properties: { harbour: { type: "string" } },
            required: ["harbour"],
          },
        },
      },
    ],
    tool_choice: "auto",
  });

  for (const call of completion.choices[0].message.tool_calls ?? []) {
    if (call.type === "function") {
      console.log(call.id, call.function.name, JSON.parse(call.function.arguments));
    }
  }
  ```
</CodeGroup>

### Send the results back

Each call in `choices[].message.tool_calls` has an `id`, a function `name` and `arguments` as a JSON string. Run the function. Then send the request again with the same `tools`, and add to `messages`:

1. The assistant message with the `tool_calls`.
2. One `tool` message per call, with the call's `id` as `tool_call_id` and the result as `content`.

```json theme={null}
{
  "messages": [
    { "role": "user", "content": "When is high tide in Dover today?" },
    {
      "role": "assistant",
      "tool_calls": [
        {
          "id": "<id of the call>",
          "type": "function",
          "function": { "name": "get_high_tide", "arguments": "{\"harbour\": \"Dover\"}" }
        }
      ]
    },
    { "role": "tool", "tool_call_id": "<id of the call>", "content": "{\"high_tide\": \"14:32\"}" }
  ]
}
```

In a stream, `arguments` arrive in pieces in `choices[].delta.tool_calls`. Join the pieces of each call by its `index`, and parse them when the stream ends.

## Structured output

Set `response_format` to get JSON in `choices[].message.content`.

<ParamField body="response_format" type="object">
  `{ "type": "text" }` is the default. `{ "type": "json_object" }` returns a JSON object. `{ "type": "json_schema", "json_schema": { "name": "...", "strict": true, "schema": { ... } } }` returns JSON that matches your schema. `strict` must be `true`.
</ParamField>

<CodeGroup>
  ```bash cURL theme={null}
  # MODEL_ID as in "Send a request" above
  curl https://api.fortytwo.network/v1/chat/completions \
    -H "Authorization: Bearer $FORTYTWO_API_KEY" \
    -H "Content-Type: application/json" \
    -d @- <<EOF
  {
    "model": "$MODEL_ID",
    "messages": [
      { "role": "user", "content": "Why is the sea salty? Name the main sources of the salt." }
    ],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "salt_sources",
        "strict": true,
        "schema": {
          "type": "object",
          "properties": {
            "summary": { "type": "string" },
            "sources": { "type": "array", "items": { "type": "string" } }
          },
          "required": ["summary", "sources"],
          "additionalProperties": false
        }
      }
    }
  }
  EOF
  ```

  ```python Python theme={null}
  import json

  # client and model_id as in "Send a request" above
  completion = client.chat.completions.create(
      model=model_id,
      messages=[
          {"role": "user", "content": "Why is the sea salty? Name the main sources of the salt."},
      ],
      response_format={
          "type": "json_schema",
          "json_schema": {
              "name": "salt_sources",
              "strict": True,
              "schema": {
                  "type": "object",
                  "properties": {
                      "summary": {"type": "string"},
                      "sources": {"type": "array", "items": {"type": "string"}},
                  },
                  "required": ["summary", "sources"],
                  "additionalProperties": False,
              },
          },
      },
  )

  print(json.loads(completion.choices[0].message.content))
  ```

  ```typescript TypeScript theme={null}
  // client and modelId as in "Send a request" above
  const completion = await client.chat.completions.create({
    model: modelId,
    messages: [
      { role: "user", content: "Why is the sea salty? Name the main sources of the salt." },
    ],
    response_format: {
      type: "json_schema",
      json_schema: {
        name: "salt_sources",
        strict: true,
        schema: {
          type: "object",
          properties: {
            summary: { type: "string" },
            sources: { type: "array", items: { type: "string" } },
          },
          required: ["summary", "sources"],
          additionalProperties: false,
        },
      },
    },
  });

  console.log(JSON.parse(completion.choices[0].message.content ?? "{}"));
  ```
</CodeGroup>

## Not supported

* Fields not described on this page or on [Streaming](/docs/streaming) return `400 invalid_request`. The full list is on [OpenAI compatibility](/docs/openai-compatibility#request-fields). Check that your client library does not add them.
* Endpoints other than `GET /v1/models` and `POST /v1/chat/completions` — see [API conventions](/apis/api-reference-intro).

## Response

<ResponseField name="id" type="string">
  An opaque identifier for this completion.
</ResponseField>

<ResponseField name="object" type="string">
  `chat.completion` for a single response. A streamed response carries `chat.completion.chunk` instead.
</ResponseField>

<ResponseField name="created" type="integer">
  When the completion was created, as a Unix timestamp.
</ResponseField>

<ResponseField name="model" type="string">
  The model ID from the request.
</ResponseField>

<ResponseField name="choices" type="array">
  The generated results. Each entry carries `index`, `message` and `finish_reason`.
</ResponseField>

<ResponseField name="choices[].message" type="object">
  The assistant message, with its `role`, its `content` and, when the model calls tools, `tool_calls`.
</ResponseField>

<ResponseField name="choices[].message.tool_calls" type="array">
  Present when the model calls tools. Each entry has an `id`, `type: "function"`, and a `function` with `name` and `arguments` (a JSON string) — see [Tool calling](#tool-calling).
</ResponseField>

<ResponseField name="choices[].message.reasoning" type="string">
  The reasoning before the answer, when the model reasons. Its tokens are counted in `usage.completion_tokens` and billed as output. Do not send it back in later requests.
</ResponseField>

<ResponseField name="choices[].message.reasoning_details" type="array">
  Structured reasoning entries, when the model reasons. Their shape can change. Do not send them back in later requests.
</ResponseField>

<ResponseField name="usage" type="object">
  `prompt_tokens`, `completion_tokens` and `total_tokens`. Always present on a non-streaming response — see [Streaming](/docs/streaming#usage-on-the-final-chunk) for streams.
</ResponseField>

<ResponseField name="usage.prompt_tokens_details.cached_tokens" type="integer">
  Prompt tokens served from the prompt cache.
</ResponseField>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.