SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
33.5 KB

# xAI (Grok API) — provider research for PolyLLM

Last documentation audit: 2026-09-08 Probes executed 2026-09-08 with a real key against https://api.x.ai/v1 (scripts in research/xai/, raw outputs in research/xai/out/). Everything marked (probed) was observed live; everything marked (docs) comes from the pages listed at the end.

Heads-up: docs.x.ai/docs/... URLs given in the brief are all 404 now. The documentation moved to https://docs.x.ai/developers/... (see "Documentation pages used"). The API error for Live Search still points to the old /docs/guides/tools/overview URL, which is dead.


# 1. Base URL, auth, headers

Item Value
REST base URL https://api.x.ai/v1 (also a gRPC API at api.x.ai:443, not relevant for a web app)
Auth Authorization: Bearer <XAI_API_KEY>
Content type application/json (malformed JSON → 422 text/plain Rust/serde message, e.g. Failed to deserialize the JSON body into the target type: messages: invalid type: string "nope", expected a sequence at line 1 column 37) (probed)
Useful request headers x-grok-conv-id: <stable id> — routes a conversation to the same server to maximise prompt-cache hits (docs); accepted (probed). Body field prompt_cache_key is the equivalent for Responses API.
Response headers (probed) x-request-id, x-ratelimit-limit-requests, x-ratelimit-remaining-requests, x-ratelimit-limit-tokens, x-ratelimit-remaining-tokens (present on inference calls; not on /models). Behind Cloudflare (cf-ray). Observed limits: grok-4.6 7200 req / 50 000 000 tokens per window; grok-4.3 1800 req / 10 000 000 tokens (window is per minute per the TPM docs; the RPS numbers below suggest the header window is 1 minute).
Key introspection GET /v1/api-key (redacted key, name, ACLs, team/user ids) and GET /v1/me (adds zdr_status, team_blocked) (probed, 200). Handy for a "validate key" button: cheap, no tokens. But an invalid key returns 400, not 401 (see §14).

# 2. SDK recommendation (TypeScript / Node)

  • There is no official xAI JavaScript/TypeScript SDK. xai-sdk is Python-only (gRPC). npm xai-sdk / @xai/sdk do not exist (checked 2026-09-08).
  • xAI's own docs give JS examples with openai (OpenAI SDK) pointed at baseURL: "https://api.x.ai/v1" and with Vercel AI SDK @ai-sdk/xai (latest 4.0.54; xai(model) = Responses API since AI SDK 7, xai.chat(model) = legacy chat completions; exposes xai.tools.webSearch/xSearch/codeExecution/mcpServer/…).
  • Anthropic SDK compatibility (/v1/messages, /v1/complete) is "fully deprecated" (docs, Legacy section). Do not build on it.
  • Recommendation for PolyLLM: use the OpenAI SDK openai@7.10.0 (what we probed) with baseURL: https://api.x.ai/v1, maxRetries handled by us, and a long timeout (docs recommend 3600 s for reasoning models; we used 120 s fine for small prompts). Both client.chat.completions.create and client.responses.create work unchanged. A raw fetch SSE parser also works (see §16) and is what we used to capture exact chunk shapes.

# 3. Endpoints (inference + management)

