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

  1. A YiduoChan account. Sign up and add prepaid credit. The minimum top-up is $5; there are no free credits or trials.
  2. An API key created in the console. It is sent as Authorization: Bearer <API key> on every request.
  3. The base URL https://yiduochan.com/v1. Chat requests go to /v1/chat/completions, /v1/models lists the models your key can call, and text-to-speech is at /v1/audio/speech.
  4. One of the three text model IDs: MiniMax-M2.7, MiniMax-M2.7-highspeed or MiniMax-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);

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.

ModelContextInputOutputCache readCache write
MiniMax-M2.7Standard$0.30$1.20$0.06$0.375
MiniMax-M2.7-highspeedStandard$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

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:

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:

StatusCodeMeaningAction
401Missing or invalid API keyFix the Authorization header. Do not retry.
403insufficient_user_quotaPrepaid balance exhaustedTop up in the console. Do not retry.
429Rate limit reachedBack off exponentially and retry.
variesmodel_not_foundModel ID misspelled or not enabled for the keyCheck the ID against the table above. Match on the code, not the status.
5xx / networkTransient failureRetry 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

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.

Sign up and get an API key See full model pricing