Chat and streaming
POST /v1/chat/completions is the main endpoint. Request and response match OpenAI, so any compatible SDK works.
Parameters
Section titled “Parameters”| Field | Type | Description |
|---|---|---|
model |
string, required | Catalog identifier or alias (gpt-4o, claude-opus) |
messages |
array, required | Conversation so far; the last user message is the prompt |
stream |
boolean | true streams the answer over SSE |
stream_options |
object | {"include_usage": true} appends a final chunk with token usage |
temperature |
number | As in OpenAI. Not every model takes it: GPT-5.x/5.6/6 and Claude have none — check supported_parameters in GET /models |
max_tokens |
integer | Ceiling on answer length |
max_completion_tokens |
integer | Synonym for max_tokens, for newer SDKs |
reasoning_effort |
string | Reasoning depth: off, low, medium, high, xhigh or max — the set depends on the model, see reasoning effort. For routers (typesafe/jev-router) it is a hint: the router picks the effort itself and may override it |
tools |
array | Tool descriptions in the OpenAI format; passed to the model as is — how to use |
tool_choice |
string | object | auto, none, required, or a specific tool |
web_search |
bool | string | true/"auto" — the model’s native search where available, else Exa; "native"/"exa" — explicit. Sources arrive in message.annotations; full guide — web search |
response_format |
object | {"type": "json_object"} or {"type": "json_schema", …} — guaranteed JSON — structured output |
stop |
array | Up to 4 stop sequences: generation ends as soon as one appears in the answer |
seed |
integer | Sampling seed — best-effort reproducibility |
top_p |
number 0–1 | Nucleus sampling: only the most likely tokens covering this probability mass |
top_k |
integer | Keep the K most likely tokens. Available on open-weight models |
min_p |
number 0–1 | Drop tokens below this share of the top token’s probability |
frequency_penalty |
number −2…2 | Penalty grows with how often the token already appeared |
presence_penalty |
number −2…2 | Penalty for the token appearing at all |
repetition_penalty |
number 0–2 | Penalty for repeating the prompt and the answer so far |
logprobs |
boolean | Return log probabilities of the chosen tokens |
top_logprobs |
integer 0–20 | Alternatives per position to return; requires logprobs |
provider |
object | Route selection: order, only, ignore, sort — provider routing |
service_tier |
string | flex — cheaper and slower, priority — dearer and faster; not every model has a tier |
plugins |
array | Upstream plugins as-is; for typesafe/jev-router — a custom model pool: [{"id": "jev-router", "models": ["anthropic/*", "openai/gpt-6*"]}] (fields models/allowed_models, excluded_models, cost_tier) |
Models accept different parameter sets: the upstream drops what a model does
not know instead of erroring. The actual list per model is in the
supported_parameters field of GET /models.
Routers
Section titled “Routers”typesafe/jev-router picks the model and reasoning effort for each request itself — the model that actually served the request arrives in the served_model response field (in streaming: a separate chunk with empty choices before the usage chunk). The field appears only when the served model differs from the requested one. Billing follows the chosen model’s rates.
Streaming
Section titled “Streaming”stream = client.chat.completions.create( model="gpt-5.6-luna", messages=[{"role": "user", "content": "Write a haiku about deadlines"}], stream=True, stream_options={"include_usage": True},)for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True)const stream = await client.chat.completions.create({ model: 'gpt-5.6-luna', messages: [{ role: 'user', content: 'Write a haiku about deadlines' }], stream: true, stream_options: { include_usage: true },})for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? '')}curl -N https://api.mixen.ai/v1/chat/completions \ -H "Authorization: Bearer $MIXEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.6-luna", "messages": [{"role": "user", "content": "Write a haiku about deadlines"}], "stream": true, "stream_options": {"include_usage": true} }'The stream is text/event-stream and ends with data: [DONE]. With include_usage, a chunk carrying usage arrives just before it — a convenient place to log spend: it also carries cost / cost_rub, what the request was charged (see Tokens and charges).
The usage also carries prompt_tokens_details.cached_tokens — how many input tokens came from the context cache. See Context caching.
Image input
Section titled “Image input”Models with capabilities.vision: true accept an image inside a message:
resp = client.chat.completions.create( model="gpt-5.6-terra", messages=[{ "role": "user", "content": [ {"type": "text", "text": "What's wrong with this chart?"}, {"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBORw0KG..."}}, ], }],)Only data: URLs (data:image/png;base64,...) are accepted, in the last user message — external HTTP links are not supported, so base64-encode the image.
File input
Section titled “File input”Models with capabilities.file: true take up to 5 files per request. Formats: PDF, DOCX, TXT, CSV, XLSX, PPTX — the text is extracted and handed to the model as context:
{ "role": "user", "content": [ {"type": "text", "text": "Summarise the Risks section"}, {"type": "file", "file": {"filename": "report.pdf", "file_data": "data:application/pdf;base64,JVBERi0..."}} ]}Each file yields at most 150,000 characters; longer ones are cut with a [...truncated] marker appended. There is no OCR: an image-only PDF scan comes back empty — use documents with a text layer.
A model that cannot read files returns 400, so check the flag in the catalog first.
Audio input
Section titled “Audio input”Models with audio input (in the catalog: capabilities.audio: true, e.g. google/gemini-4-argon) take sound as an input_audio content part:
{ "role": "user", "content": [ {"type": "text", "text": "Transcribe this recording and list the agreements"}, {"type": "input_audio", "input_audio": {"data": "//uQx...", "format": "mp3"}} ]}The data field is raw base64 without the data:audio/...;base64, prefix (unlike images); the format is a separate field: mp3, wav, flac, pcm16. Audio tokens are billed at a separate audio-input rate (on multimodal models it can differ from the text one — see the Providers panel on the model page); repeated requests with the same audio hit the audio cache at a discount.
Reasoning effort
Section titled “Reasoning effort”Some models let you dial reasoning depth. The accepted values arrive in capabilities.reasoning_efforts — usually low, medium, high, xhigh, max.
{"model": "claude-opus-4.8", "reasoning_effort": "high", "messages": [...]}Two things to watch:
- Reasoning consumes the
max_tokensbudget. If you expect long JSON from a model that reasons, set the ceiling generously or the answer gets cut mid-structure. Truncation is visible in the response:finish_reason: "length"(orstop_reason: "max_tokens"on/v1/messages), so you can detect it programmatically and continue. - Reasoning cannot always be switched off — check
capabilities.can_disable_reasoning. Where it readsfalse, the model always reasons, and the price reflects that.
Web search
Section titled “Web search”Models with type: search (the Perplexity Sonar family) reach the internet themselves. There is no separate endpoint or tool — just name one:
curl https://api.mixen.ai/v1/chat/completions \ -H "Authorization: Bearer $MIXEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"sonar-pro","messages":[{"role":"user","content":"What did Anthropic ship this week?"}]}'On a scoped key these models need the search scope rather than chat.
The web_search parameter — search on any model
Section titled “The web_search parameter — search on any model”Web search can also be enabled on any other text model with the web_search parameter:
{ "model": "google/gemini-3.8-flash", "web_search": "native", "messages": [{"role": "user", "content": "What is the central bank USD rate today? One line with a source"}]}Values: true/"auto" — the model’s native search where available, else Exa; "native" — the provider’s built-in search; "exa" — neutral search. Native search exists on OpenAI, Anthropic, Google and xAI models — the catalog marks them with capabilities.native_search: true; sources arrive in message.annotations (url_citation), same as Sonar. When streaming, they come as a separate chunk carrying delta.annotations right before the final chunk with finish_reason. "native" on a model without built-in search is silently downgraded to "auto". Billing, the balance reserve and Sonar’s behaviour are covered in the Web search guide.
Search is billed per performed request (not per token): the price sits next to the model in the catalog (web_search_rub_per_request in GET /v1/models). The model decides on its own whether and how many times to search — a question like “what is a fraction” triggers none.
Other protocols
Section titled “Other protocols”Besides /v1/chat/completions, two more chat protocols run on the same key and balance:
POST /v1/messages— Anthropic-compatible (Claude Code, Anthropic SDK): acceptsthinking.budget_tokens,cache_controland tool-use; there is alsoPOST /v1/messages/count_tokens. See Claude Code.POST /v1/responses— the OpenAI Responses API (Codex CLI): stateless, acceptsreasoning.effort. See Codex CLI.