# Chat Completions

> The OpenAI Chat Completions format: parameters, differences, examples and errors.

Creates a model response for a conversation in the OpenAI Chat Completions format. Works with the official OpenAI SDKs and any OpenAI-compatible tool.

## Method and path

`POST /v1/chat/completions`

Base URL `https://api.relayvia.dev/v1`. Send the key as `Authorization: Bearer sk-...`.

## Common parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `model` | `string` | Yes | Official model name. |
| `messages` | `array` | Yes | The conversation: system, user, assistant and tool messages. Images work on models that accept image input. |
| `stream` | `boolean` | No | `true` to receive Server-Sent Events. |
| `stream_options` | `object` | No | `include_usage: true` adds a final chunk with token usage. |
| `max_completion_tokens` | `integer` | No | Output limit (`max_tokens` also works). It decides how much is reserved before the call, see [Limits and billing](https://relayvia.dev/docs/limits). |
| `temperature` | `number` | No | Sampling temperature. |
| `tools` | `array` | No | Tools your code runs (function and custom tools). |
| `tool_choice` | `string \| object` | No | Whether the model must, may or must not call a tool. |
| `response_format` | `object` | No | Structured output: `json_object` or `json_schema`. |
| `n` | `integer` | No | Number of choices to generate. |

Requests are forwarded in the original format, so the original API's parameters work unless the next section says otherwise. This table lists the common ones; see the [official reference](https://platform.openai.com/docs/api-reference/chat/create) for the rest.

## Differences from the original API

- Server-side stored state cannot be referenced: a non-null `previous_response_id`, `conversation`, `container`, `file_id`, `vector_store_ids`, `item_reference` or `cached_content`, or a top-level `file`, returns 400 `unsupported_parameter` with `param` naming the field. Your own tool schemas, tool-call arguments and `metadata` are not checked. `store` is always treated as false.
- `tools[]` only accepts `function` and `custom` tools. Tools that the model maker runs and bills separately (web search, code execution, file search and similar), `web_search_options` and `mcp_servers` return 400 `unsupported_parameter`.
- `user`, `safety_identifier` and `prompt_cache_key` are scoped to your account before forwarding, so prompt caches and user ids are never shared with other accounts.
- Chunks are forwarded as they arrive. A `: keepalive` comment line is sent after every 15 seconds without data; SSE clients ignore it. The usage chunk is only sent if you set `stream_options.include_usage`. The `model` field always shows the name you sent.
- A non-streaming request with no response within 95 seconds returns 500 `upstream_timeout`; use streaming for long outputs. Or send the header `X-Robi-Keepalive: 1`: if there is no response within 30 seconds you get a 200 right away and a space every 15 seconds until the JSON arrives, and a later failure arrives as an error object in the body.
- `n` must be a positive integer, otherwise 400 `invalid_request`. The reservation and the charge cover every choice.

## Request examples

**cURL**

```bash
curl https://api.relayvia.dev/v1/chat/completions \
  -H "Authorization: Bearer sk-..." \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-5.5","messages":[{"role":"user","content":"Hello!"}]}'
```

**Python**

```python
from openai import OpenAI

client = OpenAI(base_url="https://api.relayvia.dev/v1", api_key="sk-...")
resp = client.chat.completions.create(model="gpt-5.5", messages=[{"role": "user", "content": "Hello!"}])
print(resp.choices[0].message.content)
```

**JavaScript**

```javascript
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.relayvia.dev/v1", apiKey: "sk-..." });
const resp = await client.chat.completions.create({
  model: "gpt-5.5",
  messages: [{ role: "user", content: "Hello!" }],
});
console.log(resp.choices[0].message.content);
```

## Response and streaming examples

**Response**

```json
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1793750400,
  "model": "gpt-5.5",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Hello! How can I help?"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 8,
    "total_tokens": 17
  }
}
```

**Streaming**

```text
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","model":"gpt-5.5","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello!"}}]}

: keepalive

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","model":"gpt-5.5","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]
```

## Errors this endpoint returns

| HTTP | code | When |
| --- | --- | --- |
| `400` | `invalid_request` | A parameter is invalid. If the model rejected it, the message is this sentence, or `Invalid value for parameter <param>.` with `param` set. |
| `400` | `unsupported_parameter` | The request uses something Relayvia does not offer: stored server-side state, a hosted tool or a remote file. `param` names it. |
| `400` | `invalid_media` | The model reports that an input image or file format is not supported. |
| `400` | `media_unreachable` | The model could not read the media URL you sent. |
| `400` | `content_policy` | The input did not pass the model's safety review. If the model does not say whether the input or the output was refused, the message is `Content did not pass safety review.` |
| `400` | `content_policy_output` | The model generated a result, but the result did not pass safety review. Your input was not the problem. |
| `401` | `invalid_api_key` | The API key does not exist, is disabled or has expired. |
| `402` | `insufficient_quota` | Not enough credits: the account's credits, the key's limit, the team member's limit or an account spend limit cannot cover the request, or the account is frozen. |
| `403` | `forbidden` | The key may not use this model, the source IP is not on the allowlist, or the account is disabled. |
| `404` | `model_not_found` | The model is not listed. |
| `413` | `request_too_large` | The request body is over 20 MB, or the model reports that the input exceeds its context or size limit (then with this sentence). |
| `429` | `rate_limited` | Too many requests: per IP, per key, per account, or too many requests in progress at once. |
| `500` | `model_price_error` | The model has no price yet, so it cannot be called. |
| `500` | `upstream_error` | Relayvia tried other model services and every attempt failed. |
| `503` | `no_available_channel` | No model service is available for this model right now. |
| `503` | `service_busy` | Every model service for this model reported being busy. |
| `500` | `upstream_timeout` | Every attempt timed out. |

Every code, its fixed message and what to do are on the [error codes](https://relayvia.dev/docs/errors) page.
