Chat
POST /v1/chat/completions
Creates a chat completion on a text model. OpenAI-compatible request and response; synchronous — the reply is in the response (or streamed, see below). Credits are reserved for the estimated prompt plus min(max_tokens, default_max_tokens) output tokens and settled to the actual usage when the call ends; nothing is charged when no tokens were produced.
Headers
| Header | Required | Description |
|---|---|---|
| Authorization | yes | Bearer <API key> |
| Content-Type | yes | application/json |
| Idempotency-Key | no | Idempotency-Key is optional here. Reusing one returns 409 idempotency_key_reused. |
Body
| Field | Type | Description |
|---|---|---|
| model | string | Required. A text model id from GET /v1/models, e.g. "qwen3.8-max". |
| messages | array | Required. {role: "system" | "user" | "assistant", content: string | parts[]}. Content parts (text, image_url) as on the OpenAI API; image_url only on models with vision: true. |
| max_tokens | integer | Reply cap. ≤ the model's max_output_tokens; default default_max_tokens. Reasoning tokens count toward it. |
| temperature | number | 0–2. Default from the model's default_params. |
| top_p | number | 0–1. |
| stop | string[] | Up to 4 stop sequences. |
| stream | boolean | Default false. true → text/event-stream, see Streaming. |
| enable_thinking | boolean | Extension. Reasoning before the answer, returned as reasoning_content. Only on models with thinking: true. |
| web_search | boolean | Extension. Lets the model consult the web. Only on models with web_search: true. |
| reasoning_effort | string | none / minimal turn thinking off, low … xhigh turn it on. Only on models with thinking: true. |
| tools | array | OpenAI function definitions. Only on models with tools: true; see Tool calling. |
| tool_choice | string | object | auto · none · required · {type: "function", function: {name}}. |
| response_format | object | {type: "json_object"} or {type: "json_schema", json_schema: {name, schema}}; see Structured output. |
Not supported — rejected with 400 unsupported_parameter, never silently ignored: n > 1, functions (legacy), logprobs.
curl -s -X POST https://api.genmux.tech/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"qwen3.8-max","messages":[{"role":"user","content":"Explain idempotency keys in two sentences."}],"max_tokens":200}'Response 200 OK
{
"id": "0d8b6a3e-2f1c-4a5b-9c1d-7e2f3a4b5c6d",
"object": "chat.completion",
"created": 1756411279,
"model": "qwen3.8-max",
"choices": [
{"index": 0, "message": {"role": "assistant", "content": "An idempotency key is …"}, "finish_reason": "stop"}
],
"usage": {"prompt_tokens": 31, "completion_tokens": 58, "total_tokens": 89, "cost": 0.004, "cost_source": "vendor"}
}usage.cost is the credits charged (decimal, up to 3 places); cost_source is "vendor" when the upstream reported the token counts and "estimated" when they were counted locally.
Limits
| Field | Limit |
|---|---|
| max_tokens | ≤ the model's max_output_tokens; default default_max_tokens. A prompt that leaves no room for a reply is a 400 context_length_exceeded. |
| stop | Up to 4 strings. |
| temperature | 0–2. |
| messages | Any number of system, user and assistant turns within the context window. image_url parts only on models with vision: true. |
Streaming
With "stream": true the response is text/event-stream. Every data: line carries one chat.completion.chunk; the last chunk has an empty choices array and the usage block (with the credits charged), and the stream ends with data: [DONE].
curl -sN -X POST https://api.genmux.tech/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"qwen3.8-max","messages":[{"role":"user","content":"Explain idempotency keys in two sentences."}],"stream":true}'data: {"id":"0d8b6a3e-…","object":"chat.completion.chunk","model":"qwen3.8-max","choices":[{"index":0,"delta":{"role":"assistant","content":"An"},"finish_reason":null}]}
data: {"id":"0d8b6a3e-…","object":"chat.completion.chunk","model":"qwen3.8-max","choices":[{"index":0,"delta":{"content":" idempotency"},"finish_reason":null}]}
…
data: {"id":"0d8b6a3e-…","object":"chat.completion.chunk","model":"qwen3.8-max","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"0d8b6a3e-…","object":"chat.completion.chunk","model":"qwen3.8-max","choices":[],"usage":{"prompt_tokens":31,"completion_tokens":58,"total_tokens":89,"cost":0.004,"cost_source":"estimated"}}
data: [DONE]If the upstream fails mid-stream, an error frame is sent instead of the usage chunk and the stream still ends with [DONE]; the reservation is settled against what was actually generated, and GET /v1/generations/{id} shows the final cost.
data: {"error": {"code": "upstream_error", "message": "the model provider closed the connection"}}
data: [DONE]Closing the connection stops generation. The tokens generated before the abort are billed; nothing else is.
OpenAI SDK
The endpoint speaks the OpenAI chat completions dialect, so the official SDKs work unchanged: point base_url at https://api.genmux.tech/v1 and pass your key as api_key. Model ids are ours (see GET /v1/models), not the vendor's.
curl -s -X POST https://api.genmux.tech/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"qwen3.8-max","messages":[{"role":"user","content":"Explain idempotency keys in two sentences."}],"max_tokens":200}'import os
from openai import OpenAI
client = OpenAI(base_url="https://api.genmux.tech/v1", api_key=os.environ["API_KEY"])
completion = client.chat.completions.create(
model="qwen3.8-max",
messages=[{"role": "user", "content": "Explain idempotency keys in two sentences."}],
max_tokens=200,
)
print(completion.choices[0].message.content)
print(completion.usage.model_extra["cost"], "credits")
stream = client.chat.completions.create(model="qwen3.8-max", messages=[{"role": "user", "content": "Hi"}], stream=True)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
if chunk.usage:
print("\n", chunk.usage.total_tokens, "tokens,", chunk.usage.model_extra["cost"], "credits")import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.genmux.tech/v1", apiKey: process.env.API_KEY });
const completion = await client.chat.completions.create({
model: "qwen3.8-max",
messages: [{ role: "user", content: "Explain idempotency keys in two sentences." }],
max_tokens: 200,
});
console.log(completion.choices[0].message.content, completion.usage.cost);
const stream = await client.chat.completions.create({ model: "qwen3.8-max", messages: [{ role: "user", content: "Hi" }], stream: true });
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
if (chunk.usage) console.log(`\n${chunk.usage.total_tokens} tokens, ${chunk.usage.cost} credits`);
}Supported: model, messages (system/user/assistant; image_url parts on vision models), max_tokens, temperature, top_p, stop, stream, plus the extensions enable_thinking and web_search passed as extra body fields.
Also supported: tools / tool_choice on models with tools: true (see Tool calling), response_format json_object / json_schema (see Structured output), reasoning_effort. Rejected with 400 unsupported_parameter: n > 1, the legacy functions field, logprobs. Accepted and ignored: stream_options, parallel_tool_calls, seed, user, metadata, store, logit_bias and message name.
Read usage from the completion (sync) or from the last chunk (stream): cost is the credits charged.
Tool calling
OpenAI function calling on the models whose catalogue entry says tools: true. Send tools + tool_choice (auto · none · required · {type: "function", function: {name}}); the reply comes back with message.tool_calls and finish_reason tool_calls; return results as role: tool messages with the matching tool_call_id. Function calling is emulated by the api — the tools are rendered into the system prompt and the reply is parsed back — so the wire format is exactly OpenAI's while the models never see a native tools field.
Models with tools: true right now: doubao-seed-2-pro, doubao-seed-2-code, kimi-k3, claude-opus-5-5, claude-fable-5-1, gpt-6-astra, gpt-5.6-terra, qwen3.8-max. A tools request to any other model is 400 tools_unsupported.
from openai import OpenAI
client = OpenAI(base_url="https://api.genmux.tech/v1", api_key="nb_live_...")
tools = [{"type": "function", "function": {"name": "get_weather", "description": "Weather by city",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}}]
r = client.chat.completions.create(model="qwen3.8-max",
messages=[{"role": "user", "content": "Weather in Paris?"}], tools=tools)
call = r.choices[0].message.tool_calls[0] # call.function.name == "get_weather"
# run your function, then send the result back:
r2 = client.chat.completions.create(model="qwen3.8-max", tools=tools, messages=[
{"role": "user", "content": "Weather in Paris?"},
r.choices[0].message,
{"role": "tool", "tool_call_id": call.id, "content": "18°C, cloudy"},
])
print(r2.choices[0].message.content){
"id": "0d8b6a3e-…",
"object": "chat.completion",
"model": "qwen3.8-max",
"choices": [
{"index": 0, "finish_reason": "tool_calls", "message": {"role": "assistant", "content": null,
"tool_calls": [{"id": "call_1", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\": \"Paris\"}"}}]}}
],
"usage": {"prompt_tokens": 96, "completion_tokens": 21, "total_tokens": 117, "cost": 0.003, "cost_source": "vendor"}
}With stream: true the call arrives as delta.tool_calls chunks — index, id and name first, then the arguments in fragments — followed by a chunk with finish_reason tool_calls and the usual usage chunk.
data: {"id":"0d8b6a3e-…","object":"chat.completion.chunk","model":"qwen3.8-max","choices":[{"index":0,"delta":{"role":"assistant","tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"get_weather","arguments":""}}]},"finish_reason":null}]}
data: {"id":"0d8b6a3e-…","object":"chat.completion.chunk","model":"qwen3.8-max","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\": \"Paris\"}"}}]},"finish_reason":null}]}
data: {"id":"0d8b6a3e-…","object":"chat.completion.chunk","model":"qwen3.8-max","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}
data: {"id":"0d8b6a3e-…","object":"chat.completion.chunk","model":"qwen3.8-max","choices":[],"usage":{"prompt_tokens":96,"completion_tokens":21,"total_tokens":117,"cost":0.003,"cost_source":"estimated"}}
data: [DONE]Limits
| Field | Limit |
|---|---|
| tools | ≤ 128 definitions, ≤ 64 KiB in total. Each definition costs prompt tokens: about 40 per tool plus its JSON schema. |
| tool_choice | auto (default), none, required or a named function. required and a named function are instructions the model usually follows; small models occasionally answer in prose instead. |
| parallel calls | Models usually emit one tool call per turn; parallel_tool_calls is accepted and ignored. Agents loop, so multi-step tasks complete. |
| strict | Accepted and ignored — there is no constrained decoding upstream. |
| response_format | Cannot be combined with tools in one request. |
| max_tokens | Values above max_output_tokens are clamped, not rejected. Default reply budget on the tool models is 8192 tokens. |
Structured output
Pass response_format with {"type": "json_object"} or {"type": "json_schema", "json_schema": {"name", "schema"}} ({name, schema}, JSON Schema 2020-12) and the model is instructed to answer with JSON only. Non-streamed replies are validated: on a failure the api retries once with the validation error fed back to the model; a second failure is 502 json_invalid and both attempts are billed. Streamed replies pass through unvalidated. response_format and tools cannot be combined.
curl -s -X POST https://api.genmux.tech/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"qwen3.8-max",
"messages":[{"role":"user","content":"Extract the city and the date: meet me in Paris on 3 March"}],
"response_format":{"type":"json_schema","json_schema":{"name":"event",
"schema":{"type":"object","properties":{"city":{"type":"string"},"date":{"type":"string"}},"required":["city","date"],"additionalProperties":false}}}}'{"id": "…", "object": "chat.completion", "model": "qwen3.8-max", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\"city\": \"Paris\", \"date\": \"2026-03-03\"}"}, "finish_reason": "stop"}], "usage": {…}}