Kimi K2 API Retirement: Migrate K2.7 Code to K3.1 Pro

Move K2.6 and K2.7 Code workloads to K3.1 Pro before Kimi's planned December retirement. Resolve account-cutoff wording, replace K2 parameters, preserve complete tool-call history, update OpenCode and Claude Code aliases, and reconcile cache writes with token usage. Includes a runnable API probe and regression checks.

Contents
Kimi K2 API Retirement: Migrate K2.7 Code to K3.1 Pro

Move kimi-k2.6 and kimi-k2.7-code workloads to kimi-k3.1-pro before Kimi’s planned retirement in late December 2026. Set reasoning effort and an output cap explicitly, then verify tool history and cache charges before switching production traffic. As of 2026-11-05, new-account restrictions are already active.

When does Kimi K2 stop working?

Eligible existing accounts retain access for now, but Kimi has not published an exact shutdown day or time in these notices.

The pricing notice says K2 became unavailable to newly registered users starting October 31, 2026. It plans retirement of all K2 models by the end of December 2026.

The current model list uses slightly different wording. It says users registered before November 1 can continue using kimi-k2.6, kimi-k2.7-code, and kimi-k2.7-code-highspeed. Their retirement is expected in late December 2026.

If you registered around that boundary, check your account’s actual access. The wording does not establish one precise cutoff instant. For new deployments, start with K3.1 Pro.

Do not assume every K2 access failure has the same error. The August changelog confirms 404 “model not found” for retired kimi-k2.5 and moonshot-v1 models. That statement does not specify the eventual shutdown response for K2.6 or K2.7 Code.

Change the request contract before switching traffic

Update the request body and conversation storage together. Kimi’s current parameter comparison documents differences that a model-name replacement can miss.

Settingkimi-k2.6kimi-k2.7-codekimi-k3.1-pro
ThinkingEnabled by default; can be disabledAlways enabledAlways enabled; remove the K2 thinking object
reasoning_effortUnsupportedUnsupportedTop-level low, high, or max; default max
temperatureFixed at 1.0 while thinking; 0.6 without thinkingFixed at 1.0Fixed at 1.0; omit it
Documented generation defaultmax_tokens: 32768max_tokens: 32768max_completion_tokens: 131072

K2.7 Code already required thinking and preserved reasoning. These behaviors are a larger change when moving from K2.6’s non-thinking mode.

The K3.1 Pro quickstart also fixes top_p=0.95, n=1, presence_penalty=0, and frequency_penalty=0. Omit these fields and temperature. Check wrapper defaults too: a client may insert a sampling value that your application never set.

Use max_completion_tokens for new Chat Completions requests. The API reference marks max_tokens deprecated; it does not say the field has been removed. K3.1 Pro accepts a configured maximum of 1048576, subject to available context space.

An output cap is a ceiling, not a promise that every request consumes that many tokens. Choose a cap for your task instead of inheriting the larger default.

For reasoning effort, start with low for connectivity checks and bounded lookups. Try high for normal coding work. Use max when your regression tasks justify the additional reasoning. low reduces effort; it does not disable thinking.

Run a bounded API and tool-call check

Verify the direct API before debugging an agent’s configuration.

Create a Global Kimi Open Platform API key. As of 2026-11-05, the access requirements require a successful top-up of at least $1. Use a key from the platform that matches https://api.moonshot.ai. For Kimi Code account errors, start with the separate Kimi Code limits guide.

In Bash, collect the key without displaying it:

read -rs MOONSHOT_API_KEY && export MOONSHOT_API_KEY

The following Bash command uses Python 3’s standard library. It sends two billable requests and executes a real local multiplication function. The 4096 generation cap is for this arithmetic probe, not a general coding budget.

python3 - <<'PY'
import json
import os
import urllib.error
import urllib.request

tools = [{
    "type": "function",
    "function": {
        "name": "multiply",
        "description": "Multiply two integers.",
        "parameters": {
            "type": "object",
            "properties": {
                "a": {"type": "integer"},
                "b": {"type": "integer"}
            },
            "required": ["a", "b"],
            "additionalProperties": False
        }
    }
}]