Endpoint Status Notes
POST /v1/responses preferred (docs: "The Responses API is the preferred way… New features come to the Responses API first") Stateful by default (store: true, 30-day retention), previous_response_id, server-side tools, encrypted reasoning, structured outputs via text.format. (probed)
GET /v1/responses/{id}, GET /v1/responses/{id}/input_items, DELETE /v1/responses/{id} active Delete returns {"id","object":"response","deleted":true} (probed)
POST /v1/responses/compact active Context compaction → opaque blob to feed back verbatim (docs)
POST /v1/chat/completions legacy but fully working OpenAI-compatible; reasoning_content returned; no deprecation date. Multi-agent model rejected here. (probed)
GET /v1/chat/deferred-completion/{request_id} active For deferred: true requests (202 while pending, 24 h retention, single read) (docs)
POST /v1/completions, POST /v1/messages, POST /v1/complete deprecated/legacy Not supported by reasoning models; Anthropic compat fully deprecated (docs)
GET /v1/models, GET /v1/models/{id} active Minimal (id, aliases). (probed)
GET /v1/language-models, GET /v1/language-models/{id} active Rich metadata + pricing (§12). Does NOT include context_length despite docs example. (probed)
GET /v1/image-generation-models, GET /v1/video-generation-models (+/{id}) active Image: image_price, pricing[], max_prompt_length. (probed)
POST /v1/tokenize-text active {text, model} → {token_ids:[{token_id,string_token,token_bytes}]} (probed) — usable for client-side token counting (one network call).
GET /v1/api-key, GET /v1/me active See §1
POST /v1/images/generations, /v1/images/edits, videos, voice (STT/TTS/speech-to-speech), /v1/files, collections, batches, embeddings (section exists, no text-embedding model listed) active Out of scope for the chat adapter except image generation (grok-imagine-*).

# 4. Chat Completions request/response (probed shapes)

Request body fields (docs API ref + probes): model, messages, max_completion_tokens (default 128 000; max_tokens is deprecated but still accepted (probed)), temperature 0–2, top_p 0–1, n, seed, stop (≤4; rejected by reasoning models), frequency_penalty/presence_penalty (rejected by every current model), logit_bias, logprobs/top_logprobs (0–8; "ignored by grok-4.20+", accepted (probed)), reasoning_effort (none|low|medium|high|xhigh, model-dependent), response_format, tools (≤128 docs API ref; ≤200 docs function-calling page), tool_choice, parallel_tool_calls, stream, stream_options.include_usage, prompt_cache_key, service_tier (default|priority), user, deferred, web_search_options (OpenAI compat), search_parameters (dead → 410).

Roles: system, user, assistant, tool. developer role is accepted (probed, all 6 models). Unknown top-level params (foo_bar) are silently ignored (probed); top_k is accepted on chat completions although only documented for Responses (probed).

Non-streaming response (probed):

json
{
  "id": "…", "object": "chat.completion", "created": 1788842609, "model": "grok-4.6",
  "choices": [{ "index": 0, "finish_reason": "stop",
    "message": { "role": "assistant", "content": "2 + 2 equals 4.",
                 "reasoning_content": "The user asked: …", "refusal": null } }],
  "usage": {
    "prompt_tokens": 652, "completion_tokens": 8, "total_tokens": 770,
    "prompt_tokens_details": { "text_tokens": 652, "audio_tokens": 0, "image_tokens": 0, "cached_tokens": 512 },
    "completion_tokens_details": { "reasoning_tokens": 110, "audio_tokens": 0,
                                   "accepted_prediction_tokens": 0, "rejected_prediction_tokens": 0 },
    "num_sources_used": 0, "cost_in_usd_ticks": 12440000 },
  "system_fingerprint": "fp_e41c6060b2628547", "service_tier": "default"
}
  • total_tokens = prompt + completion + reasoning (reasoning tokens are not inside completion_tokens; 652+8+110=770).
  • cost_in_usd_ticks: 10 000 000 000 ticks = $1 (docs) → 12 440 000 ticks = $0.001244. Responses API also returns cost_in_usd_ticks (and docs mention cost_in_nano_usd). Use this for exact per-request cost display.
  • finish_reason: stop, length (probed: max_completion_tokens: 10 → "length" on both reasoning and non-reasoning models; on reasoning models the cap applies to visible tokens, reasoning still ran ~680 tokens), tool_calls (probed). Docs also list end_turn (streaming).
  • Hidden system prompt: even a one-line prompt costs ~190–200 prompt tokens on grok-4.3/4.20/build, ~500 on grok-4.5 and ~650 on grok-4.6 (probed) — budget for it.

