MiniMax API with the OpenAI SDK in Python and Node.js
Call MiniMax-M2.7, MiniMax-M2.7-highspeed and MiniMax-M3 from the official OpenAI SDK in Python and Node.js by changing only the base URL, key and model ID. Runnable examples for chat, streaming, tool calling and JSON mode, plus per-token prices, cost controls and error handling.
YiduoChan serves MiniMax's text models through an OpenAI-compatible endpoint. If your code already uses the official openai package in Python or Node.js, the only changes are the base URL, the API key and the model ID. This page covers the full loop: installation, client setup, basic chat, streaming, tool calling, JSON output, model and price selection, cost controls, error handling and the equivalent curl requests.
What you need
- A YiduoChan account. Sign up and add prepaid credit. The minimum top-up is $5; there are no free credits or trials.
- An API key created in the console. It is sent as
Authorization: Bearer <API key>on every request. - The base URL
https://yiduochan.com/v1. Chat requests go to/v1/chat/completions,/v1/modelslists the models your key can call, and text-to-speech is at/v1/audio/speech. - One of the three text model IDs:
MiniMax-M2.7,MiniMax-M2.7-highspeedorMiniMax-M3. The IDs are case-sensitive and must be sent exactly as written.
Billing is pay-as-you-go against a USD balance, with no subscription. Failed requests are never charged. See pricing for the current list and the MiniMax overview for a summary of every model on the platform.
Install the SDK
Both official SDKs accept a custom base URL, so no third-party wrapper is needed.
pip install openai
npm install openai
The examples assume Python 3.9 or later and Node.js 18 or later with ES module syntax. Nothing below depends on a specific minor version of either SDK.
Initialize the client
Python
from openai import OpenAI
client = OpenAI(
base_url="https://yiduochan.com/v1",
api_key="YOUR_API_KEY", # created in the YiduoChan console
)
Node.js
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://yiduochan.com/v1",
apiKey: "YOUR_API_KEY", // created in the YiduoChan console
});
Keep the key out of source control and load it from your secret manager at startup. The same client object is reused in every example that follows.
Basic chat completion
Python
resp = client.chat.completions.create(
model="MiniMax-M2.7",
messages=[
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": "Explain idempotency in one paragraph."},
],
max_tokens=300,
)
print(resp.choices[0].message.content)
print(resp.usage)
Node.js
const resp = await client.chat.completions.create({
model: "MiniMax-M2.7",
messages: [
{ role: "system", content: "You are a concise assistant." },
{ role: "user", content: "Explain idempotency in one paragraph." },
],
max_tokens: 300,
});
console.log(resp.choices[0].message.content);
console.log(resp.usage);
The response has the standard OpenAI shape: choices[0].message.content holds the text and usage reports prompt and completion tokens. Log usage in production; it is the number you multiply by the per-token prices in the table below.
Streaming
Set stream to true to receive tokens as they are generated. Streaming does not change the price; it changes time-to-first-token. Pair it with MiniMax-M2.7-highspeed when a user is waiting on the other end.
Python
stream = client.chat.completions.create(
model="MiniMax-M2.7-highspeed",
messages=[{"role": "user", "content": "Write a haiku about retries."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
print()
Node.js
const stream = await client.chat.completions.create({
model: "MiniMax-M2.7-highspeed",
messages: [{ role: "user", content: "Write a haiku about retries." }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
process.stdout.write("\n");
Guard against chunks with an empty choices array or a delta with no content; both examples do this. The last chunk carries finish_reason, which is where you detect a length stop. For batch jobs where nobody is watching the output, leave streaming off: a single response is simpler to log, retry and store.
Function and tool calling
Tool calling uses the standard tools and tool_choice parameters. The sequence is: send the tool definitions, check whether the model returned tool_calls, execute each call locally, append the results as role: "tool" messages, then call the API again so the model can write the final answer. The example is complete and runnable; replace get_weather with a real lookup.
Python
import json
def get_weather(city: str) -> dict:
return {"city": city, "temp_c": 21, "condition": "clear"} # stub
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
messages = [{"role": "user", "content": "What is the weather in Berlin?"}]
first = client.chat.completions.create(
model="MiniMax-M2.7", messages=messages, tools=tools, tool_choice="auto",
)
msg = first.choices[0].message
messages.append(msg)
for call in msg.tool_calls or []:
args = json.loads(call.function.arguments)
result = get_weather(**args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result),
})
final = client.chat.completions.create(
model="MiniMax-M2.7", messages=messages, tools=tools,
)
print(final.choices[0].message.content)
Node.js
const tools = [{
type: "function",
function: {
name: "get_weather",
description: "Get the current weather for a city.",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
}];
function getWeather({ city }) {
return { city, temp_c: 21, condition: "clear" }; // stub
}
const messages = [{ role: "user", content: "What is the weather in Berlin?" }];
const first = await client.chat.completions.create({
model: "MiniMax-M2.7", messages, tools, tool_choice: "auto",
});
const msg = first.choices[0].message;
messages.push(msg);
for (const call of msg.tool_calls ?? []) {
const result = getWeather(JSON.parse(call.function.arguments));
messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) });
}
const final = await client.chat.completions.create({
model: "MiniMax-M2.7", messages, tools,
});
console.log(final.choices[0].message.content);
function.argumentsis a JSON string, not an object. Parse and validate it before executing anything.- Append the assistant message that carried
tool_callsbefore appending the tool results; the second request is malformed otherwise. - Loop the tool step if the model requests further calls, and cap the number of iterations.
- Tool definitions count as prompt tokens on every request. Keep descriptions short and see the prompt caching section below.
JSON mode and structured output
To get machine-readable output, pass response_format with type json_object and describe the expected keys in the system prompt, including the word JSON. Parse the result on the client and validate it against your own schema; treat a parse failure like any other retryable error.
Python
resp = client.chat.completions.create(
model="MiniMax-M2.7",
messages=[
{"role": "system", "content": "Reply with JSON only. Keys: title (string), tags (array of strings)."},
{"role": "user", "content": "Summarize: PostgreSQL 16 adds logical replication from standbys."},
],
response_format={"type": "json_object"},
max_tokens=200,
)
data = json.loads(resp.choices[0].message.content)
assert isinstance(data["tags"], list)
Node.js
const resp = await client.chat.completions.create({
model: "MiniMax-M2.7",
messages: [
{ role: "system", content: "Reply with JSON only. Keys: title (string), tags (array of strings)." },
{ role: "user", content: "Summarize: PostgreSQL 16 adds logical replication from standbys." },
],
response_format: { type: "json_object" },
max_tokens: 200,
});
const data = JSON.parse(resp.choices[0].message.content);
For strict schema enforcement, validate with pydantic in Python or zod in Node.js and, on failure, send the validation error back to the model as a follow-up message. This pattern is provider-independent and keeps your code portable. Also check finish_reason before parsing: a response cut off by max_tokens is almost always invalid JSON, and the fix is a larger limit, not a retry.
Choosing a model
All three text models are served at MiniMax list-level pricing in USD. Prices are per 1M tokens.
| Model | Context | Input | Output | Cache read | Cache write |
|---|---|---|---|---|---|
MiniMax-M2.7 | Standard | $0.30 | $1.20 | $0.06 | $0.375 |
MiniMax-M2.7-highspeed | Standard | $0.60 | $2.40 | $0.06 | $0.375 |
MiniMax-M3 (prompt up to 512K) | 1,048,576 tokens | $0.30 | $1.20 | $0.06 | — |
MiniMax-M3 (prompt 512K to 1M) | 1,048,576 tokens | $0.60 | $2.40 | $0.12 | — |
- MiniMax-M2.7 is the default for most workloads: batch processing, agents, code generation and anything not bound by interactive latency. Details on the M2.7 page.
- MiniMax-M2.7-highspeed is the same model at the same quality with lower latency, at twice the input and output price. Use it for chat interfaces and streaming assistants. Cache read and cache write rates are identical to M2.7, so a well-cached highspeed workload costs less than the headline numbers suggest.
- MiniMax-M3 has a 1,048,576-token context window. Prompts up to 512K tokens cost the same as M2.7; from 512K to 1M, input, output and cache read double. Choose it for whole-repository analysis, long transcripts or multi-document retrieval where chunking is not an option. See the M3 page.
Switching models is a one-line change to the model parameter; nothing else in the code differs. Because M2.7 and M2.7-highspeed are the same model at the same quality, there is no need to evaluate them separately for accuracy; the decision is purely latency against price. For M3, the tier is determined by the size of the prompt on each request, so a workload that mostly stays under 512K tokens is billed at the lower rate even though the 1M window is available.
Controlling cost
max_tokens
Output tokens cost four times input tokens on every model here ($1.20 versus $0.30 on M2.7), so max_tokens is the main lever. Set it per call to the longest answer the feature actually needs: a classifier rarely needs more than 50, a code generator may need several thousand. When the model stops because of the limit, finish_reason is length, which you can detect and handle instead of silently accepting a truncated answer.
Prompt caching
Cache read tokens cost $0.06 per 1M on all three models (rising to $0.12 only in the M3 512K to 1M tier), one fifth of the $0.30 input rate. Cache write is $0.375, so a prefix reused a single time already costs less than sending it uncached twice: $0.375 + $0.06 against 2 × $0.30. To benefit:
- Put stable content first: system prompt, tool definitions, few-shot examples, reference documents.
- Put variable content last: the user message and per-request retrieved snippets.
- Keep the stable prefix byte-identical between requests. A timestamp or request ID inside the system prompt defeats the cache.
- In agent loops, append to the same message history rather than rebuilding it in a different order.
Compare the usage figures from repeated requests against the price table to confirm the effect on your own workload.
Error handling and retries
The endpoint returns OpenAI-style error bodies with a code field, which both SDKs expose on the raised error. The cases to handle explicitly:
| Status | Code | Meaning | Action |
|---|---|---|---|
| 401 | — | Missing or invalid API key | Fix the Authorization header. Do not retry. |
| 403 | insufficient_user_quota | Prepaid balance exhausted | Top up in the console. Do not retry. |
| 429 | — | Rate limit reached | Back off exponentially and retry. |
| varies | model_not_found | Model ID misspelled or not enabled for the key | Check the ID against the table above. Match on the code, not the status. |
| 5xx / network | — | Transient failure | Retry with backoff. |
Both SDKs retry connection errors, 429 and 5xx automatically, controlled by max_retries in Python and maxRetries in Node.js. A model_not_found error can arrive with a 5xx status, so a default-configured SDK will retry a typo several times before surfacing it. The examples below disable built-in retries and inspect the code first. Failed requests are never billed, so retries only cost time. In tool-calling loops, retry only the API call, never the local tool execution, so that a transient error does not run a side-effecting function twice.
Python
import time
from openai import (
OpenAI, APIConnectionError, APIStatusError,
AuthenticationError, PermissionDeniedError, RateLimitError,
)
client = OpenAI(
base_url="https://yiduochan.com/v1",
api_key="YOUR_API_KEY",
max_retries=0, # retried below so the error code is checked first
)
def ask(prompt, model="MiniMax-M2.7", attempts=4):
for attempt in range(attempts):
try:
return client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
max_tokens=400,
)
except AuthenticationError:
raise # 401: fix the key
except PermissionDeniedError as e:
if e.code == "insufficient_user_quota":
raise RuntimeError("balance exhausted, top up") from e
raise
except RateLimitError:
pass # 429: back off below
except APIStatusError as e:
if e.code == "model_not_found":
raise ValueError(f"unknown model: {model}") from e
if e.status_code < 500:
raise # other 4xx: not transient
except APIConnectionError:
pass # network: back off below
time.sleep(2 ** attempt)
raise RuntimeError("gave up after retries")
Node.js
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://yiduochan.com/v1",
apiKey: "YOUR_API_KEY",
maxRetries: 0, // retried below so the error code is checked first
});
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function ask(prompt, model = "MiniMax-M2.7", attempts = 4) {
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await client.chat.completions.create({
model,
messages: [{ role: "user", content: prompt }],
max_tokens: 400,
});
} catch (err) {
if (err instanceof OpenAI.APIError) {
if (err.code === "model_not_found") throw new Error(`unknown model: ${model}`);
if (err.status === 401) throw new Error("invalid API key");
if (err.status === 403 && err.code === "insufficient_user_quota") {
throw new Error("balance exhausted, top up");
}
if (err.status !== 429 && err.status < 500) throw err; // other 4xx
}
await sleep(1000 * 2 ** attempt); // 429, 5xx or network
}
}
throw new Error("gave up after retries");
}
curl equivalent
Every SDK call above maps to a single HTTP request. Use these to verify a key or to debug from a shell.
curl https://yiduochan.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "MiniMax-M2.7",
"messages": [{"role": "user", "content": "Say hello in one sentence."}],
"max_tokens": 50
}'
Add "stream": true to the body to receive server-sent events instead of a single JSON object. The response body, including the usage object and any error, is byte-for-byte what the SDKs parse, so a curl call is the fastest way to see exactly what the gateway returned when the SDK raises. To list the models available to your key:
curl https://yiduochan.com/v1/models \
-H "Authorization: Bearer YOUR_API_KEY"
Beyond chat completions
The same key also works with the Anthropic Messages format at /v1/messages. Set ANTHROPIC_BASE_URL=https://yiduochan.com, ANTHROPIC_AUTH_TOKEN=<API key> and ANTHROPIC_MODEL=MiniMax-M2.7 or MiniMax-M3; count_tokens is not available. Setup for Claude Code is on the Claude Code page. Text-to-speech through the OpenAI-compatible /v1/audio/speech endpoint is covered on the speech-2.8 page.
Billing summary
- Prepaid USD credits, pay-as-you-go, no subscription.
- Minimum top-up $5; presets of $5, $10, $20, $50, $100, $200, $500 or any custom amount.
- Credits are valid for 12 months.
- Failed requests are never charged.
Integration or billing questions: [email protected].
FAQ
Does the MiniMax API work with the official OpenAI Python and Node.js SDKs?
Yes. Point the client at https://yiduochan.com/v1, pass your YiduoChan API key, and use the model IDs MiniMax-M2.7, MiniMax-M2.7-highspeed or MiniMax-M3; no other code changes are needed.
Does MiniMax support streaming and function calling through the OpenAI SDK?
Yes. The stream, tools and tool_choice parameters are passed through unchanged, and the tool-calling loop is the same as with any OpenAI-compatible model: execute the returned tool_calls locally, append the results as tool messages, and call the API again.
How much does the MiniMax API cost per 1M tokens?
MiniMax-M2.7 is $0.30 input and $1.20 output per 1M tokens, MiniMax-M2.7-highspeed is $0.60 and $2.40, and MiniMax-M3 matches M2.7 for prompts up to 512K tokens and doubles above that. Full details are on the pricing page.
How do I reduce MiniMax API costs with the OpenAI SDK?
Set max_tokens per call to what the feature needs, since output costs four times input, and keep a stable prompt prefix so repeated tokens are billed at the cache read rate of $0.06 per 1M instead of the $0.30 input rate.
What does the model_not_found error mean?
The model in the request does not exactly match MiniMax-M2.7, MiniMax-M2.7-highspeed or MiniMax-M3, or is not enabled for your key. Check spelling and case, and match on the error code rather than the HTTP status when handling it, since the status is not always 404.
Do I need a subscription or is there a free tier for the MiniMax API on YiduoChan?
No subscription and no free credits: you top up prepaid USD credits starting at $5, pay per token, credits are valid for 12 months, and failed requests are never charged. Create an account at /register.