def chat(messages, choice):
    payload = {
        "model": "kimi-k3.1-pro",
        "messages": messages,
        "reasoning_effort": "low",
        "max_completion_tokens": 4096,
        "tools": tools,
        "tool_choice": choice
    }
    request = urllib.request.Request(
        "https://api.moonshot.ai/v1/chat/completions",
        data=json.dumps(payload).encode("utf-8"),
        headers={
            "Authorization": "Bearer " + os.environ["MOONSHOT_API_KEY"],
            "Content-Type": "application/json"
        },
        method="POST"
    )
    try:
        with urllib.request.urlopen(request, timeout=120) as response:
            data = json.load(response)
    except urllib.error.HTTPError as error:
        raise SystemExit(f"HTTP {error.code}: check access and request parameters.")
    if data.get("model") != "kimi-k3.1-pro":
        raise RuntimeError("Unexpected response model.")
    if data["choices"][0]["finish_reason"] == "length":
        raise RuntimeError("Generation limit reached; do not accept this result.")
    return data

messages = [{
    "role": "user",
    "content": "Use multiply to calculate 6 times 7, then report the result."
}]
first = chat(messages, "required")
assistant = first["choices"][0]["message"]
calls = assistant.get("tool_calls") or []
if not calls:
    raise RuntimeError("Expected a tool call.")
messages.append(assistant)

for call in calls:
    function = call["function"]
    arguments = json.loads(function["arguments"])
    if function["name"] != "multiply":
        raise RuntimeError("Unexpected tool.")
    if not isinstance(arguments, dict) or set(arguments) != {"a", "b"}:
        raise RuntimeError("Unexpected arguments.")
    if any(type(arguments[key]) is not int for key in ("a", "b")):
        raise RuntimeError("Expected integer arguments.")
    result = arguments["a"] * arguments["b"]
    messages.append({
        "role": "tool",
        "tool_call_id": call["id"],
        "content": json.dumps({"result": result})
    })

final = chat(messages, "none")
choice = final["choices"][0]
answer = choice["message"].get("content") or ""
if choice["finish_reason"] != "stop" or not answer.strip():
    raise RuntimeError("Expected a completed final answer.")
print(answer)
print(json.dumps({"first": first["usage"], "final": final["usage"]}))
PY

Expect a final answer containing 42, followed by usage objects for both requests. The probe rejects missing tool calls, an unexpected model, invalid arguments, truncated generation, and an incomplete final response.

The important operation is messages.append(assistant). It preserves the complete response message, including reasoning_content and tool_calls. Each tool result then uses its matching tool_call_id. Follow this history-preservation rule in your production conversation store.

The probe deliberately uses required and forbidden tool calls to test both stages. Normal agent loops can use auto when the model should decide whether another tool call is needed.

If the request fails, use the error reference to select the next check:

  • For 401, verify the key’s platform and endpoint, then repeat the same probe.
  • For 400 in your existing application, inspect its outgoing JSON for K2 thinking settings or fixed sampling overrides.
  • For 404, check both the model ID and account access. The code does not prove a spelling error.
  • For finish_reason="length", treat the answer as incomplete. Adjust the task, effort, or generation cap before accepting it.

BetterToken also lists kimi-k3.1-pro as of 2026-11-05. Its OpenAI-compatible API uses https://www.bettertoken.ai/v1 with your own BetterToken API key. Check its current prices and request contract separately; Moonshot’s cache rules do not establish gateway behavior.

Update OpenCode and Claude Code

Change every route that can select K2, including background tasks and subagents. A successful main conversation does not verify those routes.

OpenCode: select the Moonshot provider

Use the built-in Moonshot AI provider and refresh its model catalog. Its provider ID is moonshotai, and it reads MOONSHOT_API_KEY.

For the OpenCode v1.19.5 CLI contract, run these commands in the same Bash session:

opencode models moonshotai --refresh
opencode run --model moonshotai/kimi-k3.1-pro --variant high \
  "Inspect the current project and identify the test command. Do not edit files."

The refreshed list should contain moonshotai/kimi-k3.1-pro. The run should return a project-specific answer under that provider and model. If the model is absent, check the installed client version and catalog refresh before proceeding.

The CLI reference documents both flags. To persist the model, set "model": "moonshotai/kimi-k3.1-pro" in project opencode.json or global ~/.config/opencode/opencode.json. Follow the configuration precedence rules when both files exist.

The command sets high for that invocation. In an interactive session, select the effort with /variants before sending the first prompt. If you also upgrade the client, review OpenCode 2 permission changes before allowing repository edits.

Claude Code: update every model alias

Moonshot documents a Claude Code compatibility route. Anthropic’s gateway documentation says it does not support routing Claude Code to non-Claude models. Treat this as Moonshot’s adapter path and validate it with your installed client.

First inspect ~/.claude/settings.json and project settings. Reconcile stale endpoint, credential, model, and effort values in their env blocks. Moonshot warns that settings-file environment values can override terminal exports.

