Documentation

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.

  1. Browser cookie API key — optional override via connect UI (not on this docs page)
  2. Grok CLI OAuth~/.grok/auth.json from grok login or grok login --device-auth
  3. XAI_API_KEY — public api.x.ai path

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