Two built-in providers route Claude Code to xAI’s Grok models. They differ only in how they authenticate and which xAI surface they hit — pick one:
| Provider | Auth | Backend | Billing |
|---|---|---|---|
grok |
xai_oauth — your SuperGrok / X Premium+ login |
cli-chat-proxy.grok.com/v1 (the Grok CLI chat proxy) |
your subscription — no per-token charge |
xai |
api_key (XAI_API_KEY) |
api.x.ai/v1 (the developer API) |
metered API credits |
Both are kind = "responses" providers speaking xAI’s Responses dialect — the same translation
path as Codex, which this page mirrors. It links out to the deeper topic pages
(Effort & Context, Model Discovery,
Providers) rather than repeating them.
Quick start
Let a coding agent wire it up for you — shunt add prints the setup blueprint for either path
(offline and read-only; the agent edits the config, the command never does):
shunt add upstream grok --print | claude # SuperGrok / X Premium+ subscription (OAuth)
shunt add upstream xai --print | claude # xAI developer API (XAI_API_KEY)Or follow the manual steps below.
How it works
shunt translates Claude Code’s Anthropic Messages request into the OpenAI Responses API, sends
it to xAI, and translates the streamed reply back. The xai Responses flavor (drops params xAI
rejects, keeps tools as function tools) is selected two ways: by an api.x.ai host, or by
auth = "xai_oauth" (the grok proxy isn’t an x.ai host, so its dialect is keyed on the auth).
| Aspect | grok (subscription) |
xai (API key) |
|---|---|---|
| Endpoint | cli-chat-proxy.grok.com/v1/responses |
api.x.ai/v1/responses |
| Auth | Grok CLI OAuth from ~/.shunt/xai-auth.json, or a pool of named logins under ~/.shunt/accounts/xai/, auto-refreshed |
Bearer $XAI_API_KEY |
| Identity headers | Grok CLI headers (x-xai-token-auth, x-grok-client-identifier, x-grok-client-version) so the proxy honors the subscription |
none |
Path A — SuperGrok subscription (grok)
1. Log in
Run shunt’s own device-code login (RFC 8628). It prints a URL and a code; approve in a browser on any device — no loopback callback server:
shunt login xaiOn success shunt writes the tokens to ~/.shunt/xai-auth.json with 0600 permissions and
refreshes them automatically (a 5-minute expiry buffer; xAI rotates the refresh token on every refresh, so shunt
persists the rotated one under a single-flight lock). If the refresh token is gone or the response
omits a rotated token, shunt tells you to run shunt login xai again.
2. The provider block (optional)
grok is built in — you don’t need to declare it. This is the full default; a partial table
overrides only the keys you set (config maps deep-merge):
[providers.grok]
kind = "responses"
base_url = "https://cli-chat-proxy.grok.com/v1" # shunt appends /responses
auth = "xai_oauth" # read + auto-refresh ~/.shunt/xai-auth.json
# effort = "high" # optional — opt in to reasoning effort (§ Reasoning effort)3. Route a model to grok
[[routes]]
model = "grok-4.6"
provider = "grok"
# upstream_model = "grok-4.6" # optional: forward a different slug upstreamSeveral SuperGrok accounts
shunt can pool several SuperGrok logins behind the grok provider and fail over between them, the
way it pools Claude, Codex, and Kimi accounts. Log each one in under its own name:
shunt login xai --name main
shunt login xai --name backupEach login is saved to ~/.shunt/accounts/xai/<name>.json (0600, in a 0700 directory;
override the directory with $SHUNT_XAI_ACCOUNTS_DIR). With no further config, grok uses every
account in that directory. To pick some, list them on the provider:
[[providers.grok.accounts]]
name = "main"
[[providers.grok.accounts]]
name = "backup"
priority = 200 # optional: lower is preferred (default 100)In an [[upstreams]] config, name them in the auth map instead:
auth = { mode = "xai_oauth", accounts = ["main", "backup"] } (or account = "main" for one).
shunt keeps a session (x-claude-code-session-id) on one account and round-robins otherwise. When
an account answers 401, 402, 429, or 5xx, or its token cannot be refreshed, shunt cools it
down and retries the request on the next one. After a 401, shunt also refreshes that account’s
token before using it again. Any other 4xx, including 403, is returned as is:
a 403 can be a content-policy refusal, and re-sending it to every account could get all of them
flagged. The serving account is named in the x-shunt-account response header, or in an
account event before a streamed reply.
With [server.pool] usage_refresh_seconds set,
shunt also reads each named account’s Grok usage, the credit total and per-product usage such as
Grok Build, and shows it as that account’s usage bars on the admin dashboard. The bars are for
display only: they do not change which account serves a request. When your Grok CLI login
(~/.grok/auth.json) belongs to the same xAI user as a named account, the dashboard shows the two
as one row under the account’s name.
Path B — xAI developer API (xai)
1. Export the key
export XAI_API_KEY=xai-…2. The provider block (optional)
[providers.xai]
kind = "responses"
base_url = "https://api.x.ai/v1" # shunt appends /responses
auth = "api_key"
api_key_env = "XAI_API_KEY"3. Route a model to xai
[[routes]]
model = "grok-4.6"
provider = "xai"Model slugs
The slug catalog is xAI’s, not shunt’s — shunt forwards whatever slug you route. Current
coding/frontier slugs are grok-4.6, grok-4.5, and grok-build-0.1. Use upstream_model in a
route to map an alias onto a live slug without touching your Claude Code env. (Model
discovery only surfaces claude-named aliases you declare, so it can’t
list a raw Grok slug — reach these via ANTHROPIC_CUSTOM_MODEL_OPTION or a tier remap below.)
Select the model in Claude Code
Grok slugs don’t start with claude-, so Claude Code’s /model picker won’t list them from
discovery. The mechanics are identical to Codex — add the id to the picker directly:
export ANTHROPIC_CUSTOM_MODEL_OPTION="grok-4.6" # must resolve via [models.upstream_model], [[routes]], or [[route_prefixes]]The same Codex section covers the rest verbatim:
putting a subagent on a Grok slug via model: frontmatter, and remapping the tier aliases
(ANTHROPIC_DEFAULT_SONNET_MODEL, …) onto Grok slugs for a whole session.
Reasoning effort
Unlike Codex, effort is opt-in for Grok. Several Grok models (grok-4*, grok-3,
grok-code-fast, …) return 400 on a reasoning.effort field even though they reason natively, so
shunt sends the dial only when you configure it on the provider or route (or pass it per
request) — otherwise the model uses its native reasoning:
[providers.grok]
effort = "high" # applies to all grok traffic
# …or per route
[[routes]]
model = "grok-4.6"
provider = "grok"
effort = "high"grok-4.6 accepts reasoning.effort (verified live). Leave effort unset for any slug that
400s on it. Full precedence and the effort table: Effort & Context.
Context window
Claude Code sizes its context bar at a fixed 200k for a mapped id. If your Grok slug’s real
window is larger, raise it — the value follows a non-claude- id automatically:
export CLAUDE_CODE_MAX_CONTEXT_TOKENS=256000 # set your slug's real window, per xAI's model docsIt’s global (one value per session); set it to the smallest real window among your mapped
models, since exceeding a model’s real window causes prompt is too long overflow churn. Details
and count_tokens behavior: Effort & Context.
Web search
Claude Code’s built-in web search does not work on Grok routes. xAI’s Responses API accepts only
function tools, so shunt drops the hosted web_search tool on the xai flavor (both grok and
xai). Use a codex or openai route when you need hosted web search.
Full example (subscription path)
shunt.toml:
[server]
bind = "127.0.0.1:3001"
default_provider = "anthropic"
[providers.grok]
effort = "high" # optional: opt in to reasoning effort for all Grok traffic
[[routes]]
model = "grok-4.6"
provider = "grok"Shell (both shunt and Claude Code run with these):
shunt login xai # one-time device-code login
./target/release/shunt run # start the gateway
export ANTHROPIC_BASE_URL=http://127.0.0.1:3001
export ANTHROPIC_CUSTOM_MODEL_OPTION="grok-4.6" # add to /model pickerPick grok-4.6 from /model. Everything else in the session still flows to Anthropic unchanged;
only the mapped model’s inference is answered by your SuperGrok subscription.
Troubleshooting
| Symptom | Cause / Fix |
|---|---|
run shunt login xai on startup |
No ~/.shunt/xai-auth.json (or wrong $SHUNT_XAI_AUTH_FILE). Run shunt login xai. |
xAI refresh response missing refresh_token; run shunt login xai |
The stored refresh token was consumed/rotated away. Log in again. |
402 … personal-team-blocked:spending-limit / “need a Grok subscription” |
On the xai (developer API) path without API credits. Add credits at console.x.ai, or route to grok to use your subscription. |
403 … not authorized for API access (subscription tier gate) |
On the grok path your subscription tier doesn’t include API access — re-logging in won’t help. Set XAI_API_KEY and use the xai path, or upgrade at x.ai/grok. With several accounts, a tier gate that shows up when shunt refreshes an account’s token cools that account down, and shunt moves to the next one. A 403 on a request is returned as is, because it can also be a content-policy refusal. |
refusing to send a subscription token off-origin (from shunt check) |
An xai_oauth provider’s base_url host isn’t x.ai/grok.com, isn’t HTTPS, or isn’t kind = "responses". Fix the block. |
all upstreams failed (N attempted) with named xAI accounts |
Every pooled account failed before any upstream answered: each one failed to load or refresh its credential, or hit a transport error or timeout. For a credential failure, run shunt login xai --name <account> again, or check the files under ~/.shunt/accounts/xai/. If an account did answer with 401, 429, or 5xx, shunt stops retrying and returns that last answer, after gateway mapping, instead of this message. A 402 from every account reaches the client as 502. |
xAI account scope references missing store account "<name>" |
account/accounts names an account with no file in the store. Log it in with shunt login xai --name <name> or fix the name. |
400 when effort is set |
That Grok slug rejects reasoning.effort. Remove effort from the provider/route for it. |
model <slug> is not enabled for this account |
Unentitled slug — confirm the slug against xAI’s catalog. |
| Web search returns nothing | Not supported on Grok routes; shunt drops the tool. Use a codex/openai route. |
See the full Troubleshooting reference for more.