
Kimi K3 API: Access, Rate Limits & SDKs 2026
Kimi K3 answers in the OpenAI chat format, so most teams reach it with a base-URL swap and a key. What decides the bill is automatic prompt caching, always-on reasoning, and rate tiers shared across every Kimi model.
Kimi K3 API verdict
Kimi K3 speaks the OpenAI chat-completions format, so if you already call OpenAI or a compatible model.
Moving over is mostly pointing the SDK at Moonshot and setting a key. Everything runs through one chat call: tool use, JSON-schema output, streaming, prefill, and native image and video input all ride on the same request.
Treat adoption as a config change, not a rewrite. Point the OpenAI SDK at Moonshot, set your key, and keep the model id as kimi-k3. After that, three habits keep the bill sane. Put the long, unchanging part of your prompt first so automatic caching can turn repeat input into the cache-hit rate of $0.30 per 1M tokens. Set the completion-length cap yourself instead of leaning on the high default, because the rate limits count that cap even when the model writes less. And send bulk, non-urgent jobs to a cheaper Kimi model, since K3 has no Batch discount at launch.
- Reasoning is always on and the effort level only accepts max at launch, so you cannot turn thinking down to save output tokens. Artificial Analysis clocked 130M output tokens against a 63M average, and output runs $15.00 per 1M.
- The completion-length cap defaults to 131,072 tokens and can go to 1,048,576. Rate limits count your request tokens plus that cap, not the tokens actually generated, so a high cap quietly eats your per-minute budget.
- The 60% Batch discount does not cover K3 at launch. It applies to kimi-k2.7-code, kimi-k2.6 and kimi-k2.5, so bulk work on the flagship pays full rate.
- Rate limits are set per account and shared across every Kimi model. A burst of kimi-k2.6 traffic spends the same requests-per-minute budget as K3.
- Compatibility
- OpenAI chat format
- Cache-hit input
- $0.30 / 1M (-90%)
- Context window
- 1M tokens
- Rate tiers
- 6, recharge-gated
- Batch on K3
- Not yet
Kimi K3 API calls, task by task
Point the OpenAI SDK at Moonshot's endpoint, pass your key, and ask kimi-k3 one question. The reply arrives in the familiar chat-completions shape; check usage.cached_tokens on your second call to watch the automatic cache kick in.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["MOONSHOT_API_KEY"],
base_url="https://api.moonshot.ai/v1",
)
response = client.chat.completions.create(
model="kimi-k3",
messages=[
{"role": "user", "content": "Explain optimistic versus pessimistic locking in three sentences."},
],
max_completion_tokens=2048,
)
print(response.choices[0].message.content){
"id": "cmpl-...",
"object": "chat.completion",
"model": "kimi-k3",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "..."},
"finish_reason": "stop"
}],
"usage": {"prompt_tokens": 21, "completion_tokens": 64, "total_tokens": 85, "cached_tokens": 0}
}- Tier-1 limits (kimi-k3 (Tier 1, $10 recharge; shared across all models)): 200 RPM, 2,000,000 TPM, 50 concurrent.
- Cost levers: Automatic prefix caching: cache-hit input $0.30 vs $3.00 per 1M (-90%); Estimate-Tokens API to size cost before sending; Route batchable non-frontier work to kimi-k2.6 or kimi-k2.5 for the 60% Batch discount (K3 is not batchable yet).
- Handle: 400 (bad request), 401 (auth failure), 429 (rate limit), 500 (server error), 504 (request over 2h timeout).
Every call above is lifted from the official Moonshot documentation and runs as written. The walkthrough sections below take the same mechanics apart line by line.
Access and OpenAI compatibility
Access is deliberately boring, which is the point. Kimi runs an OpenAI-compatible HTTP API, so the official OpenAI Python and Node SDKs, along with tools like LangChain, Dify and Coze, work once you change two settings: the base URL and the key. If you are wiring this up for the first time, the exact base URL, header, model id and SDK versions are in the reference below. The one choice that is not obvious is which platform you sign up on, because the global and China platforms bill in different currencies at independently set rates.
Connection reference
| Aspect | Detail | Notes |
|---|---|---|
| Base URL | https://api.moonshot.ai/v1 | Direct HTTP, or the SDK base_url |
| Auth | Authorization: Bearer $MOONSHOT_API_KEY | Single bearer scheme; keys from the console |
| Compatibility | OpenAI Chat Completions | OpenAI Python/Node SDKs, LangChain, Dify, Coze |
| Model id | kimi-k3 | GET /v1/models lists the capability flags |
| SDK versions | Python 3.8+, Node 18+, openai 1.0+ | pip install --upgrade 'openai>=1.0' |
| Global vs China | USD platform (Singapore) or RMB platform (China) | Independently set rates; do not convert |
Who needs this: anyone doing the first integration. Copy the base URL and header into your SDK config, keep the model id as kimi-k3, and pick the platform that matches your billing currency.
With the base URL and key set, the next move is to fire one real request and read what comes back.
The first request, and what each line does
Time for one real request. Pick a language below and send the same chat call; each tab prints the reply and the cached-token count. By the end you have a working round trip and know where to read the answer.
The raw HTTP call: one POST with a bearer key, the model id kimi-k3, a messages array, and a cap on how long the reply may run. Handy for a smoke test or a language the SDK does not cover.
curl https://api.moonshot.ai/v1/chat/completions \
-H "Authorization: Bearer $MOONSHOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kimi-k3",
"messages": [{"role": "user", "content": "Summarize the architecture of this repository."}],
"max_completion_tokens": 4096
}'The same call through the official OpenAI SDK. Point base_url at Moonshot and read the key from the environment. The reply text is in choices[0].message.content, and usage.cached_tokens reports how much input was served from cache.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["MOONSHOT_API_KEY"],
base_url="https://api.moonshot.ai/v1",
)
response = client.chat.completions.create(
model="kimi-k3",
messages=[
{"role": "user", "content": "Summarize the architecture of this repository."},
],
max_completion_tokens=4096,
)
print(response.choices[0].message.content)
print(response.usage.cached_tokens)The Node SDK version: identical request, async/await. Node 18 or newer and openai 1.0 or newer are all you need.
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.MOONSHOT_API_KEY,
baseURL: "https://api.moonshot.ai/v1",
});
const response = await client.chat.completions.create({
model: "kimi-k3",
messages: [
{ role: "user", content: "Summarize the architecture of this repository." },
],
max_completion_tokens: 4096,
});
console.log(response.choices[0].message.content);What comes back, and the defaults worth setting yourself
- The reply is standard OpenAI shape: choices[0].message.content holds the text, and finish_reason is stop on a normal finish, tool_calls when the model wants a function, or length when it hit the token cap.
- usage.cached_tokens is how a cache hit surfaces to you: the count of input tokens served from the automatic prefix cache and billed at $0.30 per 1M instead of $3.00.
- max_completion_tokens defaults to 131,072 and can be set up to 1,048,576. Set it to what you actually need: rate limits count request tokens plus this number, so an unset default can burn per-minute budget on tokens you never generate.
- Reasoning is always on. K3 reads the top-level reasoning_effort, which only accepts max for now, so do not send the K2-style thinking parameter.
- The sampling knobs are fixed on K3: temperature 1.0, top_p 0.95, n 1, presence_penalty 0 and frequency_penalty 0. Leave them out of the request rather than trying to override them.
- The API keeps no server-side conversation state. Each turn you resend the whole history, and for multi-turn or tool calls you return the assistant message unchanged, including its reasoning_content.
The endpoint surface
- The chat completions endpoint (POST) is the one call that does the work: chat, tools, JSON output, streaming, prefill and vision all travel through it.
- The models endpoint (GET) lists every model with capability flags: context_length, supports_image_in, supports_video_in, supports_reasoning and owned_by. Query it to confirm access before a call, since a 404 means no such model or no access.
- The token-estimate endpoint (POST) sizes a request before you send it, which matters for vision, where image and video token counts are dynamic.
- The balance endpoint (GET) reads remaining credit; the files endpoints upload, list, fetch and delete files used for vision and batch jobs.
- Errors come back as JSON with error.type and error.message. Expect 400 for a bad request, 401 for a bad key, 429 at a rate limit, 500 on a server fault, and 504 if a single request runs past about two hours.
One endpoint carries the work and you can read its output. The first thing worth tuning is the bill, so start with caching.
How automatic caching works, and how to earn a hit
Repeated input is where a K3 bill grows without anyone noticing. This step covers what earns a cache hit and how the discount is priced. Get the prompt structure right and the fixed part of every call bills at a tenth of the normal rate.
- Prompt caching is fully automatic. There is no cache id to create, no TTL to manage, and no separate storage or per-minute fee; every request gets it without asking.
- A hit is billed as the gap between two input rates: cache-miss input is $3.00 per 1M tokens, cache-hit input is $0.30 per 1M, a 90% discount.
- To earn hits, keep the long, fixed part of the prompt (a system prompt, a handbook, a schema) at the very start of messages and identical between calls, so later requests match and reuse that prefix.
- The hit surfaces in the response as usage.cached_tokens, so you can measure your real hit rate. OpenRouter's launch-week traffic averaged a 93% hit rate, which pulled effective input to about $0.488 per 1M.
- Setting response_format, or loading tools dynamically, does not invalidate the prefix cache, so structured output and large tool inventories still benefit.
Structuring a prompt so it hits the cache
Reuse a long system prefix across questions. The first call has nothing to reuse (cached_tokens is 0); the second, with the same prefix first, bills those shared tokens at the cache-hit rate of $0.30 per 1M.
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["MOONSHOT_API_KEY"], base_url="https://api.moonshot.ai/v1")
# A long, unchanging block: a handbook, a schema, a system prompt. Load it once.
HANDBOOK = open("handbook.md").read()
def ask(question: str):
return client.chat.completions.create(
model="kimi-k3",
messages=[
{"role": "system", "content": HANDBOOK}, # first and byte-for-byte identical
{"role": "user", "content": question},
],
max_completion_tokens=2048,
)
cold = ask("What does chapter 3 cover?")
print(cold.usage.cached_tokens) # 0: nothing to reuse yet
warm = ask("And chapter 4?")
print(warm.usage.cached_tokens) # shared prefix now bills at $0.30 / 1M, not $3.00The Node version of the same pattern: load the fixed context once, keep it first in messages, and read cached_tokens off usage to confirm the hit.
import OpenAI from "openai";
import { readFileSync } from "node:fs";
const client = new OpenAI({ apiKey: process.env.MOONSHOT_API_KEY, baseURL: "https://api.moonshot.ai/v1" });
const HANDBOOK = readFileSync("handbook.md", "utf8"); // long, unchanging context
async function ask(question: string) {
return client.chat.completions.create({
model: "kimi-k3",
messages: [
{ role: "system", content: HANDBOOK }, // first and identical on every call
{ role: "user", content: question },
],
max_completion_tokens: 2048,
});
}
const cold = await ask("What does chapter 3 cover?");
console.log(cold.usage?.cached_tokens); // 0 on the cold call
const warm = await ask("And chapter 4?");
console.log(warm.usage?.cached_tokens); // shared prefix now bills at the cache-hit rateOver raw HTTP there is nothing to configure. Send the fixed context in the system message and keep it identical on the next call; the shared prefix bills at $0.30 per 1M.
# Put the long, fixed context in the system message and keep it identical between calls.
# The next call that reuses this prefix bills the shared tokens at $0.30 / 1M.
curl https://api.moonshot.ai/v1/chat/completions \
-H "Authorization: Bearer $MOONSHOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kimi-k3",
"messages": [
{"role": "system", "content": "<long, unchanging handbook text>"},
{"role": "user", "content": "What does chapter 3 cover?"}
],
"max_completion_tokens": 2048
}'A cheap, repeatable call is a solid base. Next, let the model reach past its own text and run your functions.
Tool use: schemas, strict mode, and the K3-only required
A tool lets the model run your code instead of guessing an answer. Here you define one function, hand it over, and finish the round trip when the model asks to use it. Do it once and you have a full tool call, from the model's request to the final reply.
- A tool is a function definition: a name, a description, and a JSON Schema for its parameters. The name must match the pattern ^[a-zA-Z_][a-zA-Z0-9-_]{2,63}$, and a plain English name gets picked more reliably.
- strict defaults to true, which constrains the model's arguments to your schema. That schema must follow MFJS, Moonshot's JSON Schema dialect, and the walle CLI checks it before you ship. Pass strict false only when you want valid JSON without structural guarantees.
- tool_choice is auto, none or required. required forces a call and is K3-only: kimi-k2.6 and kimi-k2.7-code error if you send it.
- You can register up to 128 functions in one request. For a large inventory, load tools dynamically and lean on tool_choice; neither hurts the prefix cache.
- The round-trip is standard: the model returns finish_reason tool_calls with an id and stringified arguments, you run the function, then append a role:tool message whose tool_call_id matches, and call again.
Calling a function, end to end
Define one tool, force a call with tool_choice required, read the arguments off tool_calls, run the function, then hand the result back as a role:tool message and call again for the final answer.
import json
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Look up the current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
"additionalProperties": False,
},
# strict defaults to true: arguments are constrained to this schema (MFJS)
},
}]
messages = [{"role": "user", "content": "What is the weather in Beijing right now?"}]
first = client.chat.completions.create(
model="kimi-k3",
messages=messages,
tools=tools,
tool_choice="required", # force a call; only K3 accepts "required"
max_completion_tokens=1024,
)
call = first.choices[0].message.tool_calls[0]
args = json.loads(call.function.arguments)
# run get_weather(**args) yourself, then hand the result back:
messages.append(first.choices[0].message) # return the assistant turn unchanged
messages.append({
"role": "tool",
"tool_call_id": call.id, # must match the call
"content": json.dumps({"tempC": 29, "sky": "clear"}),
})
final = client.chat.completions.create(model="kimi-k3", messages=messages, tools=tools, max_completion_tokens=1024)
print(final.choices[0].message.content)The same round-trip in TypeScript. Note tool_choice required, which only K3 accepts, and that the assistant turn is pushed back onto messages unchanged before the tool result.
const tools = [{
type: "function" as const,
function: {
name: "get_weather",
description: "Look up the current weather for a city.",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
additionalProperties: false,
},
},
}];
const messages: any[] = [{ role: "user", content: "What is the weather in Beijing right now?" }];
const first = await client.chat.completions.create({
model: "kimi-k3",
messages,
tools,
tool_choice: "required", // only K3 accepts "required"
max_completion_tokens: 1024,
});
const call = first.choices[0].message.tool_calls![0];
const args = JSON.parse(call.function.arguments);
// run getWeather(args) yourself, then return the result:
messages.push(first.choices[0].message);
messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify({ tempC: 29, sky: "clear" }) });
const final = await client.chat.completions.create({ model: "kimi-k3", messages, tools, max_completion_tokens: 1024 });
console.log(final.choices[0].message.content);The opening request over HTTP. The reply carries finish_reason tool_calls; you then POST a follow-up that adds a role:tool message with your function's result.
# The reply comes back with finish_reason "tool_calls" and the arguments to run.
# You execute the function, then POST a follow-up that adds a {"role":"tool",...} message.
curl https://api.moonshot.ai/v1/chat/completions \
-H "Authorization: Bearer $MOONSHOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kimi-k3",
"messages": [{"role": "user", "content": "What is the weather in Beijing right now?"}],
"tool_choice": "required",
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Look up the current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}],
"max_completion_tokens": 1024
}'The model can call your code. It can also read what you show it, so the next step feeds it an image.
Vision input: base64 or an uploaded file, never a URL
K3 reads images and video inside the same chat call, with no separate endpoint to learn. This step covers how to attach a picture and the two rules that trip people up. Attach one image and you get an answer grounded in what it shows.
- K3 reads images and video natively. Images come in two ways: base64 inlined as an image_url part, or a file you upload first and reference with an ms:// file id. Public image URLs are rejected.
- message.content has to be an array of parts, for example an image_url part plus a text part. Serialize it into a plain string and it will not be treated as visual input.
- Images may be png, jpeg, webp or gif; video may be mp4, mov, webm and a few others. Keep images at or below 4K and video at or below 1080p, since higher only costs processing time.
- There is no cap on image count, but the whole request body must stay under 100 MB. For large or reused media, upload through the files endpoint instead of inlining.
- Vision is billed as input tokens on the same model as moonshot-v1, with no separate per-image fee. Counts are dynamic (resolution and, for video, key frames drive them), so size a request with the token-estimate call first.
Sending an image inline
Read a PNG, base64-encode it, and send it as an image_url part alongside a text part. The content is a list, not a string, which is what makes it a vision request. No cURL tab for this one: a base64 payload inside a shell body is unreadable, so the walkthrough sticks to the SDKs.
import base64
with open("chart.png", "rb") as f:
b64 = base64.b64encode(f.read()).decode()
response = client.chat.completions.create(
model="kimi-k3",
messages=[{
"role": "user",
"content": [ # content MUST be a list of parts, not a string
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
{"type": "text", "text": "What trend does this chart show?"},
],
}],
max_completion_tokens=1024,
)
print(response.choices[0].message.content)The Node version: base64 the file, then pass the array of parts. Same rule about content being an array of parts rather than a string.
import { readFileSync } from "node:fs";
const b64 = readFileSync("chart.png").toString("base64");
const response = await client.chat.completions.create({
model: "kimi-k3",
messages: [{
role: "user",
content: [ // must be an array of parts, not a string
{ type: "image_url", image_url: { url: `data:image/png;base64,${b64}` } },
{ type: "text", text: "What trend does this chart show?" },
],
}],
max_completion_tokens: 1024,
});
console.log(response.choices[0].message.content);Prose is fine for a person to read. When another system consumes the reply, you want a fixed shape instead.
Structured output: JSON mode versus a strict schema
When another program has to parse the reply, you want a guaranteed shape, not prose. This step sets loose JSON against a strict schema the model cannot break. The reply then comes back matching your field names and types every time.
- response_format has two modes. json_object guarantees valid JSON but leaves the fields up to the model; json_schema pins the field names, types and nesting to a schema you supply.
- json_schema is enforced by token-level constrained decoding: the sampler drops any token that would break the schema, so with strict true and additionalProperties false you get exactly the shape you asked for.
- The schema must comply with MFJS; validate it with the walle CLI first. K3 reliably handles nested objects, arrays and anyOf; kimi-k2.7-code is the most robust for oneOf, $ref and additionalProperties; kimi-k2.6 is shaky on complex schemas.
- For fields that might be missing, use a nullable union such as string or null, so the model returns null instead of inventing a value or dropping the key.
- Parse choices[0].message.content, never the whole response object, because thinking models also return reasoning_content. A truncated object comes back as finish_reason length; raise the token cap or simplify the schema. Setting response_format does not invalidate the cache.
Getting a schema-locked JSON reply
Force a strict JSON object with json_schema. The nullable email union tells the model to return null when the field is absent rather than guessing, and only content is parsed.
import json
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": ["string", "null"]}, # nullable union: returns null, not a guess
},
"required": ["name", "email"],
"additionalProperties": False,
}
response = client.chat.completions.create(
model="kimi-k3",
messages=[{"role": "user", "content": "Pull the name and email from: Dana Ko, [email protected]"}],
response_format={
"type": "json_schema",
"json_schema": {"name": "contact", "strict": True, "schema": schema},
},
max_completion_tokens=512,
)
data = json.loads(response.choices[0].message.content) # parse .content only, never the whole object
print(data["name"], data["email"])The TypeScript version. strict true plus additionalProperties false locks the shape; the sampler is constrained at decode time, so the reply parses cleanly.
const schema = {
type: "object",
properties: {
name: { type: "string" },
email: { type: ["string", "null"] }, // nullable union: returns null instead of a guess
},
required: ["name", "email"],
additionalProperties: false,
};
const response = await client.chat.completions.create({
model: "kimi-k3",
messages: [{ role: "user", content: "Pull the name and email from: Dana Ko, [email protected]" }],
response_format: {
type: "json_schema",
json_schema: { name: "contact", strict: true, schema },
},
max_completion_tokens: 512,
});
const data = JSON.parse(response.choices[0].message.content!); // parse .content only
console.log(data.name, data.email);The raw request. The json_schema block travels inside response_format; the model can only emit tokens that keep the output valid against it.
curl https://api.moonshot.ai/v1/chat/completions \
-H "Authorization: Bearer $MOONSHOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kimi-k3",
"messages": [{"role": "user", "content": "Pull the name and email from: Dana Ko, [email protected]"}],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "contact",
"strict": true,
"schema": {
"type": "object",
"properties": {"name": {"type": "string"}, "email": {"type": ["string", "null"]}},
"required": ["name", "email"],
"additionalProperties": false
}
}
},
"max_completion_tokens": 512
}'That settles what comes back. The next lever is when it arrives, so turn on streaming.
Streaming, usage on the wire, and prefill
Streaming sends the answer as it is written, which cuts the wait for the first token. This step shows how to read the stream and still recover the token totals at the end. You print the reply as it lands and still read what the call cost.
- Set stream true to get Server-Sent Events: the reply arrives as data: lines where message is replaced by delta, and the stream ends with data: [DONE]. You read text from choices[0].delta.content.
- Add stream_options include_usage to get a final usage chunk before [DONE] carrying the token totals, including cached_tokens. If the stream is cut short, that final chunk may not arrive.
- Kimi also attaches usage to the end block of each choice, so you can read per-choice totals; across choices only completion and total tokens differ, not the prompt count.
- Streaming pairs with Partial Mode, or prefill: append a trailing assistant message with partial true to force the reply to continue from a prefix such as an opening brace or a code fence. Do not combine prefill with json_object; use structured output for locked JSON.
Streaming a reply token by token
Iterate the stream, print each delta as it lands, and read the final usage chunk, which is the only one that carries cached_tokens. include_usage is what makes that last chunk appear.
stream = client.chat.completions.create(
model="kimi-k3",
messages=[{"role": "user", "content": "Explain B-trees in two short paragraphs."}],
max_completion_tokens=1024,
stream=True,
stream_options={"include_usage": True}, # adds a final usage chunk with cached_tokens
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.usage: # only the final chunk carries usage
print("\ncached:", chunk.usage.cached_tokens)The async-iterator version. Same idea: write deltas as they arrive, then pick up cached_tokens from the trailing usage chunk.
const stream = await client.chat.completions.create({
model: "kimi-k3",
messages: [{ role: "user", content: "Explain B-trees in two short paragraphs." }],
max_completion_tokens: 1024,
stream: true,
stream_options: { include_usage: true }, // final chunk carries usage + cached_tokens
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) process.stdout.write(delta);
if (chunk.usage) console.log("\ncached:", chunk.usage.cached_tokens);
}Over HTTP, stream true turns on SSE and -N keeps the socket open. You will see data: lines and a closing data: [DONE].
# -N keeps the connection open; the server sends "data:" lines and ends with "data: [DONE]".
curl -N https://api.moonshot.ai/v1/chat/completions \
-H "Authorization: Bearer $MOONSHOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kimi-k3",
"messages": [{"role": "user", "content": "Explain B-trees in two short paragraphs."}],
"max_completion_tokens": 1024,
"stream": true,
"stream_options": {"include_usage": true}
}'You have now touched the whole request surface. If you are arriving from OpenAI, a few specifics differ, so line them up before the switch.
Coming from OpenAI: what actually differs
Most of this already runs on the OpenAI SDK, so the move is small. This step lists the handful of real differences to check before you point the base URL at Moonshot. Check them and the switch is two lines, with no surprises after.
- Migration from OpenAI is a base-URL and key swap on the same SDK; the compatible surface covers the chat and files endpoints.
- temperature runs on a 0 to 1 scale here, against 0 to 2 on OpenAI. Setting temperature 0 with n greater than 1 is an error, and near 0 the API returns a single choice.
- The deprecated functions and function_call fields are not supported. Use tools and tool_calls, which also do parallel calls.
- Three Kimi-only extensions have no OpenAI equivalent: the K2-style thinking parameter (passed through the SDK's extra_body), the assistant-message partial flag for prefill, and K3's top-level reasoning_effort, which maps to OpenAI's own field but only accepts max.
The last thing to plan for is scale, because the rate tiers decide how hard you can push on day one.
How the rate tiers work
Rate limits are worth reading before you launch, because they are not per-key and they are not per-model. Every limit sits on the account and is shared across every Kimi model you call, and the tier you land in is bought, not requested: it rises with your cumulative recharge. The ladder below lays out all six tiers, so you can see where a launch-day spike needs a bigger deposit set aside in advance.
Rate-limit tiers, Tier 0 to Tier 5
| Tier (cumulative recharge) | Concurrency / RPM / TPM / TPD | Notes |
|---|---|---|
| Tier 0 ($1) | 1 / 3 / 500K / 1.5M | Throttled floor; $1 minimum to start |
| Tier 1 ($10) | 50 / 200 / 2M / Unlimited | First real working tier |
| Tier 2 ($20) | 100 / 500 / 3M / Unlimited | — |
| Tier 3 ($100) | 200 / 5,000 / 3M / Unlimited | — |
| Tier 4 ($1,000) | 400 / 5,000 / 4M / Unlimited | — |
| Tier 5 ($3,000) | 1,000 / 10,000 / 5M / Unlimited | Higher ceilings on request |
Set per account and shared across every model. Limits count request tokens plus your completion-length cap, not the tokens actually generated, and overflow returns a 429. RPM is requests per minute; TPM tokens per minute; TPD tokens per day.
Limits sit on the account and cover every Kimi model, which is the cue to pick the right one for each job.
Which Kimi model for which job
Kimi K3 is the flagship, but it shares an account and an API with the rest of the family, and picking the right model per job is the cheapest optimization there is. K3 is the only 1M-context model and the only one that accepts tool_choice required. The K2.7 Code line is built for programming agents and has the steadiest structured output. K2.6 is the vendor's cost-saver, and K2.5 with the old moonshot-v1 series are on their way out. The table sets the capabilities and rates side by side so you can route work down when the flagship is overkill.
The full Kimi model lineup
| Model | Context | Input / output (per 1M) | Best for / status |
|---|---|---|---|
| kimi-k3 | 1,048,576 | $3.00 miss / $0.30 hit in, $15.00 out | Flagship. Text plus image and video in, reasoning always on, tool_choice required, reliable structured output. Not on Batch. |
| kimi-k2.7-code | 262,144 | $0.95 miss / $0.19 hit in, $4.00 out | Dedicated coding, image and video in, thinking always on. Most stable structured output. Highspeed variant priced 2x. |
| kimi-k2.6 | 262,144 | $0.95 miss / $0.16 hit in, $4.00 out | General multimodal, thinking optional. Vendor's cost-saver; shaky on complex schemas; no required tool_choice. |
| kimi-k2.5 | 262,144 | $0.60 miss / $0.10 hit in, $3.00 out | Open general model, image in only. Blocked for new users; full sunset expected around August 31, 2026. |
| moonshot-v1 (8k/32k/128k) | 8K to 128K | $0.20 to $2.00 in, $2.00 to $5.00 out | Legacy series, differ only by context; vision-preview variants read images. Sunsetting. |
Global USD, per 1M tokens. The Batch API runs at 60% of these rates but covers only kimi-k2.7-code, kimi-k2.6 and kimi-k2.5, not K3. The China platform bills the same models in RMB at independently set rates.
Kimi K3 API FAQ
How hard is it to move an OpenAI integration to Kimi K3?
For most code it is a base-URL and key change. Kimi speaks the OpenAI chat-completions format, so the official OpenAI Python and Node SDKs work once you point them at Moonshot and set your key, with the model id kimi-k3. A few things differ: temperature runs on a 0 to 1 scale rather than 0 to 2, the old functions and function_call fields are gone in favour of tools, and K3 fixes its sampling settings so you leave temperature, top_p and n out of the request.
What does a Kimi K3 call cost, and how much does caching save?
Input is $3.00 per 1M tokens and output is $15.00 per 1M. Prompt caching is automatic, with no cache id, TTL or storage fee, and a cache hit drops input to $0.30 per 1M, a 90% cut. You earn hits by keeping the long, fixed part of the prompt at the start so later requests match it; the hit shows up in the response as cached_tokens.
What are Kimi K3's rate limits?
They are recharge-gated tiers from 0 to 5, set per account and shared across every Kimi model. Tier 0 ($1 recharge) is a throttle at 1 concurrent request, 3 per minute and 1.5M tokens a day. Tier 1 ($10) opens it to 50 concurrent, 200 per minute and 2M tokens per minute with no daily cap, and Tier 5 ($3,000) reaches 1,000 concurrent and 10,000 per minute. Limits count request tokens plus your completion-length cap, so an oversized cap costs you headroom.
Can I make Kimi K3 cheaper with the Batch API?
Not for K3 yet. The Batch API runs at 60% of standard price but only covers kimi-k2.7-code, kimi-k2.6 and kimi-k2.5, so the flagship has no async discount. K3 also keeps reasoning at full effort, which lifts output tokens, so the real levers are automatic caching, a deliberate completion-length cap, and routing bulk non-frontier work to a cheaper model.
How do I send an image to Kimi K3?
As base64 inside the message, or by uploading the file first and referencing its id; public image links are rejected. The message content has to be a list of parts, one for the image and one for your text. Keep images at or below 4K and the whole request body under 100 MB. Vision is billed as input tokens at the normal rate, not as a separate per-image fee, and you can size it in advance with the token-estimate call.
Sources & verification
| Source | What was checked | Last checked |
|---|---|---|
| Artificial Analysis | Official product page | July 17, 2026 |
| Kimi Api Chat.Md | API Chat.md | July 17, 2026 |
| Kimi Api Models Overview.Md | API Models Overview.md | July 17, 2026 |
| Kimi Api Overview.Md | API Overview.md | July 17, 2026 |
| Kimi Api Tool Use.Md | API Tool Use.md | July 17, 2026 |
| Kimi Guide Kimi K3 Quickstart.Md | Kimi K3 Quickstart.md | July 17, 2026 |
| Kimi Guide Migrating From Openai To Kimi.Md | Migrating From Openai To Kimi.md | July 17, 2026 |
Every fact on this Kimi K3 page is tied to a named source and a verification date. Freshness-sensitive figures trace to the sources above; verify against the vendor before relying on them.
Explore Kimi K3
Every page on Kimi K3 in one place, you are on api.
