- Python 3.10+ with the openai SDK 1.x, or curl 7.68+
- An account on the Kimi API platform and an API key created in its console
- Any OpenAI-compatible client (LibreChat, Open WebUI, n8n, curl, the openai SDK)
- The exact model ID and base URL from the provider's model list — this guide uses placeholders (<model-id>, <provider-domain>) because those strings change between releases

python3 --version; expected output is Python 3.10.x or higher.curl --version; the first line prints curl 8.x.x (x86_64-pc-linux-gnu).pip install --upgrade "openai>=1.0". Expected last line: Successfully installed openai-1.x.x.Checklist before you start:
python3 --version prints 3.10+curl --version prints 7.68+pip show openai prints a version >= 1.0curl -sS -o /dev/null -w "%{http_code}\n" https://api.<provider-domain>/v1/models (401 is a valid answer here — it proves DNS and TLS work)<provider-domain> is the hostname from the provider's API documentation, for example the host shown next to the "OpenAI-compatible endpoint" section. Replace it everywhere it appears.
What to do: Sign in to the provider console, open API Keys → Create new secret key, name it kimi-k3-prod, and copy the value. Then export it in your shell instead of pasting it into source files:
export KIMI_API_KEY="<paste-key-here>"
mkdir -p ~/.config/kimi && printf 'KIMI_API_KEY=%s\n' "$KIMI_API_KEY" > ~/.config/kimi/.env
chmod 600 ~/.config/kimi/.env
printf '%s\n' ".env" "*.key" >> .gitignore
On PowerShell: $env:KIMI_API_KEY = "<paste-key-here>".
Why: Every request is authenticated with a bearer token — an HTTP header Authorization: Bearer <key> sent with each call. Keeping the key in an environment variable means it never lands in a commit, and rotating it later touches one file.
How to check: Confirm the variable exists without printing the whole secret, then ask the API which models your key can see:
printf '%s\n' "${KIMI_API_KEY:0:6}…" # prints e.g. sk-abc…
curl -s -H "Authorization: Bearer $KIMI_API_KEY" \
https://api.<provider-domain>/v1/models | head -c 400
Expected output is JSON beginning with {"object":"list","data":[{"id":"... — an array of model objects. Copy the id of the K3.1 Pro entry; that exact string is your <model-id> for the rest of this guide.

What to do: An OpenAI-compatible API means the request and response bodies follow OpenAI's wire format (POST /v1/chat/completions, bearer auth, choices[].message.content). So you keep your client and change only two values:
| Client setting | Value | Where to find it |
|---|---|---|
| `base_url` | `https://api.<provider-domain>/v1` | provider docs, "API endpoint" |
| `api_key` | value of `KIMI_API_KEY` | your console |
| `model` | `<model-id>` | the `/v1/models` reply from Step 1 |
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["KIMI_API_KEY"],
base_url="https://api.<provider-domain>/v1",
)
resp = client.chat.completions.create(
model="<model-id>", # exact id from /v1/models, e.g. what the console lists
messages=[
{"role": "system", "content": "Answer in one sentence."},
{"role": "user", "content": "What does an OpenAI-compatible endpoint change in my code?"},
],
temperature=0.3,
max_tokens=200,
)
print(resp.choices[0].message.content)
print(resp.usage)
For GUI clients that take a JSON config rather than code (LibreChat, Open WebUI, n8n's OpenAI node), use the same three fields:
{
"provider": "openai-compatible",
"baseURL": "https://api.<provider-domain>/v1",
"apiKey": "${KIMI_API_KEY}",
"model": "<model-id>",
"stream": true
}
Why: Your SDK or tool already speaks that format, so the only difference between calling OpenAI and calling Kimi is the host in base_url and the model string. If you later want to keep a second provider in the same client, put a router in front instead of hardcoding hosts — see How to combine multiple AI providers behind one OpenAI-compatible router. The same swap-the-base-URL pattern applied to a self-hosted server is documented in How to connect a local LLM to Claude Code: llama-server and an OpenAI-compatible API.
How to check: Save the snippet as kimi_test.py and run python3 kimi_test.py. You should see one sentence of plain text, then a usage object of the form CompletionUsage(completion_tokens=42, prompt_tokens=38, total_tokens=80). If the token counts are non-zero, the request reached the model and was billed.

What to do: Test the raw HTTP layer, then streaming, then the client. Start with curl so you can see the untouched response:
curl -s https://api.<provider-domain>/v1/chat/completions \
-H "Authorization: Bearer $KIMI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"<model-id>","messages":[{"role":"user","content":"Reply with the word OK"}],"max_tokens":10}'
Why: The HTTP layer tells you which side is broken. A raw curl that returns "object":"chat.completion" proves the key, host, and model ID are all correct, so any later failure is in your client configuration, not in the account.
Then check streaming, which is what interactive UIs use:
curl -N -s https://api.<provider-domain>/v1/chat/completions \
-H "Authorization: Bearer $KIMI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"<model-id>","messages":[{"role":"user","content":"Count to five"}],"stream":true}'
How to check: Success looks like this, in this order:
"id":, "object":"chat.completion", "choices":[{"index":0,"message":{"role":"assistant","content":"OK"}}], and a "usage" block with non-zero prompt_tokens and completion_tokens.data: {"id":..., each carrying a delta fragment, ending with data: [DONE].python3 kimi_test.py exits with code 0 (echo $? prints 0)./v1/models lists your model IDchat.completion[DONE]usage.total_tokens > 0Problem. Every request fails immediately with 401.
{"error":{"message":"Invalid Authentication","type":"invalid_authentication_error","code":"invalid_api_key"}}.env file with quotes that the client reads literally.unset KIMI_API_KEY && export KIMI_API_KEY="$(tr -d '[:space:]' < ~/.config/kimi/.env | cut -d= -f2-)", then repeat the curl /v1/models check. If it still returns 401, create a new key in the console and delete the old one.Problem. Authentication succeeds but the completion call returns 404.
{"error":{"message":"The model '<model-id>' does not exist or you do not have access to it","type":"invalid_request_error","code":"model_not_found"}}/v1/models (case, hyphenation, a date suffix), or base_url is wrong — either missing the /v1 suffix or containing it twice (.../v1/v1/chat/completions)./v1/models output, then print the effective URL your client builds: python3 -c "from openai import OpenAI; import os; print(OpenAI(api_key=os.environ['KIMI_API_KEY'], base_url='https://api.<provider-domain>/v1').base_url)". It must end in exactly one /v1/.Problem. Requests start failing under load with 429.
{"error":{"message":"Rate limit reached for requests","type":"rate_limit_error","code":"rate_limit_exceeded"}}openai SDK retries 429 automatically by default (max_retries=2); raise it with OpenAI(..., max_retries=5, timeout=60.0) and serialize batch jobs. Check the tier limits on the provider's rate-limit page, since the numbers are per account and not quoted here.You now have a working key, a verified /v1/chat/completions call, and a client configured with three values: base URL, key, model ID. Next, move the key into your orchestrator's secret store and add a second provider behind a router so a quota error on one does not stop your pipeline.
What exactly is an "OpenAI-compatible API"?
It is a service that accepts the same HTTP request format as OpenAI's API — same paths, same JSON fields, same bearer-token header — so existing SDKs and tools work after you change only the base URL and model name.
Do I need a different SDK for Kimi?
No. Use the official openai Python or Node SDK with base_url pointed at the provider's endpoint, or use curl. Only clients that hardcode api.openai.com cannot be reconfigured — most support a custom base URL field.
How much does Kimi K3.1 Pro cost per million tokens?
Rates are set by the provider and change over time, so check the pricing page in the console before you commit. This guide deliberately avoids quoting a number that may already be stale; watch the usage object in every response to measure real spend.
Why do I get a model_not_found error when the model name looks right?
Model IDs are exact strings, and some are gated by account tier or region. Copy the id value straight from the /v1/models response rather than typing it, and if the model is absent from that list, request access in the console.