This guide sets up Kimi K2.6 as a custom BYOK model in the Droid CLI using a
Kimi Code subscription key (the coding-plan key, sk-kimi-...), so you don't need
a paid Droid subscription. Inference bills to your Kimi Code subscription.
Works the same whether you run
droiddirectly or inside a terminal multiplexer like herdr (herdr just hosts thedroidprocess in a pane — no special integration needed).
The Kimi Code endpoint (https://api.kimi.com/coding/v1, OpenAI-compatible) gates
requests by User-Agent. Only recognized coding agents (Claude Code, Kimi CLI, Roo
Code, Kilo Code, …) are allowed; anything else returns:
403 Kimi For Coding is currently only available for Coding Agents such as
Kimi CLI, Claude Code, Roo Code, Kilo Code, etc.
Droid uses the OpenAI Node SDK, which sets its own User-Agent and ignores
droid's extraHeaders override for that header. So a config-only UA spoof fails — you
need a tiny local proxy that rewrites the header before forwarding to Kimi.
- A Kimi Code subscription and its API key (
sk-kimi-...) — from the Kimi Code dashboard. droidinstalled:curl -fsSL https://app.factory.ai/cli | sh- Node.js available (any recent version).
- A free Factory account (you log in once with
droid; BYOK = your key, your bill).
Create ~/.factory/kimi-proxy.js:
#!/usr/bin/env node
// Minimal reverse proxy: forces a recognized User-Agent so a Kimi Code
// subscription key works with droid (OpenAI Node SDK overrides UA itself).
// Listen: http://127.0.0.1:8788/v1/... -> https://api.kimi.com/coding/v1/...
const http = require("node:http");
const https = require("node:https");
const PORT = 8788;
const UPSTREAM_HOST = "api.kimi.com";
const PATH_PREFIX = "/coding"; // incoming /v1/... -> /coding/v1/...
const FORCED_UA = "claude-cli/1.0.119 (external, cli)";
const server = http.createServer((req, res) => {
const headers = { ...req.headers };
headers["host"] = UPSTREAM_HOST;
headers["user-agent"] = FORCED_UA;
delete headers["accept-encoding"];
const upstream = https.request(
{ host: UPSTREAM_HOST, port: 443, method: req.method,
path: PATH_PREFIX + req.url, headers },
(uRes) => { res.writeHead(uRes.statusCode || 502, uRes.headers); uRes.pipe(res); }
);
upstream.on("error", (e) => {
res.writeHead(502, { "content-type": "application/json" });
res.end(JSON.stringify({ error: { message: "proxy: " + e.message } }));
});
req.pipe(upstream);
});
server.listen(PORT, "127.0.0.1", () =>
console.log(`kimi-proxy on http://127.0.0.1:${PORT} -> https://${UPSTREAM_HOST}${PATH_PREFIX}`)
);The proxy binds 127.0.0.1 only and passes your API key through untouched (it
never stores or modifies the key — only the User-Agent).
~/.config/systemd/user/kimi-proxy.service:
[Unit]
Description=Kimi Code -> droid User-Agent rewrite proxy
After=network-online.target
[Service]
ExecStart=/usr/bin/env node %h/.factory/kimi-proxy.js
Restart=always
RestartSec=2
[Install]
WantedBy=default.targetEnable it:
systemctl --user daemon-reload
systemctl --user enable --now kimi-proxy.service
loginctl enable-linger "$USER" # survive logout/reboot(macOS: use a launchd plist, or just run node ~/.factory/kimi-proxy.js &.)
Edit ~/.factory/settings.json and add a customModels array (merge with existing
settings — don't overwrite the file):
{
"customModels": [
{
"model": "kimi-k2.6",
"displayName": "Kimi K2.6 (Kimi Code)",
"baseUrl": "http://127.0.0.1:8788/v1",
"apiKey": "sk-kimi-YOUR_KIMI_CODE_KEY",
"provider": "generic-chat-completion-api",
"maxOutputTokens": 16384
}
]
}Note baseUrl points at the local proxy, not api.kimi.com directly.
curl -s http://127.0.0.1:8788/v1/chat/completions \
-H "Authorization: Bearer sk-kimi-YOUR_KIMI_CODE_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"kimi-k2.6","messages":[{"role":"user","content":"say ok"}],"max_tokens":8}'A JSON completion with "model":"kimi-for-coding" means it works. A 403 means the
proxy isn't running or the UA string is no longer accepted.
droid # then: /model -> pick "Kimi K2.6 (Kimi Code)"Or inside herdr:
herdr # press n -> new workspace, then run `droid` in a pane- Rotate the key: edit
apiKeyin~/.factory/settings.jsononly. The proxy passes the key through, so no proxy change needed. - Kimi changes its accepted-client list: bump
FORCED_UAinkimi-proxy.jsto a current Claude Code / Kimi CLI UA, thensystemctl --user restart kimi-proxy. - Service control:
systemctl --user status|restart|stop kimi-proxy.
Routing a Kimi Code subscription through a client it doesn't officially list (via UA rewrite) may be against Kimi's terms of service. You're using your own key and your own subscription, but do this at your own discretion.