# Create a chat completion

Send a conversation in OpenAI’s Chat Completions shape and get the model’s reply, whole or streamed. Momor reads only what routes the call; everything else goes to the provider as you wrote it.

`POST https://api.momor.ai/v1/chat/completions`

Also answers at `/models/completions`. An OpenAI SDK works unchanged with its base URL set to the `/v1` address and a Momor key. A call routes exactly as one sent to [POST /models](/docs/gateway/reference/chat/) does: the same offerings, keys, failover and record.

An OpenAI offering is called on OpenAI’s own Chat Completions API through this route, and on OpenAI’s Responses API through [POST /models](/docs/gateway/reference/chat/), which is what carries reasoning blocks and their summaries back in Momor’s reply. Each route adds only a few milliseconds over calling that API directly, but OpenAI’s Responses API itself starts answering later than its Chat Completions API, so the first token of an OpenAI call arrives sooner through `/v1/chat/completions`.

## Headers

- `Authorization` (string, required): Bearer and your API key. A signed-in Momor session works too.
- `Content-Type` (string, required): application/json. Anything else is refused with 415.

## Body

Momor reads four fields, to route the call. Everything else goes to the provider as you wrote it, but for [what Momor changes on the way](#what-momor-changes-on-the-way), and is not checked: a field, message or part a provider does not take is that provider’s to answer.

- `model` (string | string[]): Momor reads it. A selector, such as anthropic/claude-sonnet-5, tier/high or auto, or several. Required unless models names one.
- `models` (string | string[]): Momor reads it. Further selectors, tried in order after every one in model.
- `provider` (object): Momor reads it. Which providers lead, which serve nothing on this call, how tied offerings are ordered, whether it runs under zero data retention, whether deprecated models may serve it and how old a model may be. A value Momor cannot read is refused with 400; any other option is ignored.
  - `order` (string[]): Providers that lead, in this order, ahead of price: a root such as azure, or a region as name@region, such as azure@eu.
  - `ignore` (string[]): Providers that serve nothing on this call. A root leaves out the root alone; name@region leaves out that region alone.
  - `sort` (string, default `price`): How offerings the order leaves tied are ordered. price, the cheapest first, is the only order Momor offers.
  - `zdr` (boolean, default `false`): Run the call under zero data retention: only on saved keys marked zdr and on Momor’s keys its own agreements cover. Anything but true or false is refused with 400. Where your organization holds every call to it, false here does not turn it off.
  - `deprecated` (boolean): Let deprecated models serve a tier, auto or * selector. Left out, your organization’s default, else false. A selector naming one exact model is served by it either way.
  - `maxAge` (string): How old a model may be, as ISO 8601 such as P6M, P1Y or P2Y, or a TimeSpan such as 180.00:00:00: a tier, auto or * selector is served only by models released no longer ago than this. A month counts thirty days and a year 365. Left out, your organization’s default, else no model is left out for its age. A selector naming one exact model is served by it whatever its age.
- `stream` (boolean, default `false`): Momor reads it. Send the reply as server-sent events. Anything but true or false is refused with 400.
- `messages` (object[]): The conversation, in Chat Completions’ shape. Passed to the provider.
- `max_tokens` (integer): The output ceiling, as is max_completion_tokens; both go as one, as described under What Momor changes on the way. Anthropic requires one of them.
- `tools` (object[]): Passed to the provider. A call with tools is tried first on the offerings that take them; the others stay behind them as backup.
- `response_format` (object): Passed to the provider. A json_schema format is tried first on the offerings that take one.
- `stream_options` (object): Passed to the provider. include_usage asks for a usage chunk at the end of a stream.

A body that is not a JSON object, a call that names no model, and a routing field that can’t be read or names a provider this deployment does not serve are refused with `400 invalid_value`.

## Response

A provider that speaks Chat Completions answers in its own words: its fields, `logprobs` and choices included, come back as it wrote them, with `id` and `model` written over and its reasoning as `reasoning`. An answer from Anthropic, Gemini or Vertex is translated into this shape.

- `id` (string): The call’s id, the same as X-Momor-Request-Id. Use it with Get a generation.
- `object` (string): chat.completion
- `created` (integer): When the answer was made, in Unix seconds.
- `model` (string): The id of the model that served the call, publisher/name in lowercase.
- `choices` (object[]): The answers. Anthropic answers with one; Gemini with as many as n asks for.
  - `index` (integer): The choice’s position.
  - `message` (object): The assistant’s turn.
    - `role` (string): assistant
    - `content` (string | null): The answer, or null for a turn that only called tools.
    - `reasoning` (string): The text of the model’s reasoning.
    - `reasoning_details` (object[]): Each reasoning block, with the provider state it is replayed with in data. Keep it in the message you send back.
    - `tool_calls` (object[]): Each call whole, with its arguments as a JSON string. A Gemini call carries its signature in extra_content.google.thought_signature.
  - `logprobs` (object | null): The log probability of each token and its alternatives, where logprobs asked and the provider returned them.
  - `finish_reason` (string): Why the answer ended. Values: `stop`, `length`, `tool_calls`, `content_filter`.
- `usage` (object): Tokens, as the provider reported them.
  - `prompt_tokens` (integer): Input, cache reads and cache writes together. prompt_tokens_details.cached_tokens is the cache reads.
  - `completion_tokens` (integer): Output, separately billed reasoning included. completion_tokens_details.reasoning_tokens is the reasoning within it.
  - `total_tokens` (integer): The two together.
- `system_fingerprint` (string): The backend that served the call, where the provider names one.

Who served the call, every attempt and what each cost are on the call record: read it with [Get a generation](/docs/gateway/reference/generation/).

## Response headers

- `X-Momor-Request-Id` (string): The call’s id. Sent on every answer the route gives, a routing refusal included.
- `X-Momor-Provider` (string): On a stream: the provider answering.
- `X-Momor-Model` (string): On a stream: the id of the model answering.
- `X-RateLimit-Limit` (integer): The requests allowed in the window. See Rate limits, with X-RateLimit-Remaining and X-RateLimit-Reset.

## Streaming

With `stream` set, the answer starts once a model has something to show, with `X-Momor-Provider` and `X-Momor-Model` naming who serves it. A failure before then is an HTTP error, so the SDK raises it as one, and routing may still move to another model.

A provider that speaks Chat Completions streams its own chunks, each with `id` and `model` written over and its reasoning as `delta.reasoning`. A translated stream’s first chunk names the assistant; text arrives as `delta.content`, reasoning as `delta.reasoning` and then as one `delta.reasoning_details` entry once a block ends. The last choice chunk carries `finish_reason`, a chunk with empty `choices` carries `usage` where `stream_options.include_usage` asked for it, and `data: [DONE]` ends the stream.

A failure after output ends the stream on one error chunk, with no `[DONE]`. A Chat Completions stream never moves to another provider once output has begun.

```text
data: {"id":"4bf92f3577b34da6a3ce929d0e0e4736","object":"chat.completion.chunk","created":1759400000,"model":"anthropic/claude-sonnet-5","provider":"Anthropic","error":{"code":502,"message":"The provider closed the connection mid-answer."},"choices":[{"index":0,"delta":{"content":""},"finish_reason":"error"}]}
```

## Errors

Every failure is OpenAI’s error object, `{ "error": { "message", "type", "param", "code" } }`, with `param` `null` unless the provider’s own error object names one.

| Status | Code | What happened |
| --- | --- | --- |
| `400` | `invalid_value` | The body is not a JSON object, names no model, or a routing field can’t be read or names a provider this deployment does not serve. provider.zdr that is not a boolean is refused as 'provider.zdr' must be a boolean. |
| `401` | `invalid_api_key` | The key is missing, revoked, expired or not a Momor key. |
| `404` | `model_not_found` | No model this deployment serves answers to the selectors, or none of them runs on your own key or on a key Momor resells. Under zero data retention, that is a saved key marked zdr or a Momor key its own agreements cover. |
| `413` | `request_too_large` | The body is larger than 30 MiB. |
| `415` | `unsupported_media_type` | The body is not application/json. |
| `422` | `unsupported_parameter` | Models matched, and none of them could carry the call. |
| `429` | `rate_limit_exceeded` | Too many requests; wait for Retry-After. A provider limiting your own key answers 429 too, naming the provider. |
| `502` | `—` | A provider refused your own key for it. The message names the provider. |
| `503` | `providers_unavailable` | Every candidate failed or was unavailable. |
| `Other 5xx` | `—` | The last provider’s own failure, with type server_error. |
| `Provider’s own` | `—` | Where the last provider answered with OpenAI’s error object: its status and message, with its own type, param and code. |

## Translated providers

Anthropic, Gemini and Vertex do not speak Chat Completions. A call routed to one of them is translated into its own API, and its answer is written back in Chat Completions’ shape. A field its API has no counterpart for is left off; content it has no way to carry, such as audio sent to Anthropic, fails that offering, and the call moves to the next candidate.

| Chat Completions | Anthropic | Gemini · Vertex |
| --- | --- | --- |
| system and developer messages | The system prompt | The system instruction |
| text, image_url and file parts | Text, with its cache_control; an image by data URL or address; a PDF, or a text file as its text | Text; inline bytes; an image by address |
| input_audio parts | Not carried: the call moves to the next candidate | Inline bytes |
| tool_calls and tool messages | Tool use and tool results | Function calls and responses |
| tools, tool_choice, parallel_tool_calls | Tools, with strict, and the tool choice | Function declarations and the calling mode |
| response_format | json_schema | json_schema and json_object |
| reasoning_effort | Thinking and effort, for the models that take them | The thinking level |
| max_completion_tokens · max_tokens | Required | The output ceiling |
| temperature · top_p · top_k · stop | Each | Each |
| seed · the penalties · n · logprobs · top_logprobs | Left off | Each |
| metadata | Left off | Labels |
| service_tier | Each | Each |
| user | Each | Left off |

Send an assistant message back as you received it. Its `reasoning_details` carry the state Anthropic and Gemini check on replay.

## What Momor changes on the way

- model becomes the provider’s own id for the offering, and models and provider are taken off.
- A stream always asks for usage with stream_options.include_usage, so the call can be recorded; you receive the usage chunk only where you asked for it. A provider that takes no stream_options, such as Mistral, is sent none.
- max_completion_tokens and max_tokens go as one ceiling, under the name the provider reads: OpenAI reads max_completion_tokens, and Mistral and DeepInfra read max_tokens. Where you send both, max_completion_tokens is the one that goes.
- seed goes under the name the provider reads: Mistral reads it as random_seed.
- reasoning_effort goes as a depth the model takes: the one you sent where the model takes it, otherwise the nearest deeper one it takes.
- On Momor’s key OpenAI keeps nothing: store goes to OpenAI as false, whatever you sent. On your own key, store goes as you wrote it.
- Reasoning you send back moves into the field that provider reads, and reasoning a provider answers with comes back as reasoning. Reasoning a model writes inside its answer as a <think> element stays in content.

```bash title="Request"
curl https://api.momor.ai/v1/chat/completions \
  -H "Authorization: Bearer $MOMOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5",
    "models": ["tier/high"],
    "max_tokens": 1024,
    "messages": [
      { "role": "user", "content": "What is the capital of France?" }
    ]
  }'
```

```python
import os
from openai import OpenAI

client = OpenAI(base_url="https://api.momor.ai/v1", api_key=os.environ["MOMOR_API_KEY"])

answer = client.chat.completions.create(
    model="anthropic/claude-sonnet-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "What is the capital of France?"}],
)
print(answer.choices[0].message.content)
```

```typescript
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.momor.ai/v1", apiKey: process.env.MOMOR_API_KEY });

const answer = await client.chat.completions.create({
  model: "anthropic/claude-sonnet-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "What is the capital of France?" }],
});
console.log(answer.choices[0].message.content);
```

```csharp
using System.ClientModel;
using OpenAI;
using OpenAI.Chat;

var client = new ChatClient(
    model: "anthropic/claude-sonnet-5",
    credential: new ApiKeyCredential(Environment.GetEnvironmentVariable("MOMOR_API_KEY")!),
    options: new OpenAIClientOptions { Endpoint = new Uri("https://api.momor.ai/v1") });

ChatCompletion answer = await client.CompleteChatAsync(
    [new UserChatMessage("What is the capital of France?")],
    new ChatCompletionOptions { MaxOutputTokenCount = 1024 });
Console.WriteLine(answer.Content[0].Text);
```

```json title="Response"
{
  "id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "object": "chat.completion",
  "created": 1759400000,
  "model": "anthropic/claude-sonnet-5",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "Paris.",
      "reasoning": "Recall the capitals of Europe.",
      "reasoning_details": [{ "type": "reasoning.text", "text": "Recall the capitals of Europe.", "data": { "anthropic.signature": "…" } }]
    },
    "logprobs": null,
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 800, "completion_tokens": 120, "total_tokens": 920,
    "prompt_tokens_details": { "cached_tokens": 0 },
    "completion_tokens_details": { "reasoning_tokens": 0 }
  }
}
```

```json
{
  "error": {
    "message": "No configured model answers to claud-sonnet-5.",
    "type": "invalid_request_error",
    "param": null,
    "code": "model_not_found"
  }
}
```