# 5. Streaming protocol (chat completions) (probed)

SSE, Content-Type: text/event-stream, no event: field, data: {json} lines, terminated by data: [DONE].

Chunk sequence for a reasoning model (grok-4.6):

json
data: {"id":"1039…","object":"chat.completion.chunk","created":1788842609,"model":"grok-4.6",
       "choices":[{"index":0,"delta":{"reasoning_content":"The","role":"assistant"}}],
       "system_fingerprint":"fp_e41c6060b2628547","service_tier":"default"}
data: {"…","choices":[{"index":0,"delta":{"reasoning_content":" user"}}],…}
…
data: {"…","choices":[{"index":0,"delta":{"content":"Bonjour"}}],…}
data: {"…","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],…}
data: {"…","choices":[],"usage":{"prompt_tokens":646,"completion_tokens":3,"total_tokens":1189,
       "prompt_tokens_details":{…,"cached_tokens":512},
       "completion_tokens_details":{"reasoning_tokens":540,…},"num_sources_used":0,"cost_in_usd_ticks":37820000},…}
data: [DONE]
  • Delta keys observed: role, reasoning_content, content, tool_calls. Non-reasoning model (grok-4.20-non-reasoning): only role/content.
  • Reasoning is streamed as delta.reasoning_content (plain text) for grok-4.6, 4.5, 4.3, 4.20-reasoning, grok-build → PolyLLM can render a live "thinking" pane on chat completions without any extra flag.
  • Usage arrives in a final extra chunk with choices: [] only when stream_options: {include_usage: true}; the finish_reason chunk is separate. No x-ratelimit differences for streams.
  • Chunks carry no per-chunk usage (docs' example showing usage on every chunk is outdated).

# 6. Tool / function calling (probed on all 6 models)

  • Chat completions: OpenAI nested format {type:"function", function:{name, description, parameters}}; tool_choice: auto|required|none|{type:"function",function:{name}}; parallel_tool_calls (default true). Parameter root must be an object (or anyOf/oneOf of objects).
  • Round trip works with assistant.tool_calls + {role:"tool", tool_call_id, content} on every model.
  • Streaming: the whole tool call arrives in ONE chunk (docs + probed), with full arguments:
json
{"delta":{"tool_calls":[{"id":"call-cbf4aac4-…-0","function":{"name":"get_weather","arguments":"{\"city\":\"Montreal\"}"},"index":0,"type":"function"}]}}

then finish_reason: "tool_calls". The non-reasoning model also includes "role":"assistant" in that delta. Standard OpenAI accumulation code works, but you can also treat each tool_calls delta as complete. Tool-call ids look like call-<uuid>-<n>.

  • Responses API: flat tool {type:"function", name, description, parameters}; events response.output_item.added (item type:"function_call", call_id, name, arguments:""), one response.function_call_arguments.delta with the full JSON, response.function_call_arguments.done, response.output_item.done. Return results as {type:"function_call_output", call_id, output} items (probed).
  • Tool arguments are implicitly strict (docs: "Tool calling… implicit strict: true").

# 7. Structured output (probed on all 6 models)

  • Chat completions: response_format: {type:"json_schema", json_schema:{name, schema, strict:true}} → valid JSON on all 6 models; {type:"json_object"} also works on all 6 (docs say it is accepted).
  • Responses API: text: {format: {type:"json_schema", name, schema, strict:true}} (probed OK).
  • OpenAI SDK helpers work: client.chat.completions.parse(... response_format: zodResponseFormat(...)) (docs).
  • JSON Schema support (docs): string/number/integer/boolean/null/enum/const/array/object/anyOf/oneOf/allOf(single)/$ref+$defs (non-circular); enforced formats date, time, date-time, email, uuid, ipv4, ipv6, uri; minLength/maxLength ≤2048, minItems/maxItems ≤256, min/maxProperties ≤64; additionalProperties defaults to false; rejected: empty enum/anyOf, boolean-valued properties, min/maxContains, tuple items. Regex: no backrefs/lookaround/\b/Unicode properties; . matches newline; ^/$ implicit. "Structured Outputs are available on Grok 4 family models."

# 8. Reasoning controls (probed matrix)

Model Reasoning reasoning_effort accepted reasoning_content returned
grok-4.6 always on low, medium, high (default), xhigh; minimal accepted (undocumented, likely mapped); none → 400 This model does not support \reasoning_effort` value `none`.` yes (chat completions, plain text; Responses: reasoning.summary[] + optional encrypted_content)
grok-4.5 always on low, medium, high (default); xhigh/minimal accepted (docs: xhigh treated as high); none → 400 same message yes
grok-4.3 configurable none accepted → reasoning_tokens 0, low, medium, high; xhigh/minimal accepted (mapped) yes (empty when none)
grok-4.20-0309-reasoning always on, fixed any value → 400 Model grok-4.20-0309-reasoning does not support parameter reasoningEffort. yes
grok-4.20-0309-non-reasoning none any value → 400 same pattern no (reasoning_tokens: 0)
grok-build-0.1 always on, fixed any value → 400 same pattern yes
grok-4.20-multi-agent-0309 always on `low medium= 4 agents,high
  • Same behaviour via Responses API reasoning: {effort} (probed): 4.20-*/build reject low; 4.3 accepts none (reasoning.summary becomes "none", no reasoning item).
  • Responses API returns reasoning as an output item {type:"reasoning", summary:[{type:"summary_text", text}], encrypted_content?}; streamed as response.reasoning_summary_part.added / response.reasoning_summary_text.delta / …done / response.reasoning_summary_part.done. With include: ["reasoning.encrypted_content"] you get an opaque encrypted_content you can pass back in the input for stateless multi-turn with reasoning continuity (docs). reasoning.summary (auto|concise|detailed) is "compatibility only"; the API echoed "summary":"detailed" when we sent auto.
  • No thinking-budget parameter exists. Reasoning tokens are billed at the output price.

# 9. Sampling & other parameters — support matrix (probed, chat completions)

Param 4.6 4.5 4.3 4.20-reasoning 4.20-non-reasoning build-0.1
temperature (0–2) ✓ ✓ ✓ ✓ ✓ ✓
top_p ✓ ✓ ✓ ✓ ✓ ✓
top_k (undocumented on chat) ✓ ✓ ✓ ✓ ✓ ✓
seed (+ system_fingerprint) ✓ ✓ ✓ ✓ ✓ ✓
max_completion_tokens ✓ ✓ ✓ ✓ ✓ ✓
max_tokens (deprecated) ✓ ✓ ✓ ✓ ✓ ✓
n: 2 ✓ ✓ ✓ ✓ ✓ ✓
logprobs/top_logprobs ✓ (accepted; docs: ignored on 4.20+) ✓ ✓ ✓ ✓ ✓
response_format json_object / json_schema ✓ ✓ ✓ ✓ ✓ ✓
stop 400 400 400 400 ✓ 400
frequency_penalty 400 400 400 400 400 400
presence_penalty 400 400 400 400 400 400
reasoning_effort see §8 400 400 400
developer role ✓ ✓ ✓ ✓ ✓ ✓

Exact rejection text: {"code":"invalid-argument","error":"Model grok-4.6 does not support parameter presencePenalty."} (camelCase param names: presencePenalty, frequencyPenalty, stop, reasoningEffort). Adapter rule: never send frequency_penalty/presence_penalty to xAI; send stop only to grok-4.20-0309-non-reasoning; send reasoning_effort only to 4.6/4.5/4.3 (and multi-agent). On /v1/responses, presence_penalty was silently accepted and echoed as 0 (probed) — the Responses endpoint is more lenient; Responses defaults echoed: temperature 0.7, top_p 0.95, truncation "disabled".

# 10. Modalities, context, output limits

  • Input: text + image on all 7 language models (input_modalities: ["text","image"] from /v1/language-models; vision probed OK on all 6 chat models). No audio input on text models (separate voice models/endpoints). Output: text only. Image generation = separate models grok-imagine-image, -2.0, -quality via /v1/images/generations (also an image_generation server-side tool on Responses); video = grok-imagine-video, -1.5.
  • Images: JPG/PNG, ≤20 MiB, data URL data:image/png;base64,… or public URL, detail: "high" accepted, unlimited count (docs). Hard minimums (probed, undocumented): width and height ≥ 8 px and total ≥ 512 pixels — errors {"code":"invalid_image","error":"Image dimensions 2x2 are too small. Both width and height must be at least 8 pixels."} / "Image has 64 total pixels (8x8), which is below the minimum of 512 pixels." / "Invalid PNG image.". A 32×32 PNG cost 1–3 image_tokens. Image tokens billed at the text input price (prompt_image_token_price == prompt_text_token_price).
  • Chat completions image format: {type:"image_url", image_url:{url, detail}}; Responses: {type:"input_image", image_url:"<url or data url>", detail} (string, not object) + {type:"input_text", text}.
  • Context windows (docs, not in API): grok-4.6 500k, grok-4.5 500k, grok-4.3 / 4.20-* / multi-agent 1M, grok-build-0.1 256k. long_context_threshold = 200 000 tokens for every model: when the prompt exceeds it, input/cached/output are all billed at 2×.
  • Max output: docs — grok-4.6 "no text output limit"; API default max_completion_tokens / max_output_tokens = 128 000. max_output_tokens on Responses includes reasoning tokens; max_completion_tokens on chat completions covers visible tokens only (docs + probed).
  • Files: POST /v1/files (48 MB, text/PDF/code), input_file with file_id/file_url on Responses, auto attachment_search tool ($10/1k calls), agentic models only (4.20, 4.5, 4.6). Collections (RAG) $2.50/1k searches.

# 11. Server-side (agentic) tools, search, citations (probed on grok-4.3 via /v1/responses)

  • Live Search (search_parameters) is dead: chat completions → 410 {"error":"Live search is deprecated. Please switch to the Agent Tools API: https://docs.x.ai/docs/guides/tools/overview"}; tools:[{type:"live_search",sources:[…]}] → 410 too; tools:[{type:"web_search"}] on chat completions → 422 tools[0].type: unknown variant \web_search`, expected `function` or `live_search``. Server-side tools are Responses-API-only.
  • Responses tools: {type:"web_search", allowed_domains?(≤5), excluded_domains?(≤5), enable_image_understanding?, enable_image_search?}, {type:"x_search", allowed_x_handles?/excluded_x_handles? (≤20), from_date?, to_date?, enable_image_understanding?, enable_video_understanding?}, {type:"code_interpreter"} (sandboxed Python), {type:"mcp", server_url, server_label, server_description?, allowed_tools?, authorization?, headers?} (Streamable HTTP/SSE), {type:"file_search"} / collections_search, {type:"image_generation"}. max_turns caps agentic loops. Tool-call output is not returned by default; docs' include values (web_search_call_output, …) are rejected (400 Argument not supported: "web_search_call_output" in "include" field) — what is accepted: include: ["web_search_call.action.sources"] and ["no_inline_citations"] (probed).
  • Streaming event names observed with web_search: response.created, response.in_progress, response.output_item.added, response.reasoning_summary_part.added, response.reasoning_summary_text.delta, response.reasoning_summary_text.done, response.reasoning_summary_part.done, response.output_item.done, response.web_search_call.in_progress, response.web_search_call.searching, response.web_search_call.completed, response.content_part.added, response.output_text.delta, response.output_text.annotation.added, response.output_text.done, response.content_part.done, response.completed. Each event has sequence_number, item_id, output_index.
  • Citations: (1) inline Markdown [[N]](url) in the text (default on; disable with include:["no_inline_citations"]), (2) annotations: [{type:"url_citation", url, start_index, end_index, title:"1"}] on the output_text part, (3) output[] items {type:"web_search_call", status, action:{type:"search", query, sources:[{type:"url", url}]}} (one per search, 3 searches for our question). A top-level response.citations field was not present in the REST response (the docs' response.citations is the Python SDK). Usage adds num_server_side_tools_used, server_side_tool_usage_details:{web_search_calls, x_search_calls, code_interpreter_calls, file_search_calls, mcp_calls, document_search_calls, image_generation_calls} and context_details. Our single question consumed 20 919 input tokens (11 072 cached) + 856 output → ≈ $0.032 + 3 × $0.005 tool calls. Budget accordingly.
  • Tool pricing (docs): web/X search $5 per 1 000 calls, code execution $5/1k, file attachments $10/1k, collections $2.50/1k; image/video understanding token-based.
  • No computer-use tool. Image generation available as a tool and as /v1/images/generations.

# 12. Model listing & pricing units (probed)

GET /v1/language-models fields: id, fingerprint, created, object, owned_by, version, input_modalities, output_modalities, prompt_text_token_price, cached_prompt_text_token_price, prompt_image_token_price, completion_text_token_price, search_price, prompt_text_token_price_long_context, cached_prompt_text_token_price_long_context, completion_text_token_price_long_context, long_context_threshold, aliases. No context_length, no max output.

Unit (verified against the pricing page): USD cents per 100 000 000 tokens → $ per 1M tokens = value / 10 000. grok-4.6 prompt_text_token_price: 20000 → $2.00/M ✓; cached 5000 → $0.50 ✓; completion 60000 → $6.00 ✓; grok-4.3 12500 → $1.25 ✓. Image models: image_price in 1/100 000 000 cent → grok-imagine-image 200000000 = $0.02 ✓.

Model ctx (docs) in / cached / out ($/M, <200k) ≥200k tokens aliases
grok-4.6 500k 2.00 / 0.50 / 6.00 4.00 / 1.00 / 12.00 —
grok-4.5 500k 2.00 / 0.30 / 6.00 4.00 / 0.60 / 12.00 grok-4.5-latest, grok-build-latest
grok-4.3 1M 1.25 / 0.20 / 2.50 2.50 / 0.40 / 5.00 grok-4.3-latest
grok-4.20-0309-reasoning 1M 1.25 / 0.20 / 2.50 2.50 / 0.40 / 5.00 grok-4.20, grok-4.20-reasoning, … (15)
grok-4.20-0309-non-reasoning 1M 1.25 / 0.20 / 2.50 2.50 / 0.40 / 5.00 grok-4.20-non-reasoning, … (8)
grok-4.20-multi-agent-0309 1M 1.25 / 0.20 / 2.50 (all agents billed) 2.50 / 0.40 / 5.00 grok-4.20-multi-agent, … (6)
grok-build-0.1 256k 1.00 / 0.20 / 2.00 2.00 / 0.40 / 4.00 grok-code-fast-1, grok-code-fast, grok-code-fast-1-0825

Image gen: grok-imagine-image $0.02, -2.0 $0.04 (pricing[] by quality/resolution 0.04–0.08), -quality $0.05 (retiring 2026-11-02 → redirected to -2.0 low). Video $0.05/s, -1.5 $0.08/s. Batch API −20 % on grok-4.3 and grok-4.20 variants only. service_tier: "priority" = 2× tokens. Knowledge cutoff grok-4.6: 2026-02-01.

# 13. Prompt caching & provider-side state

  • Automatic on all grok language models, prefix-based on the messages array; no minimum documented; no fixed TTL ("can be evicted at any time"). Reported as usage.prompt_tokens_details.cached_tokens (chat) / usage.input_tokens_details.cached_tokens (Responses). (probed: 128–512 cached tokens on the very first request — the hidden system prompt is cached.) Cached price = 10–25 % of input price (see table). Maximise hits with x-grok-conv-id header (chat) or prompt_cache_key (Responses) = stable session id; never edit earlier messages.
  • Server-side state: Responses API stores everything for 30 days by default (store: true echoed (probed)), previous_response_id continuation works (probed: recalled "pamplemousse"), instructions incompatible with previous_response_id (docs). For a BYOK privacy-conscious app send store: false and manage history client-side (docs image page even recommends not storing). zdr_status on /v1/me tells whether the team has zero-data-retention.

# 14. Errors, rate limits, retries

Error body: {"code": "<string>", "error": "<message>"} (not OpenAI's {error:{message,type,code}} — the OpenAI SDK will surface e.error = that object, e.message derived).

Case (probed) HTTP body
Invalid key 400 {"code":"invalid-argument","error":"Incorrect API key provided. You can obtain an API key from https://console.x.ai."}
No Authorization header 401 {"code":"unauthenticated:no-credentials","error":"No credentials presented."}
Unknown model 400 {"code":"invalid-argument","error":"Model not found: grok-99"}
Unsupported param 400 {"code":"invalid-argument","error":"Model X does not support parameter presencePenalty."}
Malformed body 422 (text/plain) serde message
Bad image 400 {"code":"invalid_image","error":"…"}
Bad include 400 {"code":"400","error":"Argument not supported: …"}
Live search 410 {"error":"Live search is deprecated. …"} (no code)
Multi-agent on chat completions 400 (text/plain) Multi Agent requests are not allowed on chat completions
Rate limit 429 (docs) exponential backoff recommended

Docs also list 403 (permissions/ACL), 404, 405, 415, 202 (deferred). Rate limits (docs, tiers by cumulative spend since 2026-01-01, never downgrade): T0 $0 → T4 $5 000; grok-4.6/4.5: 150 RPS / 50M TPM (T0) → 500 RPS / 100M TPM (T4); grok-4.3, 4.20-*, build: 37 RPS / 10M TPM → 208 RPS / 85M; multi-agent 9 RPS / 2.5M → 56 / 21M. TPM counts prompt + completion + reasoning + cached tokens. Headers in §1. Status page https://status.x.ai.

Retry/timeout recommendation for the adapter: retry 429/5xx with jittered exponential backoff (respect retry-after if present — none observed), never retry 400/422; do not retry mid-stream; use a long read timeout (≥ 5 min; xAI suggests 3600 s) because xhigh reasoning can run minutes before the first content delta (but reasoning_content deltas arrive quickly, which is a good liveness signal). Map xAI 400 "Incorrect API key" to the app's invalid credentials state (do not rely on 401).

# 15. Lifecycle / aliases

  • May 15 2026 retirement: grok-3, grok-4-0709, grok-4-fast-*, grok-4-1-fast-*, grok-code-fast-1, grok-imagine-image-pro. Slugs still resolve (probed: grok-3 and grok-4-fast-reasoning answered with model: "grok-4.3" and were billed at 4.3 rates); reasoning slugs → grok-4.3 low, non-reasoning → grok-4.3 none, grok-code-fast-1 → grok-build-0.1. Do not list retired slugs in the UI; if a user types one, show the redirect.
  • -latest aliases exist for 4.5, 4.3, 4.20-*; grok-4.6 has none yet. grok-build-latest → grok-4.5 (surprising). Use canonical ids from /v1/language-models and display aliases as secondary.
  • grok-4.20-multi-agent is beta ("potential breaking changes"), Responses-only, no client tools.
  • Docs pages under /docs/* are gone; only /developers/* is maintained.

# 16. Exact streaming code that worked

OpenAI SDK (openai@7.10.0) — chat completions with reasoning + usage:

ts
import OpenAI from "openai";
const client = new OpenAI({ apiKey: process.env.XAI_API_KEY, baseURL: "https://api.x.ai/v1", timeout: 3_600_000, maxRetries: 0 });

const stream = await client.chat.completions.create({
  model: "grok-4.6",
  messages: [{ role: "user", content: "Say hello in French, 5 words max." }],
  stream: true,
  stream_options: { include_usage: true },
  max_completion_tokens: 200,
  reasoning_effort: "low",             // only 4.6 / 4.5 / 4.3
}, { headers: { "x-grok-conv-id": sessionId } });

for await (const chunk of stream) {
  const d = (chunk.choices[0]?.delta ?? {}) as any;
  if (d.reasoning_content) onThinking(d.reasoning_content);
  if (d.content) onText(d.content);
  if (d.tool_calls) onToolCalls(d.tool_calls);      // arrives complete in one chunk
  if (chunk.choices[0]?.finish_reason) onFinish(chunk.choices[0].finish_reason);
  if (chunk.usage) onUsage(chunk.usage);            // last chunk, choices: []
}

Raw fetch SSE for /v1/responses (used in research/xai/lib.ts → rawSSE): POST JSON, read res.body with TextDecoder, split on \n\n, take data: lines, JSON.parse, stop at [DONE]; each event's type field is the event name (response.output_text.delta → delta, response.completed → response.usage, response.function_call_arguments.done → arguments, response.output_text.annotation.added → citation).

# 17. Probe results table

# Probe Model(s) Result
00 GET /models, /language-models, /image-generation-models, /video-generation-models, /api-key, /me, POST /tokenize-text — all 200; 7 language, 3 image, 2 video models; prices in cents/100M tokens; no context_length; tokenizer returns token ids + bytes
01a tiny chat completion max_completion_tokens: 200 6 models all 200, finish_reason: stop; reasoning_content present on 5/6; reasoning 46–333 tokens for "2+2"; ~2.6 s for grok-4.6
01b streaming + include_usage 6 models SSE data: only; delta.reasoning_content then delta.content; final choices: [] usage chunk with cached_tokens, reasoning_tokens, cost_in_usd_ticks; [DONE]
02 param matrix (21 variants) 6 models see §9; penalties rejected everywhere; stop only on non-reasoning; reasoning_effort per §8; unknown params ignored
03 function call round trip, streaming 6 models tool call in a single chunk with complete arguments; finish_reason: tool_calls; round 2 with role: tool OK on all
04 response_format: json_schema strict 6 models valid JSON matching schema on all
05 vision, base64 PNG data URL 6 models 2×2 → 400 (min 8 px sides); 8×8 → 400 (min 512 px total); 32×32 → 200 on all, image_tokens 1–3
06 invalid key / no auth / unknown model / bad body / retired slugs — 400 invalid-argument / 401 / 400 / 422 text / 200 served by grok-4.3
07 /v1/responses: basic + reasoning.summary + encrypted_content, streaming events, function call (flat tool) + function_call_output, text.format json_schema, input_image, multi-agent on chat grok-4.3 (+multi-agent) all 200; 13 event types; encrypted reasoning returned; multi-agent on chat completions → 400
08 web_search tool streaming; legacy search_parameters; web_search on chat; include variants grok-4.3 Responses OK (3 searches, url_citation annotations + inline [[N]](url), 20.9k input tokens); chat: 410 / 422; include accepts web_search_call.action.sources, no_inline_citations only
09 finish_reason on truncation; Responses reasoning.effort on 4.20/build/4.3; x-grok-conv-id + prompt_cache_key + service_tier; store default + previous_response_id + DELETE mixed length; 4.20/build reject effort, 4.3 none OK; header/params accepted, service_tier: default; store default true, follow-up recalled word, delete → deleted: true
10 tools:[{type:"live_search", sources:[{type:"web"}]}] on chat grok-4.3 410 Live search deprecated

# Documentation pages used (all fetched 2026-09-08)