One page. Any agent. Copy, paste, ship.
This is the universal instruction set for talking to Chutes.ai — decentralized, serverless inference for open-source AI models, powered by Bittensor. Every hosted model runs inside a hardware-isolated TEE (Trusted Execution Environment), and the whole thing speaks the OpenAI API. If your tool can talk to OpenAI, it can talk to Chutes by changing two lines.
TL;DR
- Inference base URL:
https://llm.chutes.ai/v1- Auth header:
Authorization: Bearer cpk_...(everywhere — see Auth)- It's the OpenAI API. Point any OpenAI SDK at the base URL above and you're done.
- Models change. The list doesn't lie:
GET https://llm.chutes.ai/v1/modelsis public and is the source of truth.
- Two base URLs
- Auth: the one rule that matters
- Get a key in 60 seconds
- Your first call (5 languages)
- Discover models (and never hardcode)
- Smart routing: one request, many models
- Streaming, tools, JSON mode, vision
- Privacy: every model is a TEE
- Money: pricing, balance, the 25% research discount
- Errors & gotchas
- Endpoint cheat sheet
- Machine-readable interfaces
Chutes has exactly two hosts. Keep them straight and everything else is easy.
| Purpose | Base URL | Auth needed? |
|---|---|---|
| Inference — chat completions, model list | https://llm.chutes.ai/v1 |
Bearer for completions; GET /models is public |
| Account & platform — keys, billing, usage, OAuth | https://api.chutes.ai |
Bearer |
That's it. llm.chutes.ai is the OpenAI-compatible inference plane. api.chutes.ai is everything else.
Authorization: Bearer cpk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxUse Authorization: Bearer with your cpk_ key on every authenticated request, on both hosts. This is exactly what standard OpenAI SDKs send by default — so they work with zero auth glue.
A few hard-won truths (live-verified 2026-06-11):
- ✅
Authorization: Bearer cpk_...works onllm.chutes.aiandapi.chutes.ai, including real paidPOST /v1/chat/completions(HTTP 200). - ❌
X-API-Keyis silently ignored on the inference plane — a completion sent with it lands on the anonymous rate-limit path (HTTP 429), byte-identical to sending no auth at all — and returns 401 on the management API. Don't use it. - 🌐
GET /v1/modelsneeds no auth. Convenient for discovery, but a200there does not prove your key works. Validate keys against an authenticated endpoint (e.g.GET https://api.chutes.ai/users/me).
Keys are
cpk_-prefixed and shown exactly once. Store them in a secret manager or env var. This repo ships a keychain-backedmanage_credentials.pyso agents never paste raw secrets into a chat.
Humans: open chutes.ai/auth/start → create account → create an API key in the dashboard. (Note: the button on chutes.ai/auth opens a support widget — always use /auth/start.)
Agents / programmatic:
# 1. Register (username: 3–20 chars, alphanumeric). Returns a 32-char fingerprint — shown ONCE.
curl -X POST https://api.chutes.ai/users/register \
-H "Content-Type: application/json" \
-d '{"username": "your-handle"}'
# 2. Create an API key (authenticate with the session from step 1).
curl -X POST https://api.chutes.ai/api_keys/ \
-H "Authorization: Bearer <session>" \
-H "Content-Type: application/json" \
-d '{"name": "my-agent-key", "admin": false}'
# → response.secret_key is your cpk_ key. Save it now; it is never shown again.
⚠️ The fingerprint is the master credential. It's displayed once and can only be recovered if you've linked a Bittensor wallet (reset at chutes.ai/auth/reset). Back it up immediately.
Same request, every ecosystem. Swap in your key and go.
curl https://llm.chutes.ai/v1/chat/completions \
-H "Authorization: Bearer $CHUTES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-ai/DeepSeek-V3.2-TEE",
"messages": [{"role": "user", "content": "Say hello in one sentence."}]
}'from openai import OpenAI
client = OpenAI(
base_url="https://llm.chutes.ai/v1",
api_key="cpk_...", # or os.environ["CHUTES_API_KEY"]
)
resp = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V3.2-TEE",
messages=[{"role": "user", "content": "Say hello in one sentence."}],
)
print(resp.choices[0].message.content)import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://llm.chutes.ai/v1",
apiKey: process.env.CHUTES_API_KEY,
});
const resp = await client.chat.completions.create({
model: "deepseek-ai/DeepSeek-V3.2-TEE",
messages: [{ role: "user", content: "Say hello in one sentence." }],
});
console.log(resp.choices[0].message.content);npm install @chutes-ai/ai-sdk-providerimport { createChutes } from "@chutes-ai/ai-sdk-provider";
import { generateText } from "ai";
const chutes = createChutes({ apiKey: process.env.CHUTES_API_KEY });
const { text } = await generateText({
model: chutes("deepseek-ai/DeepSeek-V3.2-TEE"),
prompt: "Say hello in one sentence.",
});import litellm
resp = litellm.completion(
model="chutes_ai/deepseek-ai/DeepSeek-V3.2-TEE",
messages=[{"role": "user", "content": "Say hello in one sentence."}],
api_key="cpk_...",
)Connecting Hermes or OpenClaw? Jump to their drop-in configs: Hermes · OpenClaw.
Model IDs change as the catalog evolves. Always ask the live endpoint instead of pinning an ID that may vanish:
curl https://llm.chutes.ai/v1/models # public, no authEach model object carries everything you need to choose well:
| Field | What it tells you |
|---|---|
id |
The string to pass as model (e.g. Qwen/Qwen3.5-397B-A17B-TEE) |
context_length / max_output_length |
Input window / max generation |
pricing.prompt / pricing.completion |
USD per 1M tokens (in / out) |
pricing.input_cache_read |
Discounted rate on prompt-cache hits |
confidential_compute |
true ⇒ runs in a TEE (use this, not the -TEE suffix) |
supported_features |
e.g. ["tools","json_mode","structured_outputs","reasoning"] |
supported_sampling_parameters |
Which sampling knobs the engine accepts |
input_modalities |
["text"], ["text","image"], ["text","image","video"] |
owned_by |
Inference engine: sglang or vllm |
A convenience snapshot lives at docs/known-models.md and data/chutes-models.json, auto-refreshed daily by GitHub Actions. The live /v1/models call is always the source of truth.
💡 Latency/throughput is not in
/v1/models. For live TTFT/TPS, queryGET https://api.chutes.ai/invocations/stats/llm, or just let routing pick for you (next section).
Don't bet your uptime on a single model. Chutes lets you pass a pool and a strategy right in the model field.
Comma-separate model IDs; append a strategy suffix:
# Sequential failover — try each in order until one answers
model="zai-org/GLM-5-TEE,deepseek-ai/DeepSeek-V3.2-TEE,Qwen/Qwen3.5-397B-A17B-TEE"
# Lowest latency right now (best for interactive chat)
model="zai-org/GLM-5-TEE,deepseek-ai/DeepSeek-V3.2-TEE:latency"
# Highest throughput right now (best for long generations / batch)
model="zai-org/GLM-5-TEE,deepseek-ai/DeepSeek-V3.2-TEE:throughput"Configure a pool at chutes.ai/app → Model Routing, then use:
| Alias | Strategy |
|---|---|
default |
Sequential failover |
default:latency |
Fastest first token |
default:throughput |
Most tokens/sec |
A single concrete model ID bypasses routing entirely. Manage pools via GET/POST https://api.chutes.ai/model_aliases/. Deep recipes (build pools from live data, TEE-only pools, alias governance) live in the chutes-routing skill.
All standard OpenAI features — gated by each model's supported_features, so check the model list first.
Streaming — set stream: true, read SSE chunks:
stream = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V3.2-TEE",
messages=[{"role": "user", "content": "Stream me a haiku."}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")Tool / function calling — pass tools=[...]; works on any model whose supported_features includes "tools".
JSON mode & structured outputs — response_format={"type": "json_object"} on models advertising "json_mode"; full JSON-schema enforcement on models advertising "structured_outputs".
Vision — send image parts in content to models whose input_modalities include "image" (e.g. google/gemma-4-31B-turbo-TEE, Qwen/Qwen3.6-27B-TEE, the Kimi-K2 line). Kimi also accepts "video".
Media chutes (video / image / audio generation) — these never appear on the gateway; each runs on its own host (https://<slug>.chutes.ai) and takes a flat JSON body on POST /generate, returning raw media bytes (see the full-modality catalog). Three things bite agents here:
- Renders are synchronous, and the public edge times out at ~600 s. The connection stays open for the whole generation. Chute code may reference an 1800 s cord limit, but the public ingress (nginx behind a Google LB) returns HTTP 504 at the 10-minute mark — measured live 2026-08-06: an 8 s / 30-step video render 504'd at exactly 600.2 s, while a minimum-size render (5 s @ 20 steps) completed in 272 s. Queue time counts: instances serialize renders on a per-GPU semaphore, and two concurrent minimum-size requests both 504'd — send media requests one at a time. And check the HTTP status and
Content-Type— on 504 you get a tiny HTML error page with curl still exiting 0, so "exit code 0 + output file exists" does not mean you have media. - The published schema may be incomplete. Some community chutes' llms.txt/openapi document only
promptwhile the deployed code accepts more (image conditioning, duration, seed, …). The authoritative contract is the chute's source:GET https://api.chutes.ai/chutes/code/{chute_id}— read the pydantic*Inputclass. (GET /openapi.jsonon the chute host itself just proxies to the management API; it does not describe the chute.) - Billing is per compute-second at the chute's GPU rate (
x-chutes.pricing.usdPerHourin its model-page openapi.json), so a video render costs dimes, not the micro-cents of a chat call.
Runnable example: cookbook/python/08_video_generation.py.
Before sending exotic sampling params (
top_k,repetition_penalty, …), check the model'ssupported_sampling_parameters.sglangandvllmengines accept different knobs.
As of the latest snapshot, 100% of hosted models run with confidential_compute: true — inside Intel TDX hardware enclaves. Prompts and responses are hardware-isolated; even Chutes operators can't read them.
- Filter on the boolean, not the suffix. Trust
confidential_compute: true, not the-TEEin the name. - Want proof, not promises? Each chute exposes attestation evidence at
GET https://api.chutes.ai/chutes/{chute_id}/evidence(requires a 64-hex-charnonce), plus golden measurements atGET https://api.chutes.ai/servers/tee/measurements. Thechutes-teeskill fetches and parses real TDX/GPU quotes. Don't claim cryptographic attestation unless Intel DCAP verification actually ran and passed.
Pricing is per-model, USD per 1M tokens, in the pricing object of each model. Prompt-cache hits are billed at the cheaper input_cache_read rate automatically. Public GPU/TAO pricing: GET https://api.chutes.ai/pricing.
Balance & account: GET https://api.chutes.ai/users/me → balance (USD), payment_address (Bittensor SS58 for crypto top-ups), quotas, and more.
Top up:
- Crypto — send
$TAO, SN64, or any Bittensor alpha token to yourpayment_address; auto-converts to USD within minutes (non-refundable). - Stripe — chutes.ai/app → billing → "Add Balance" → "Top up with Stripe" (25+ payment methods).
25% research discount: swap the inference base URL for https://research-data-opt-in-proxy.chutes.ai/v1 — same API, same models, same key, 25% cheaper. The trade: prompts/responses are recorded for joint caching research with Harvard. Never send sensitive data here. Confirm it's active via GET https://api.chutes.ai/users/me/discounts.
| Symptom | Likely cause | Fix |
|---|---|---|
429 on a completion you authenticated |
You sent X-API-Key, which is ignored → anonymous rate limit |
Use Authorization: Bearer cpk_... |
401 on api.chutes.ai |
Missing/invalid Bearer, or X-API-Key |
Send a valid Bearer cpk_... |
200 on /v1/models but completions fail |
/v1/models is public — it never validated your key |
Test against GET /users/me |
model not found |
Stale hardcoded ID | Re-query GET /v1/models; IDs change |
| Param rejected | Engine doesn't support that sampling knob | Check supported_sampling_parameters |
| Tool call ignored | Model lacks "tools" |
Pick a model whose supported_features includes it |
Errors return JSON shaped like { "detail": "..." }. List endpoints paginate: { "total", "page", "limit", "items": [...] } (0-indexed pages, default limit 25). POST/PATCH need Content-Type: application/json.
| What | Method | URL |
|---|---|---|
| Register | POST | https://api.chutes.ai/users/register |
| My account / balance | GET | https://api.chutes.ai/users/me |
| Create API key | POST | https://api.chutes.ai/api_keys/ |
| List / delete API keys | GET / DELETE | https://api.chutes.ai/api_keys/{id} |
| List models | GET | https://llm.chutes.ai/v1/models (public) |
| Chat completions | POST | https://llm.chutes.ai/v1/chat/completions |
| Live latency/throughput | GET | https://api.chutes.ai/invocations/stats/llm |
| Model aliases (routing) | GET / POST | https://api.chutes.ai/model_aliases/ |
| Quotas | GET | https://api.chutes.ai/users/me/quotas |
| Discounts | GET | https://api.chutes.ai/users/me/discounts |
| Pricing (public) | GET | https://api.chutes.ai/pricing |
| TEE evidence | GET | https://api.chutes.ai/chutes/{chute_id}/evidence?nonce=<64-hex> |
| TEE golden measurements | GET | https://api.chutes.ai/servers/tee/measurements |
| Swagger UI | — | https://api.chutes.ai/docs |
Point tool-based frameworks (AutoGPT, LangChain loaders, ChatGPT-plugin format) straight at these:
- OpenAPI spec:
https://api.chutes.ai/openapi.json - Plugin manifest:
https://chutes.ai/.well-known/ai-plugin.json - Agent-facing docs:
https://chutes.ai/llms.txt(alsollms-full.txt,docs.json) - Live model list:
https://llm.chutes.ai/v1/models
- Use it in Claude → install the plugin from this repo's README
- Use it in Hermes →
other-agents/hermes/README.md - Use it in OpenClaw →
other-agents/openclaw/README.md - Any other OpenAI-compatible client →
other-agents/openai-compatible/README.md - Drop into any agent's system prompt →
other-agents/system-prompt/chutes-agent-prompt.md
Models and the catalog change. When live inventory, pricing, capabilities, or TEE status matter, GET https://llm.chutes.ai/v1/models is the source of truth.