Switch from OpenAI to MiniMax without rewriting your app

If your service already speaks the OpenAI Chat Completions API, moving it to MiniMax models on YiduoChan is a configuration change: a new base URL, a new API key, a new model ID. This page shows the exact edits in Python and Node.js, what carries over untouched, what you have to handle, and how to roll the switch out on a slice of traffic first.

What a provider switch actually involves

MiniMax models on YiduoChan are served behind an OpenAI-compatible API at https://yiduochan.com/v1, so moving an existing integration is a configuration change rather than a rewrite. The same client libraries work, the same messages array goes out, the same choices[0].message.content path comes back, and the same streaming loop keeps running. What moves is where requests are sent, which credential signs them, and which model string you ask for.

This page assumes you already have a working OpenAI-compatible integration in production and want to point part or all of it at MiniMax. If you are starting from an empty project instead, the OpenAI SDK guide for Python and Node.js is a better entry point, and the MiniMax overview covers the model line-up and the endpoints that are available.

The three things that change

SettingBeforeAfter
Base URLhttps://api.openai.com/v1https://yiduochan.com/v1
API keyYour existing provider keyA YiduoChan key created at /register, sent as Authorization: Bearer <your key>
Model IDYour current model stringMiniMax-M2.7, MiniMax-M2.7-highspeed or MiniMax-M3

Everything else in the call signature stays where it is. You do not need a new SDK, a new transport, a new retry policy or a new response parser. In most codebases these three values are already read from environment variables, which means the diff is limited to a deployment configuration file and possibly one constant.

Python: before and after

import os
from openai import OpenAI

# Before
client = OpenAI(
    base_url="https://api.openai.com/v1",
    api_key=os.environ["OPENAI_API_KEY"],
)
resp = client.chat.completions.create(
    model="your-current-model",
    messages=[{"role": "user", "content": "Summarise this changelog in three bullets."}],
)
print(resp.choices[0].message.content)
import os
from openai import OpenAI

# After
client = OpenAI(
    base_url="https://yiduochan.com/v1",
    api_key=os.environ["YIDUOCHAN_API_KEY"],
)
resp = client.chat.completions.create(
    model="MiniMax-M2.7",
    messages=[{"role": "user", "content": "Summarise this changelog in three bullets."}],
)
print(resp.choices[0].message.content)

Node.js: before and after

import OpenAI from "openai";

// Before
const client = new OpenAI({
  baseURL: "https://api.openai.com/v1",
  apiKey: process.env.OPENAI_API_KEY,
});
const resp = await client.chat.completions.create({
  model: "your-current-model",
  messages: [{ role: "user", content: "Summarise this changelog in three bullets." }],
});
console.log(resp.choices[0].message.content);
import OpenAI from "openai";

// After
const client = new OpenAI({
  baseURL: "https://yiduochan.com/v1",
  apiKey: process.env.YIDUOCHAN_API_KEY,
});
const resp = await client.chat.completions.create({
  model: "MiniMax-M2.7",
  messages: [{ role: "user", content: "Summarise this changelog in three bullets." }],
});
console.log(resp.choices[0].message.content);

One build, either provider

Hard-coding the new values works for a hobby script, but it makes the rollout all-or-nothing and the rollback a code change. The safer pattern is to promote all three settings to environment variables so a single artifact can run against either provider, selected at deploy time. Name them generically — the point is that the application no longer knows which vendor it is talking to.

# .env.previous
LLM_BASE_URL=https://api.openai.com/v1
LLM_API_KEY=<your existing provider key>
LLM_MODEL=your-current-model

# .env.yiduochan
LLM_BASE_URL=https://yiduochan.com/v1
LLM_API_KEY=<your YiduoChan key>
LLM_MODEL=MiniMax-M2.7
import os
from openai import OpenAI

client = OpenAI(
    base_url=os.environ.get("LLM_BASE_URL", "https://yiduochan.com/v1"),
    api_key=os.environ["LLM_API_KEY"],
)
MODEL = os.environ.get("LLM_MODEL", "MiniMax-M2.7")

resp = client.chat.completions.create(
    model=MODEL,
    messages=[{"role": "user", "content": "ping"}],
)
import OpenAI from "openai";

export const client = new OpenAI({
  baseURL: process.env.LLM_BASE_URL ?? "https://yiduochan.com/v1",
  apiKey: process.env.LLM_API_KEY,
});
export const MODEL = process.env.LLM_MODEL ?? "MiniMax-M2.7";

Keep the three variables together as a set. A common failure during migration is updating the base URL in one place and leaving the old key or the old model name behind, which produces an authentication failure or an unknown-model rejection that looks like a platform problem but is really a half-applied config.

If your stack sits behind a framework rather than the raw SDK, the same three settings are the only ones that move; see the notes on LiteLLM, LangChain and the Vercel AI SDK. Agent tooling that speaks the Anthropic protocol reads a different set of variables:

