genmux API is now generally available. Read the docs →
genmux API

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

HeaderRequiredDescription
AuthorizationyesBearer <API key>
Content-Typeyesapplication/json
Idempotency-KeynoIdempotency-Key is optional here. Reusing one returns 409 idempotency_key_reused.

Body

FieldTypeDescription
modelstringRequired. A text model id from GET /v1/models, e.g. "qwen3.8-max".
messagesarrayRequired. {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_tokensintegerReply cap. ≤ the model's max_output_tokens; default default_max_tokens. Reasoning tokens count toward it.
temperaturenumber0–2. Default from the model's default_params.
top_pnumber0–1.
stopstring[]Up to 4 stop sequences.
streambooleanDefault false. true → text/event-stream, see Streaming.
enable_thinkingbooleanExtension. Reasoning before the answer, returned as reasoning_content. Only on models with thinking: true.
web_searchbooleanExtension. Lets the model consult the web. Only on models with web_search: true.
reasoning_effortstringnone / minimal turn thinking off, low … xhigh turn it on. Only on models with thinking: true.
toolsarrayOpenAI function definitions. Only on models with tools: true; see Tool calling.
tool_choicestring | objectauto · none · required · {type: "function", function: {name}}.
response_formatobject{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

FieldLimit
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.
stopUp to 4 strings.
temperature0–2.
messagesAny 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}'
Response · text/event-stream
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.

Mid-stream failure
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}'

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.

Python · OpenAI SDK
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)
Response · first call
{
  "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.

Streaming · text/event-stream
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

FieldLimit
tools≤ 128 definitions, ≤ 64 KiB in total. Each definition costs prompt tokens: about 40 per tool plus its JSON schema.
tool_choiceauto (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 callsModels usually emit one tool call per turn; parallel_tool_calls is accepted and ignored. Agents loop, so multi-step tasks complete.
strictAccepted and ignored — there is no constrained decoding upstream.
response_formatCannot be combined with tools in one request.
max_tokensValues above max_output_tokens are clamped, not rejected. Default reply budget on the tool models is 8192 tokens.
Verified configs for OpenCode, Aider, Cline, Roo Code and Continue, and the measured pass rates, are on the coding agents page →

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}}}}'
Response · content is the JSON document
{"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": {…}}