Turn a GitHub Copilot subscription into a local OpenAI-compatible API.
Single static binary, written in Rust. No database, no web UI, no third-party services. Just a focused CLI that:
- Logs into GitHub via device flow (or imports an existing token from
gh/ the official Copilot plugin). - Exposes
POST /v1/chat/completionsandGET /v1/modelson127.0.0.1. - Forwards requests to
api.githubcopilot.comwith full SSE streaming. - Pools multiple Copilot accounts with round-robin and automatic failover.
- Records per-request usage to a local SQLite file.
Inspired by sub2api but intentionally
minimal.
Schema snapshots track the active release line in VERSION.
If VERSION is still on v0.2.x, keep schema snapshot updates on the v0.2.0
files instead of introducing v0.3.0 snapshot names early.
cargo install --path .# 1. Add a Copilot account (GitHub device flow)
tokn-router login
# or import an existing GitHub token
tokn-router import --from gh
# 2. Start the local server (default 127.0.0.1:4141)
tokn-router serve
# 3. Point any OpenAI-compatible client at it
curl http://127.0.0.1:4141/v1/chat/completions \
-H 'content-type: application/json' \
-d '{
"model": "gpt-4o",
"messages": [{"role":"user","content":"hi"}],
"stream": true
}'~/.config/tokn-router/config.toml (mode 0600).
[server]
host = "127.0.0.1"
port = 4141
[pool]
strategy = "round_robin"
failure_cooldown_secs = 60
[usage]
enabled = true
# Optional outbound HTTP/HTTPS/SOCKS5 proxy. Omit the section entirely to make
# a direct connection. Setting `system = true` defers to the standard
# HTTP_PROXY / HTTPS_PROXY env vars.
[proxy]
# url = "http://user:pass@proxy.example.com:8080"
# url = "socks5h://127.0.0.1:1080"
# system = false
# no_proxy = ["localhost", "127.0.0.1", ".internal"]
[[accounts]]
id = "personal"
provider = "github-copilot" # default — see "Providers" below for others
auth_type = "bearer"
refresh_token = "gho_..."
access_token = "tid=..."
access_token_expires_at = 1730000000
refresh_url = "https://api.github.com/copilot_internal/v2/token"
[accounts.settings]
editor_version = "vscode/1.95.0"
editor_plugin_version = "copilot-chat/0.20.0"
user_agent = "GitHubCopilotChat/0.20.0"
copilot_integration_id = "vscode-chat"
openai_intent = "conversation-panel"
initiator_mode = "auto"The downstream client may also send X-Initiator: user|agent per request,
which overrides the auto-classifier and the config setting.
If Copilot starts rejecting requests with 401/403 + HTML, your editor identity
headers are likely stale. Bump the account's [accounts.settings] values to
match the current VS Code Copilot Chat extension and restart.
tokn-router login [--provider PROVIDER] [--no-proxy]
tokn-router import --from gh|copilot-plugin|env [--provider PROVIDER] [--env-var NAME]
tokn-router account list|remove ID|show ID
tokn-router headers [--account ID] # inspect resolved Copilot identity headers
tokn-router serve [--port N] [--with-proxy] [--proxy-route-mode MODE] [--no-proxy] [--insecure-allow-remote]
tokn-router proxy [start] [--port N] [--route-mode MODE] [--no-proxy] [--insecure-allow-remote]
tokn-router proxy env [--shell sh|fish|pwsh]
tokn-router proxy shell [--shell /path/to/shell]
tokn-router proxy ca path|show|regenerate
tokn-router usage [--since 24h] [--account ID]
tokn-router config get|set|unset KEY [--account ID] [--add]
tokn-router config list | edit | path
proxy runs a local HTTP CONNECT forward proxy that MITMs a small allowlist of
LLM API hosts and routes those requests through tokn-router's existing account
pool.
First run generates a local CA under ~/.config/tokn-router/ca/:
tokn-router proxy
tokn-router proxy ca showThen trust the printed CA cert and inject proxy + CA env vars into your shell:
eval "$(tokn-router proxy env)"Or spawn a subshell with those variables already set:
tokn-router proxy shellproxy shell uses SHELL when available and falls back to /bin/sh. Pass
--shell /path/to/shell to override detection.
The emitted env block sets:
HTTPS_PROXY/HTTP_PROXYSSL_CERT_FILE(to a generated merged bundle containing system roots + the tokn-router CA)NODE_EXTRA_CA_CERTSREQUESTS_CA_BUNDLE(merged bundle)CURL_CA_BUNDLE(merged bundle)GIT_SSL_CAINFO(merged bundle)
NODE_EXTRA_CA_CERTS still points at the local ca.crt because Node appends it to
the built-in trust store. The other variables point at ca-bundle.crt so they do
not drop system roots.
Config:
[proxy_mode]
host = "127.0.0.1"
port = 4142
route_mode = "route"
# optionally default recognized providers to proxy-only modes
[proxy_mode.provider_modes]
# github-copilot = "passthrough"
# openai = "switch"
# optional; defaults to ~/.config/tokn-router/ca
# ca_dir = "/some/path"
# extend the built-in MITM allowlist
# intercept_hosts = ["my-tokn-gateway.example.com"]
# force selected hosts to pass through untouched
# passthrough_hosts = ["api.githubcopilot.com"]Requests to hosts outside the allowlist are tunneled through untouched.
When a request does not explicitly choose a route mode, proxy interception
applies mode precedence in this order: request override, provider-specific
[proxy_mode.provider_modes], then global [proxy_mode].route_mode.
tokn-router serve --with-proxy runs the OpenAI-compatible HTTP server and the
MITM proxy together in one process. They share the same account pool and event
pipeline, but each listener can keep its own default route mode via
[server].route_mode and [proxy_mode].route_mode (or --proxy-route-mode).
For a trusted LAN central server, bind the API server and proxy to reachable interfaces explicitly:
tokn-router serve --host 0.0.0.0 --with-proxy --insecure-allow-remoteThis also exposes LAN bootstrap helpers on the API listener:
/-/lan/bootstrap.jsonprints API/proxy URLs, CA fingerprint, and helper links./-/lan/ca.crtserves only the public proxy CA certificate./-/lan/env?shell=sh|bash|zsh|fish|pwshprints client env setup that downloads the CA and exportsOPENAI_BASE_URL, proxy vars, and CA bundle vars.
The server prints the CA SHA-256 fingerprint at startup. Verify that fingerprint before trusting a CA fetched over the LAN. The private CA key is never served.
| id | auth | notes |
|---|---|---|
github-copilot (default) |
GitHub OAuth device flow → short-lived API token | identity headers; auto-classified X-Initiator |
zai-coding-plan / zai / zhipuai-coding-plan / zhipuai |
static API key (Authorization: Bearer ...) |
Four provider ids sharing one implementation; OpenAI-compatible upstream; auto-injects thinking: { type: "enabled", clear_thinking: false } for reasoning models |
The four Z.ai provider ids share one backend implementation, but each id is
registered independently. The default upstream is
https://api.z.ai/api/coding/paas/v4; override per-account for the
China-mainland endpoint:
[[accounts]]
id = "coding-plan"
provider = "zai-coding-plan"
auth_type = "bearer"
api_key = "sk-..."
base_url = "https://open.bigmodel.cn/api/paas/v4"Add a Z.ai account interactively (key is read with hidden input and verified
against /models):
tokn-router login --provider zai-coding-plan
# or non-interactively from the environment
ZAI_API_KEY=sk-... tokn-router import --from env --provider zai --id work/v1/models returns the upstream OpenAI-shape entries unchanged; each entry
gains an x_tokn_router block with the resolved provider id, display name,
auth kind, and (when known) static capabilities/cost/limit metadata.
Dual-licensed under MIT or Apache-2.0.