Its current example still maps Haiku to kimi-k2.7-code. The following Bash configuration proposes moving every listed model tier to the migration target:

unset ANTHROPIC_API_KEY
export ANTHROPIC_BASE_URL="https://api.moonshot.ai/anthropic"
export ANTHROPIC_AUTH_TOKEN="$MOONSHOT_API_KEY"
export ANTHROPIC_MODEL="kimi-k3.1-pro"
export ANTHROPIC_DEFAULT_OPUS_MODEL="$ANTHROPIC_MODEL"
export ANTHROPIC_DEFAULT_SONNET_MODEL="$ANTHROPIC_MODEL"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="$ANTHROPIC_MODEL"
export ANTHROPIC_DEFAULT_FABLE_MODEL="$ANTHROPIC_MODEL"
export CLAUDE_CODE_SUBAGENT_MODEL="$ANTHROPIC_MODEL"
export CLAUDE_CODE_EFFORT_LEVEL="high"

claude --model kimi-k3.1-pro

This reuses the exported key from the API probe. Inspect any remaining legacy K2 settings, including ANTHROPIC_SMALL_FAST_MODEL.

Run /status and check the base URL and model. Then request a simple answer and a task that reads a local file. Confirm the effective effort setting through the model configuration controls.

Claude Code uses the Messages protocol. Keep these Kimi Messages fields separate from Chat Completions:

PurposeChat CompletionsKimi Messages
Reasoning effortreasoning_effortoutput_config.effort
Generation capmax_completion_tokensRequired max_tokens
Cache lifetimeprompt_cache_options.ttlTop-level cache_control.ttl

Do not rename the Messages max_tokens field because the Chat Completions version is deprecated.

Verify cache costs without counting tokens twice

Split input into cache reads, cache writes, and uncached input. K3.1 Pro bills cache writes separately. Include those charges in your cost calculation.

For Chat Completions, read:

  • Total input: usage.prompt_tokens.
  • Cache reads: usage.prompt_tokens_details.cached_tokens.
  • Cache writes: usage.prompt_tokens_details.cache_write_tokens.
  • Total output: usage.completion_tokens.

The API accounting contract makes the three input categories mutually exclusive. Output already includes reasoning and answer tokens.

Use rates in one currency per 1,000,000 tokens:

uncached = prompt_tokens - cached_tokens - cache_write_tokens

token_cost = (
    uncached * miss_rate
    + cached_tokens * hit_rate
    + write_5m * write_5m_rate
    + write_1h * write_1h_rate
    + completion_tokens * output_rate
) / 1_000_000

Here, write_5m + write_1h must equal cache_write_tokens. Obtain rates from the provider’s current pricing. This estimates token charges; reconcile it against the bill.

Kimi automatically uses 5m caching when you omit cache options. For requests separated by longer gaps, evaluate "prompt_cache_options": {"mode": "implicit", "ttl": "1h"}.

The cache guide specifies that an existing entry retains its original lifetime. Sending 1h does not upgrade an active 5m entry. Capture Msh-Usage-Cache-Write-Tokens-5m and Msh-Usage-Cache-Write-Tokens-1h response headers to identify actual write quantities.

Test a representative, unchanged prefix across repeated requests. The arithmetic probe may be too small to demonstrate useful caching because partial blocks remain uncached. Keep reasoning effort constant during the test: changing it invalidates prefix-cache hits.

For streaming applications, also set stream_options.include_usage=true. Follow the streaming guide to assemble reasoning, answer, and tool-call fragments. Read the final usage chunk even when its choices array is empty.

Set regression checks before the cutover

Move a small traffic slice only after the new route passes your existing acceptance tests.

Use the same repository revision, prompts, tool definitions, and test commands for each comparison. Run low, high, and max in separate sessions. Record completed-task quality, full response latency, input categories, total output, retries, and billed cost.

Include these failure-sensitive cases:

  • A multi-turn task that depends on an earlier assistant response.
  • A tool call followed by another model response.
  • A streamed tool call with fragmented JSON arguments.
  • A task near your configured generation cap.
  • Background or subagent work that could retain a K2 alias.

Choose the lowest effort that meets your quality and latency requirements. If high fails an important task that max completes reliably in your checks, keep max for that task class.

Keep a K2 rollback only while the account still has access. Before the retirement window, test a fallback that does not depend on K2 and remove stale K2 IDs from scheduled jobs, retry routes, and agent configuration.

Ready to optimize your LLM workflow?

Join thousands of developers building faster, smarter, and more cost-effective AI applications with BetterToken.

Get Started for Free