Grok xAI Router
OpenAI-compatible local proxy for Grok Build / xAI. Uses official
grok login OAuth (same pattern as 9router) against
cli-chat-proxy.grok.com, with optional API-key fallback to
api.x.ai. Try image generation/edits & video below — they use
your live CLI session or API key.
Image playground
POST /v1/images/generations
Generate from text, or edit a source image with a prompt.
Uses api.x.ai with your active auth. Response defaults to
b64_json (ZDR-safe). Default model:
grok-imagine-image-quality
Video playground
POST /v1/videos/generations
Async job on xAI — start, then poll until done. Default model:
grok-imagine-video
Job
request_id: —
No job yet.
Quickstart
npm install
cp .env.example .env
# COOKIE_ENCRYPTION_KEY=$(openssl rand -base64 32)
# API_BEARER_TOKEN=$(openssl rand -hex 32)
npm run dev
# auth via official CLI (no API key required)
grok login
# smoke test with OpenAI SDK
BASE_URL=http://127.0.0.1:3000 npm run test:openai
Authentication
Upstream credentials are resolved server-side. Clients must send
Authorization: Bearer <API_BEARER_TOKEN>. The playground
token field stores this credential only for the current browser tab.
- Browser cookie API key — optional override via connect UI (not on this docs page)
- Grok CLI OAuth —
~/.grok/auth.jsonfromgrok loginorgrok login --device-auth XAI_API_KEY— publicapi.x.aipath
CLI mode sends x-xai-token-auth: xai-grok-cli and client fingerprint headers.
Tokens auto-refresh and write back to auth.json.
Set PREFER_CLI_AUTH=0 to prefer the env API key over CLI OAuth.
GET /api/session
GET /api/grok-login/help
Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/models |
Yes | List models from active upstream |
| POST | /v1/chat/completions |
Yes | Chat Completions (CLI path converts to Responses) |
| POST | /v1/responses |
Yes | OpenAI Responses API |
| POST | /v1/images/generations |
Yes | Image generation (via api.x.ai) |
| POST | /v1/images/edits |
Yes | Image edits (JSON, not multipart) |
| POST | /v1/videos/generations |
Yes | Start video job → { request_id } (optional ?wait=1) |
| POST | /v1/videos/edits |
Yes | Edit existing video (async) |
| POST | /v1/videos/extensions |
Yes | Extend video from last frame (async) |
| GET | /v1/videos/:requestId |
Yes | Poll video job status |
| GET | /api/session |
No | Auth status + defaults (no tokens) |
Models GET /v1/models
Yes — this proxy exposes a models endpoint. It forwards to the active upstream
(cli-chat-proxy.grok.com/v1/models for CLI OAuth, or
api.x.ai/v1/models for API keys).
curl -s http://127.0.0.1:3000/v1/models | jq .
Live models
Loading…
Default chat model: grok-build
· Default image model: grok-imagine-image-quality
Chat Completions POST /v1/chat/completions
OpenAI-compatible. Under CLI OAuth the body is converted to the Responses API
for cli-chat-proxy; non-stream responses are converted back to
chat-completions shape.
curl -s http://127.0.0.1:3000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "grok-build",
"messages": [{"role":"user","content":"Reply with exactly: pong"}],
"stream": false
}'
Responses POST /v1/responses
curl -s http://127.0.0.1:3000/v1/responses \
-H 'Content-Type: application/json' \
-d '{
"model": "grok-build",
"input": [{"type":"message","role":"user","content":"hi"}],
"stream": false,
"store": false
}'
Images
Image routes always hit api.x.ai (using the CLI bearer when no API key
is configured). Both generation and editing are supported.
Edits are JSON-based (image / image_url / data URI),
not multipart uploads — OpenAI SDK images.edit() will not work.
Defaults to response_format: "b64_json" for ZDR teams.
# generate
POST /v1/images/generations
{ "model": "grok-imagine-image-quality", "prompt": "…", "n": 1 }
# edit (public URL or data URI)
POST /v1/images/edits
{
"model": "grok-imagine-image-quality",
"prompt": "Render this as a pencil sketch",
"image": { "url": "https://…", "type": "image_url" }
}
# edit — image_url convenience (proxy coerces to image object)
POST /v1/images/edits
{ "model": "grok-imagine-image-quality", "prompt": "…", "image_url": "data:image/png;base64,…" }
Videos POST /v1/videos/*
Grok Imagine video is async on xAI. Start a job, then poll until
status is done / failed / expired.
Routes use public api.x.ai with the active CLI or API-key bearer.
Default model: grok-imagine-video.
# text-to-video (start)
curl -s http://127.0.0.1:3000/v1/videos/generations \
-H 'Content-Type: application/json' \
-d '{
"model": "grok-imagine-video",
"prompt": "A glowing rocket launching from Mars",
"duration": 5,
"aspect_ratio": "16:9",
"resolution": "480p"
}'
# → { "request_id": "…" }
# poll
curl -s http://127.0.0.1:3000/v1/videos/$REQUEST_ID
# start + wait (proxy polls for you)
curl -s 'http://127.0.0.1:3000/v1/videos/generations?wait=1' \
-H 'Content-Type: application/json' \
-d '{ "model": "grok-imagine-video", "prompt": "…", "duration": 3 }'
# image-to-video
{ "prompt": "pan out slowly", "image": { "url": "https://…" }, "duration": 8 }
# edit / extend
POST /v1/videos/edits
{ "prompt": "add sunglasses", "video": { "url": "https://….mp4" } }
POST /v1/videos/extensions
{ "prompt": "camera zooms out", "video": { "url": "https://….mp4" }, "duration": 6 }
Modes: text-to-video (prompt only), image-to-video (image),
reference-to-video (reference_images), edit, extend.
Duration 1–15s. Smoke test: npm run test:openai:video
OpenAI SDK
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.API_BEARER_TOKEN,
baseURL: "http://127.0.0.1:3000/v1",
});
const models = await client.models.list();
const chat = await client.chat.completions.create({
model: "grok-build",
messages: [{ role: "user", content: "hello" }],
});
const image = await client.images.generate({
model: "grok-imagine-image-quality",
prompt: "a red cube",
response_format: "b64_json",
});
// Image edit is JSON — not OpenAI SDK images.edit() (multipart)
const edited = await fetch("http://127.0.0.1:3000/v1/images/edits", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.API_BEARER_TOKEN}` },
body: JSON.stringify({
model: "grok-imagine-image-quality",
prompt: "make it blue",
image: { url: "https://…/source.png", type: "image_url" },
response_format: "b64_json",
}),
}).then((r) => r.json());
// Video is async — use fetch (OpenAI SDK has no xAI video helper yet)
const start = await fetch("http://127.0.0.1:3000/v1/videos/generations", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.API_BEARER_TOKEN}` },
body: JSON.stringify({
model: "grok-imagine-video",
prompt: "a cat in a sunbeam",
duration: 5,
}),
}).then((r) => r.json());
Smoke tests:
npm run test:openai
· npm run test:openai:image
· npm run test:openai:video
Environment
| Variable | Default | Purpose |
|---|---|---|
PORT |
3000 |
HTTP port |
COOKIE_ENCRYPTION_KEY |
— | Required if using browser cookie keys (32-byte base64) |
XAI_API_KEY |
— | Optional public API key |
GROK_CHAT_MODEL |
grok-build |
Default chat / responses model |
XAI_IMAGE_MODEL |
grok-imagine-image-quality |
Default image model |
XAI_VIDEO_MODEL |
grok-imagine-video |
Default video model |
VIDEO_POLL_TIMEOUT_MS |
600000 |
Max wait for ?wait=1 video jobs |
GROK_CLI_BASE_URL |
https://cli-chat-proxy.grok.com/v1 |
CLI OAuth upstream |
XAI_BASE_URL |
https://api.x.ai/v1 |
API-key upstream |
PREFER_CLI_AUTH |
on | Set 0 to prefer env API key over CLI |
GROK_AUTH_JSON / GROK_HOME |
~/.grok/auth.json |
CLI credential path overrides |