ANTHROPIC_BASE_URL=https://yiduochan.com
ANTHROPIC_AUTH_TOKEN=<your YiduoChan key>
ANTHROPIC_MODEL=MiniMax-M2.7
ANTHROPIC_DEFAULT_HAIKU_MODEL=MiniMax-M2.7-highspeed

That path is documented separately in the Claude Code setup guide.

What carries over unchanged

The following behaviours are the reason the switch is mechanical rather than architectural. They use the standard OpenAI-compatible shapes, so existing code paths do not need conditional branches for the new provider.

Chat completions

Request and response envelopes are the same: roles, multi-turn message arrays, system prompts, temperature, max_tokens, and the choices array on the way back. Your prompt templates, message builders and response parsers are untouched.

Streaming

stream = client.chat.completions.create(
    model="MiniMax-M2.7",
    messages=[{"role": "user", "content": "Explain the migration in three bullets."}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)

Tool and function calling

tools = [{
    "type": "function",
    "function": {
        "name": "get_invoice",
        "description": "Look up an invoice by id.",
        "parameters": {
            "type": "object",
            "properties": {"invoice_id": {"type": "string"}},
            "required": ["invoice_id"],
        },
    },
}]

resp = client.chat.completions.create(
    model="MiniMax-M2.7",
    messages=[{"role": "user", "content": "Fetch invoice INV-2031."}],
    tools=tools,
)
print(resp.choices[0].message.tool_calls)

JSON mode

resp = client.chat.completions.create(
    model="MiniMax-M2.7",
    messages=[
        {"role": "system", "content": "Reply with a single JSON object."},
        {"role": "user", "content": "Country and capital of France."},
    ],
    response_format={"type": "json_object"},
)

The speech endpoint

/v1/audio/speech keeps the same parameter names — model, input, voice and response_format (mp3, wav, flac or pcm, defaulting to mp3). The voice identifiers are the part that changes: use MiniMax voice IDs such as male-qn-qingse, female-shaonv or English_expressive_narrator. Provider-specific extras such as voice_setting.emotion and audio_setting.sample_rate travel in a metadata object.

audio = client.audio.speech.create(
    model="speech-2.8-turbo",
    input="The migration is complete.",
    voice="English_expressive_narrator",
    response_format="mp3",
)
with open("out.mp3", "wb") as f:
    f.write(audio.content)

No per-request character limit is published, so chunk long scripts on sentence or paragraph boundaries and concatenate the audio yourself. Details and per-character rates are on the speech-2.8 page.

What differs and has to be handled

Model IDs are case-sensitive

The accepted strings are exactly MiniMax-M2.7, MiniMax-M2.7-highspeed and MiniMax-M3. Use the IDs exactly as written, and copy them from /v1/models rather than typing them by hand. If your configuration normalises model names to lowercase somewhere in the pipeline — a common habit in routing layers and cache keys — remove that normalisation before you cut over, or the canary will fail on every request for a reason that has nothing to do with the model.

There is no free tier

Accounts are prepaid with no trial credits, so a key created seconds ago cannot serve traffic until the balance is funded. The minimum top-up is $5, with presets at $5, $10, $20, $50, $100, $200 and $500 or a custom amount; credits are valid for 12 months and failed requests are never charged. Fund the account as the first step of the migration, not after the canary starts throwing errors.

count_tokens is not available

The Anthropic-compatible endpoint does not expose count_tokens. If any part of your system calls it — a context-budget guard, a pre-flight cost estimate, a truncation helper — replace it with a local tokenizer estimate or with the usage figures returned on the response itself. Audit for this before the cutover; it tends to be buried in a utility module rather than in the request path you are actively testing.

Errors you will see during cutover

SymptomLikely causeFix
401 unauthorisedThe old provider key is still in the environment, the header is malformed, or the key was revokedSend Authorization: Bearer <your key> with a current YiduoChan key and confirm the deploy actually picked up the new secret
Insufficient balanceThe account has no credits, or the balance ran out mid-rolloutTop up at /pricing; the $5 minimum applies to the first payment as well
Unknown or rejected modelModel ID case or spelling does not matchCopy the exact ID from /v1/models

Verify before routing traffic

Two checks, in order. The first proves the key and the base URL are correct and shows the exact model IDs your account can call. The second proves an end-to-end completion works.

curl -s https://yiduochan.com/v1/models \
  -H "Authorization: Bearer $YIDUOCHAN_API_KEY"
curl https://yiduochan.com/v1/chat/completions \
  -H "Authorization: Bearer $YIDUOCHAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMax-M2.7",
    "messages": [{"role": "user", "content": "Reply with the word: ok"}]
  }'

A 401 on the first command means the credential or the header is wrong and there is no point continuing. A successful first command followed by a balance error on the second means the key is valid but the account is unfunded.

