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

# Create chat completion

> POST /v1/chat/completions. The OpenAI chat completions body, plus task.

Creates a model response for a conversation. This is the only inference endpoint:
every workload goes through it, distinguished by its scope.

## Protégé parameters

<ParamField body="task" type="string">
  Alias for the workload name, for clients where a body field is easier than a
  header. Must match `^[a-z0-9_-]{1,63}$`.

  `x-protege-workload` wins when both are present. Omitting both attributes the
  call to the `main` workload of the `default` project. See
  [Projects and workloads](/concepts/projects-and-workloads).
</ParamField>

<ParamField header="x-protege-project" type="string" default="default">
  Project slug, `^[a-z0-9-]{1,63}$`. Provisioned on first use.
</ParamField>

<ParamField header="x-protege-workload" type="string" default="main">
  Workload name, `^[a-z0-9_-]{1,63}$`. Provisioned on first use.
</ParamField>

<ParamField body="model" type="string" required>
  A model id from the catalog, for example `deepseek-v4-flash`. The
  provider-pinned form (`alibaba/qwen-flash`) is accepted and normalises to the
  same canonical id, which is what comes back in the response.
</ParamField>

## Standard parameters

<ParamField body="messages" type="array" required>
  The conversation so far. Each message has a `role` of `system`, `user`,
  `assistant` or `tool`, and `content`.
</ParamField>

<ParamField body="stream" type="boolean" default="false">
  Stream partial deltas as server-sent events. See [Streaming](#streaming).
</ParamField>

<ParamField body="max_tokens" type="integer">
  Upper bound on generated tokens. Output bills at several times input on most
  models, so this is the highest-leverage cost control you have before any
  routing decision.
</ParamField>

<ParamField body="temperature" type="number" default="1">
  Sampling temperature between 0 and 2. Lower is more deterministic.
</ParamField>

<ParamField body="top_p" type="number" default="1">
  Nucleus sampling. Set this or `temperature`, not both.
</ParamField>

<ParamField body="stop" type="string | array">
  Up to four sequences that halt generation.
</ParamField>

<ParamField body="response_format" type="object">
  Set `{"type": "json_object"}` to constrain output to valid JSON. Schema-shaped
  output is usually cheaper and more reliably scored than free text, which is why
  it often clears an eval on a smaller model.
</ParamField>

<ParamField body="tools" type="array">
  Tool definitions the model may call, in OpenAI function-calling format.
</ParamField>

<ParamField body="tool_choice" type="string | object">
  `auto`, `none`, `required`, or a specific tool.
</ParamField>

<ParamField body="seed" type="integer">
  Best-effort determinism for repeated identical requests.
</ParamField>

<ParamField body="user" type="string">
  Stable end-user or tenant identifier. Use this to separate tenants within one
  task rather than encoding the tenant into the task name.
</ParamField>

<ParamField body="metadata" type="object">
  Up to 16 string key-value pairs echoed back on the response. Useful for
  carrying your own request or trace IDs.
</ParamField>

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.protege.sh/v1/chat/completions \
    -H "Authorization: Bearer $PROTEGE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "task": "invoice_extraction",
      "model": "deepseek-v4-flash",
      "messages": [
        {"role": "system", "content": "Return only the total, as a number."},
        {"role": "user", "content": "INVOICE #4412 ... TOTAL 1,284.00 USD"}
      ],
      "max_tokens": 16,
      "response_format": {"type": "json_object"}
    }'
  ```

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

  client = OpenAI(
      base_url="https://api.protege.sh/v1",
      api_key=os.environ["PROTEGE_API_KEY"],
  )

  resp = client.chat.completions.create(
      model="deepseek-v4-flash",
      messages=[
          {"role": "system", "content": "Return only the total, as a number."},
          {"role": "user", "content": invoice_text},
      ],
      max_tokens=16,
      extra_body={"task": "invoice_extraction"},
  )
  ```

  ```typescript TypeScript theme={null}
  const resp = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages: [
      { role: "system", content: "Return only the total, as a number." },
      { role: "user", content: invoiceText },
    ],
    max_tokens: 16,
    // @ts-expect-error - `task` is a Protégé extension
    task: "invoice_extraction",
  });
  ```
</CodeGroup>

## Response

<ResponseField name="id" type="string">
  Unique identifier for the completion.
</ResponseField>

<ResponseField name="object" type="string">
  Always `chat.completion`.
</ResponseField>

<ResponseField name="model" type="string">
  The model that actually served the call. With `model: "deepseek-v4-flash"` this is the
  resolved route, not the string you sent.
</ResponseField>

<ResponseField name="choices" type="array">
  <Expandable title="properties">
    <ResponseField name="index" type="integer" />

    <ResponseField name="message" type="object">
      `role` and `content`, plus `tool_calls` when tools were used.
    </ResponseField>

    <ResponseField name="finish_reason" type="string">
      `stop`, `length`, `tool_calls` or `content_filter`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="usage" type="object">
  `prompt_tokens`, `completion_tokens` and `total_tokens`.
</ResponseField>

### Response headers

| Header                           | Meaning                                      |
| -------------------------------- | -------------------------------------------- |
| `x-protege-project`              | The project the call was attributed to       |
| `x-protege-workload`             | The workload it was attributed to            |
| `x-request-id`                   | Identifier to quote when reporting a problem |
| `x-ratelimit-limit-requests`     | Requests permitted in the window             |
| `x-ratelimit-remaining-requests` | Requests left                                |
| `x-ratelimit-reset-requests`     | Seconds until reset                          |

Log the first two. Seeing `default` and `main` when you expected otherwise means
the header was dropped or `task` was stripped by your SDK.

```json Response theme={null}
{
  "id": "chatcmpl-9f2a7c31",
  "object": "chat.completion",
  "created": 1786531200,
  "model": "deepseek-v4-flash",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "{\"total\": 1284.00}" },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 812, "completion_tokens": 11, "total_tokens": 823 }
}
```

## Streaming

Set `stream: true` to receive `chat.completion.chunk` events as server-sent
events, terminated by `data: [DONE]`.

```bash theme={null}
curl https://api.protege.sh/v1/chat/completions \
  -H "Authorization: Bearer $PROTEGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task": "support_draft_reply",
    "model": "deepseek-v4-flash",
    "stream": true,
    "messages": [{"role": "user", "content": "Draft a reply about a late order."}]
  }'
```

Scope and rate-limit headers arrive with the response head, before the first
chunk.

<Card title="Errors" icon="triangle-exclamation" href="/api-reference/errors">
  Status codes, error shapes, and which are worth retrying.
</Card>
