How to migrate from the official APIs or another OpenAI-compatible service
Moving to Tutujin API usually means changing base_url, api_key and the model names. Check the model list, streaming, error codes and rate limits before launch, then shift traffic in stages and keep the old service until things are stable. A checklist, percentage-based routing code and a rollback plan.
Moving to Tutujin API usually changes three things in your code: base_url becomes https://www.tutujin.com/v1, api_key becomes a key you create in the console, and model becomes a name from this site's model hub. The real work is the checking before launch and the switch after it: confirm the models with the models endpoint, check the differences in streaming, error codes and rate limits, then move traffic over in stages — a small share, half, then all of it. Keep the old service until the full rollout is stable, and keep the share in configuration so a rollback is just setting it back to 0.
Check before you migrate
| What to confirm | How |
|---|---|
| The endpoints you use are supported | Compare with the table below; native APIs are available for the Claude and Gemini SDKs too |
| Your models exist and the names match | Call the models endpoint with the new key, as in step 2 |
| You know what it will cost | Look up prices in Models and pricing; estimating is covered in How LLM API pricing works |
| You have picked a group | The group decides the line and the price; see How to choose a group |
Supported endpoints:
| Endpoint | Method and path | Status | Models |
|---|---|---|---|
| Chat Completions | POST/v1/chat/completions | Verified | 681 |
| Responses | POST/v1/responses | Relayed as-is | 5 |
| Claude Messages | POST/v1/messages | Verified | 49 |
| Gemini generateContent | POST/v1beta/models/{model}:generateContent | Verified | 63 |
| Image generation | POST/v1/images/generations | Relayed as-is | 54 |
| Image editing | POST/v1/images/edits | Relayed as-is | 31 |
| Embeddings | POST/v1/embeddings | Verified | 18 |
| Rerank | POST/v1/rerank | Relayed as-is | 6 |
| Text to speech | POST/v1/audio/speech | Relayed as-is | 8 |
| Speech to text | POST/v1/audio/transcriptions | Relayed as-is | 1 |
| Midjourney | POST/mj/submit/imagine | Relayed as-is | 13 |
| Suno music | POST/suno/submit/music | Relayed as-is | 2 |
"Verified" means we have made real calls and checked billing on this site. "Relayed as-is" means requests are forwarded in their original format; parameters follow the model's official documentation. Model counts are the models in the current catalog that support the endpoint.
Step 1: create a key and change the base URL
Create a key just for the migration on the API keys page of the console, with a spending cap and a group. Then change your configuration for the way you call the API today:
| How you call the API now | What to change | New value |
|---|---|---|
| OpenAI SDK, or another OpenAI-compatible service | base_url / baseURL, or the OPENAI_BASE_URL environment variable | https://www.tutujin.com/v1 |
| Anthropic SDK | base_url / baseURL | https://www.tutujin.com |
| Google Gen AI SDK | base_url in http_options | https://www.tutujin.com |
| Plain HTTP requests | The full URL | For example https://www.tutujin.com/v1/chat/completions |
Replace api_key with your key from this site in every case. Projects that read environment variables can switch without touching code:
export OPENAI_BASE_URL="https://www.tutujin.com/v1"
export OPENAI_API_KEY="sk-your-key"The three header styles and full SDK setups are in Authentication and base URL.
Step 2: check the model names
Call the models endpoint with the new key; it returns exactly the models that key can use:
curl https://www.tutujin.com/v1/models \
-H "Authorization: Bearer $TUTUJIN_API_KEY"Match every model name your code uses against the response. The same model may be named differently on different platforms; this site's name is the one that works here, so map any mismatches in code. If a model is missing, the key's group most likely does not offer it — check the model's page for its groups, and see Models endpoint.
Step 3: check the behavior differences
The API format is compatible, but a few behaviors need handling the way this site works:
| Area | How it works here | What to do in code |
|---|---|---|
| Insufficient balance | 403 with the code insufficient_user_quota; OpenAI returns 429 | Handle 403 on its own: prompt a top-up, do not queue a retry |
| The group lacks the model | 503 with the code model_not_found | Do not retry forever; fix the model name or the group |
| Error body | code in the error object is often an empty string; message ends with a request id | Decide by HTTP status, log the full error response, and send the request id to support when you need help |
| Rate limits | 429; limits vary with the line and the load, and no fixed numbers are published | Back off and retry, cap concurrency; see Rate limits |
| Streaming | Streaming for Chat Completions has been tested on this site; an error after data has started shows up as the stream ending early without data: [DONE] | Treat a stream that never sent its end marker as failed |
| Parameter support | Non-OpenAI models differ in how they support response_format, tools, reasoning_effort and similar parameters | Unsupported parameters may be ignored or return 400; test with a few requests after switching models |
| Usage fields | usage in each response returns input and output tokens, plus cached tokens on a cache hit | Record usage from usage in your own system and reconcile it with the console |
The full status code table is in Errors and retries, and the billing fields are explained in Billing and usage.
Step 4: roll out in stages
Do not switch everything at once. Route by percentage and look at the data before each next stage:
| Stage | Share sent here | What to watch | Move on when |
|---|---|---|---|
| Offline check | No live traffic | Replay a set of real past requests and compare output quality, latency and cost | The results are acceptable |
| Small share | Around 5% | Error rate, latency, cost per request | It has run steadily for a while with no new kinds of errors |
| Half | 50% | The same, plus any 429s at peak hours | Peak hours are stable too |
| All | 100% | The same | Retire the old service once it is stable |
Pin each user or session to one side, so comparisons and debugging stay clear. Keep the share in an environment variable or a config service, so changing it or rolling back needs no code change:
import os
import zlib
from openai import OpenAI
tutujin = OpenAI(base_url="https://www.tutujin.com/v1", api_key=os.environ["TUTUJIN_API_KEY"])
legacy = OpenAI(base_url=os.environ["OLD_BASE_URL"], api_key=os.environ["OLD_API_KEY"])
# old service's model name -> this site's model name, e.g. names with a vendor prefix
MODEL_MAP = {"openai/gpt-4.1-mini": "gpt-5.4-mini"}
def rollout() -> float:
# 0 = everything to the old service, 1 = everything here; read on every call so changes apply at once
return float(os.environ.get("TUTUJIN_ROLLOUT", "0"))
def chat(user_id: str, model: str, messages: list):
bucket = zlib.crc32(user_id.encode()) % 100 # the same user always lands in the same bucket
if bucket < rollout() * 100:
return tutujin.chat.completions.create(model=MODEL_MAP.get(model, model), messages=messages)
return legacy.chat.completions.create(model=model, messages=messages)import OpenAI from 'openai'
import { crc32 } from 'node:zlib'
const tutujin = new OpenAI({ baseURL: 'https://www.tutujin.com/v1', apiKey: process.env.TUTUJIN_API_KEY })
const legacy = new OpenAI({ baseURL: process.env.OLD_BASE_URL, apiKey: process.env.OLD_API_KEY })
// old service's model name -> this site's model name, e.g. names with a vendor prefix
const MODEL_MAP = { 'openai/gpt-4.1-mini': 'gpt-5.4-mini' }
// 0 = everything to the old service, 1 = everything here; read on every call so changes apply at once
const rollout = () => Number(process.env.TUTUJIN_ROLLOUT ?? '0')
export function chat(userId, model, messages) {
const bucket = crc32(userId) % 100 // the same user always lands in the same bucket
if (bucket < rollout() * 100) {
return tutujin.chat.completions.create({ model: MODEL_MAP[model] ?? model, messages })
}
return legacy.chat.completions.create({ model, messages })
}The Python and Node.js versions put the same user in the same bucket, so mixed services stay consistent. At every stage, filter Logs in the console by the migration key's name to check request counts, errors and spend, and compare them with what you recorded from usage in your own system.
Rolling back
- If something goes wrong, set the share back to 0 first. All requests return to the old service at once, with no code change and no redeploy.
- After the full rollout, keep the old service's account and keys for a while until the new setup has proven stable.
- After a rollback, use the time, model and error details in Logs to find the cause; if you need our help, send the request id from the error to WeChat support at
JsnonoChat.
After the switch
- Give production keys a spending cap and an IP allowlist; see API key security best practices.
- For critical workloads, give the key several groups and turn on cross-group retry; see LLM API high availability.
- Export usage by key every month for reconciliation; see Cost allocation and reconciliation.
FAQ
How much code has to change?
For projects on the OpenAI SDK, usually just base_url, api_key and the model names. One difference needs its own handling, though. An insufficient balance returns 403 here, not the 429 OpenAI uses, so keep it out of your retry queue.
Can we run the old service and Tutujin API side by side during the migration?
Yes. During a staged rollout both run in parallel. Send a share of requests here and the rest to the old service, then raise the share once things are stable.
What if the model names differ from the old platform?
Use the names from this site's model hub and models endpoint, and keep a name mapping in your code that you apply when switching.
What are the rate limits here?
There are no fixed published numbers; limits vary with the line and the load. Design for backoff and a concurrency cap. If you need more throughput long term, contact WeChat support at JsnonoChat with the models, groups and request volume.
How do we roll back?
Set the rollout share back to 0 and every request goes to the old service again. Keep the share in an environment variable or a config service, so rolling back needs no code change and no redeploy.