Migration checklist

  1. Create an account at /register and fund it with at least $5.
  2. Store the key in your secret manager under a new name; do not overwrite the existing provider secret yet.
  3. Promote base URL, key and model to environment variables if they are not already.
  4. Run the two verification commands above from the same network the application runs on.
  5. Grep the codebase for lowercase model normalisation and for count_tokens.
  6. Pick a starting model. MiniMax-M2.7 is the general default, MiniMax-M2.7-highspeed is the same model quality at lower latency, and MiniMax-M3 is for long-context work up to a hard 1,048,576-token ceiling. The model comparison lays out the trade-offs.
  7. Run your existing evaluation suite against the new model before any live traffic moves.
  8. Roll out on a percentage of traffic, then widen.

MiniMax-H3 video generation is temporarily unavailable on this platform, so it is not part of a migration plan today.

Canary rollout

Because the provider choice is now three environment variables, a canary is a routing decision rather than a branch in your business logic. Instantiate two clients and pick between them per request.

import os, random
from openai import OpenAI

primary = OpenAI(base_url=os.environ["LLM_BASE_URL"], api_key=os.environ["LLM_API_KEY"])
canary = OpenAI(base_url=os.environ["CANARY_BASE_URL"], api_key=os.environ["CANARY_API_KEY"])
CANARY_SHARE = float(os.environ.get("CANARY_PERCENT", "0")) / 100

def pick_client():
    if random.random() < CANARY_SHARE:
        return canary, os.environ["CANARY_MODEL"]
    return primary, os.environ["LLM_MODEL"]

client, model = pick_client()
resp = client.chat.completions.create(
    model=model,
    messages=[{"role": "user", "content": "ping"}],
)
StageTraffic on MiniMaxWatchAdvance when
1Internal onlyAuth, model IDs, tool-call parsingZero config errors over a full deploy cycle
21%Error rate, latency percentiles, balance burnMetrics indistinguishable from baseline for 24 hours
310%Output quality on real prompts, tool-call success rateEvaluation scores hold and no quality regressions are reported
450%Sustained throughput, credit burn against forecastSpend tracks the estimate and the error budget is intact
5100%Everything aboveKeep the old credential available for one more release

Set CANARY_PERCENT from your config system rather than baking it into the image, so widening or reverting the rollout does not require a rebuild.

Pricing context

These are list rates in USD per 1M tokens; billing is prepaid pay-as-you-go with no subscription.

ModelNotesInputOutputCache read
MiniMax-M2.7General default$0.30$1.20$0.06
MiniMax-M2.7-highspeedSame model quality, lower latency$0.60$2.40$0.06
MiniMax-M3, prompts up to 512K1,048,576-token context, a hard ceiling$0.30$1.20$0.06
MiniMax-M3, prompts above 512KPrompts above 512K up to 1M$0.60$2.40$0.12

Cache writes on the MiniMax-M2.7 models are $0.375 per 1M tokens. Speech is billed per input character: speech-2.8-hd at $100 per 1M characters and speech-2.8-turbo at $60 per 1M characters, broken down on the speech pricing page.

Every account currently sits in a group whose ratio is 0.95, so the amount actually billed is 5% below the list prices above. A canary that sends 10M input and 2M output tokens through MiniMax-M2.7 lists at 10 × $0.30 + 2 × $1.20 = $5.40 and bills at $5.13 with the current 5% group discount applied. Treat that as the group setting in force today rather than a permanent guarantee, and model your own volumes with the cost calculator.

Rollback

Because nothing about the provider is compiled into the application, reverting is a configuration operation. Set CANARY_PERCENT to 0 to drain the canary immediately, or point LLM_BASE_URL, LLM_API_KEY and LLM_MODEL back at the previous values and redeploy. No code change, no release branch, no data migration. Keep the old credential valid and funded until at least one full release cycle has run at 100% on the new provider — the cost of leaving a key in place for a week is nothing next to the cost of discovering it was deleted during an incident.

FAQ

Do I have to change my code to switch from OpenAI to MiniMax?

No. If you call the OpenAI SDK or /v1/chat/completions over raw HTTP, you change the base URL, the API key and the model ID, and the request and response shapes stay identical.

What is the OpenAI-compatible base URL for MiniMax on YiduoChan?

It is https://yiduochan.com/v1, authenticated with an Authorization: Bearer header carrying your key. Available endpoints include /v1/chat/completions, /v1/audio/speech and /v1/models.

Are the MiniMax model IDs case-sensitive?

Yes. Use MiniMax-M2.7, MiniMax-M2.7-highspeed or MiniMax-M3 exactly as written; lowercased variants are not aliases and are rejected.

Is there a free tier for testing the migration?

No. Accounts are prepaid with no free credits and no trial, so fund the balance with the $5 minimum top-up before your first request.

Do streaming, tool calling and JSON mode still work after the switch?

Yes, all three go through the standard OpenAI-compatible shapes, including stream=True for streaming. The Anthropic-compatible endpoint is the one exception worth auditing, because count_tokens is not available there.

How do I roll back if the canary looks wrong?

Set your canary percentage to 0, or point the base URL, key and model environment variables back at the previous provider and redeploy. No code change is involved.

Sign up and get an API key See full model pricing