TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1# xAI (Grok API) — provider research for PolyLLM23Last documentation audit: **2026-09-08**4Probes 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.56> 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.78---910## 1. Base URL, auth, headers1112| Item | Value |13|---|---|14| REST base URL | `https://api.x.ai/v1` (also a gRPC API at `api.x.ai:443`, not relevant for a web app) |15| Auth | `Authorization: Bearer <XAI_API_KEY>` |16| 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)** |17| 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. |18| 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). |19| 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). |2021## 2. SDK recommendation (TypeScript / Node)2223- **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).24- 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/…`).25- **Anthropic SDK compatibility (`/v1/messages`, `/v1/complete`) is "fully deprecated"** (docs, Legacy section). Do not build on it.26- **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.2728## 3. Endpoints (inference + management)2930| Endpoint | Status | Notes |31|---|---|---|32| `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)** |33| `GET /v1/responses/{id}`, `GET /v1/responses/{id}/input_items`, `DELETE /v1/responses/{id}` | active | Delete returns `{"id","object":"response","deleted":true}` **(probed)** |34| `POST /v1/responses/compact` | active | Context compaction → opaque blob to feed back verbatim (docs) |35| `POST /v1/chat/completions` | **legacy but fully working** | OpenAI-compatible; `reasoning_content` returned; no deprecation date. Multi-agent model rejected here. **(probed)** |36| `GET /v1/chat/deferred-completion/{request_id}` | active | For `deferred: true` requests (202 while pending, 24 h retention, single read) (docs) |37| `POST /v1/completions`, `POST /v1/messages`, `POST /v1/complete` | **deprecated/legacy** | Not supported by reasoning models; Anthropic compat fully deprecated (docs) |38| `GET /v1/models`, `GET /v1/models/{id}` | active | Minimal (id, aliases). **(probed)** |39| `GET /v1/language-models`, `GET /v1/language-models/{id}` | active | Rich metadata + pricing (§12). **Does NOT include `context_length`** despite docs example. **(probed)** |40| `GET /v1/image-generation-models`, `GET /v1/video-generation-models` (+`/{id}`) | active | Image: `image_price`, `pricing[]`, `max_prompt_length`. **(probed)** |41| `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). |42| `GET /v1/api-key`, `GET /v1/me` | active | See §1 |43| `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-*). |4445## 4. Chat Completions request/response (probed shapes)4647Request 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**).4849Roles: `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)**.5051Non-streaming response **(probed)**:5253```json54{55 "id": "…", "object": "chat.completion", "created": 1788842609, "model": "grok-4.6",56 "choices": [{ "index": 0, "finish_reason": "stop",57 "message": { "role": "assistant", "content": "2 + 2 equals 4.",58 "reasoning_content": "The user asked: …", "refusal": null } }],59 "usage": {60 "prompt_tokens": 652, "completion_tokens": 8, "total_tokens": 770,61 "prompt_tokens_details": { "text_tokens": 652, "audio_tokens": 0, "image_tokens": 0, "cached_tokens": 512 },62 "completion_tokens_details": { "reasoning_tokens": 110, "audio_tokens": 0,63 "accepted_prediction_tokens": 0, "rejected_prediction_tokens": 0 },64 "num_sources_used": 0, "cost_in_usd_ticks": 12440000 },65 "system_fingerprint": "fp_e41c6060b2628547", "service_tier": "default"66}67```6869- `total_tokens` = prompt + completion + **reasoning** (reasoning tokens are *not* inside `completion_tokens`; 652+8+110=770).70- `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.**71- `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).72- 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.7374## 5. Streaming protocol (chat completions) **(probed)**7576SSE, `Content-Type: text/event-stream`, no `event:` field, `data: {json}` lines, terminated by `data: [DONE]`.7778Chunk sequence for a reasoning model (grok-4.6):7980```json81data: {"id":"1039…","object":"chat.completion.chunk","created":1788842609,"model":"grok-4.6",82 "choices":[{"index":0,"delta":{"reasoning_content":"The","role":"assistant"}}],83 "system_fingerprint":"fp_e41c6060b2628547","service_tier":"default"}84data: {"…","choices":[{"index":0,"delta":{"reasoning_content":" user"}}],…}85…86data: {"…","choices":[{"index":0,"delta":{"content":"Bonjour"}}],…}87data: {"…","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],…}88data: {"…","choices":[],"usage":{"prompt_tokens":646,"completion_tokens":3,"total_tokens":1189,89 "prompt_tokens_details":{…,"cached_tokens":512},90 "completion_tokens_details":{"reasoning_tokens":540,…},"num_sources_used":0,"cost_in_usd_ticks":37820000},…}91data: [DONE]92```9394- Delta keys observed: `role`, `reasoning_content`, `content`, `tool_calls`. Non-reasoning model (grok-4.20-non-reasoning): only `role`/`content`.95- **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.96- 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.97- Chunks carry no per-chunk `usage` (docs' example showing usage on every chunk is outdated).9899## 6. Tool / function calling **(probed on all 6 models)**100101- 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).102- Round trip works with `assistant.tool_calls` + `{role:"tool", tool_call_id, content}` on every model.103- **Streaming: the whole tool call arrives in ONE chunk** (docs + probed), with full `arguments`:104105```json106{"delta":{"tool_calls":[{"id":"call-cbf4aac4-…-0","function":{"name":"get_weather","arguments":"{\"city\":\"Montreal\"}"},"index":0,"type":"function"}]}}107```108 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>`.109- 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)**.110- Tool arguments are implicitly `strict` (docs: "Tool calling… implicit strict: true").111112## 7. Structured output **(probed on all 6 models)**113114- 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).115- Responses API: `text: {format: {type:"json_schema", name, schema, strict:true}}` **(probed OK)**.116- OpenAI SDK helpers work: `client.chat.completions.parse(... response_format: zodResponseFormat(...))` (docs).117- 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."118119## 8. Reasoning controls **(probed matrix)**120121| Model | Reasoning | `reasoning_effort` accepted | `reasoning_content` returned |122|---|---|---|---|123| 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`) |124| grok-4.5 | always on | `low`, `medium`, `high` (default); `xhigh`/`minimal` accepted (docs: xhigh treated as high); `none` → 400 same message | yes |125| grok-4.3 | configurable | **`none` accepted → reasoning_tokens 0**, `low`, `medium`, `high`; `xhigh`/`minimal` accepted (mapped) | yes (empty when `none`) |126| grok-4.20-0309-reasoning | always on, fixed | **any value → 400** `Model grok-4.20-0309-reasoning does not support parameter reasoningEffort.` | yes |127| grok-4.20-0309-non-reasoning | none | any value → 400 same pattern | no (`reasoning_tokens: 0`) |128| grok-build-0.1 | always on, fixed | any value → 400 same pattern | yes |129| grok-4.20-multi-agent-0309 | always on | `low|medium` = 4 agents, `high|xhigh` = 16 agents (docs) | Responses only; sub-agents hidden |130131- Same behaviour via Responses API `reasoning: {effort}` **(probed)**: 4.20-*/build reject `low`; 4.3 accepts `none` (`reasoning.summary` becomes `"none"`, no reasoning item).132- 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`.133- No thinking-budget parameter exists. Reasoning tokens are billed at the output price.134135## 9. Sampling & other parameters — support matrix **(probed, chat completions)**136137| Param | 4.6 | 4.5 | 4.3 | 4.20-reasoning | 4.20-non-reasoning | build-0.1 |138|---|---|---|---|---|---|---|139| `temperature` (0–2) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |140| `top_p` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |141| `top_k` (undocumented on chat) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |142| `seed` (+ `system_fingerprint`) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |143| `max_completion_tokens` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |144| `max_tokens` (deprecated) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |145| `n: 2` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |146| `logprobs`/`top_logprobs` | ✓ (accepted; docs: ignored on 4.20+) | ✓ | ✓ | ✓ | ✓ | ✓ |147| `response_format` json_object / json_schema | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |148| `stop` | **400** | **400** | **400** | **400** | ✓ | **400** |149| `frequency_penalty` | **400** | **400** | **400** | **400** | **400** | **400** |150| `presence_penalty` | **400** | **400** | **400** | **400** | **400** | **400** |151| `reasoning_effort` | see §8 | | | 400 | 400 | 400 |152| `developer` role | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |153154Exact 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"`.155156## 10. Modalities, context, output limits157158- **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`.159- **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`).160- 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}`.161- **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×**.162- **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).163- 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.164165## 11. Server-side (agentic) tools, search, citations **(probed on grok-4.3 via /v1/responses)**166167- **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.**168- 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)**.169- 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`.170- **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.171- 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.172- No computer-use tool. Image generation available as a tool and as `/v1/images/generations`.173174## 12. Model listing & pricing units **(probed)**175176`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.**177178**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 ✓.179180| Model | ctx (docs) | in / cached / out ($/M, <200k) | ≥200k tokens | aliases |181|---|---|---|---|---|182| grok-4.6 | 500k | 2.00 / 0.50 / 6.00 | 4.00 / 1.00 / 12.00 | — |183| grok-4.5 | 500k | 2.00 / 0.30 / 6.00 | 4.00 / 0.60 / 12.00 | grok-4.5-latest, **grok-build-latest** |184| grok-4.3 | 1M | 1.25 / 0.20 / 2.50 | 2.50 / 0.40 / 5.00 | grok-4.3-latest |185| 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) |186| 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) |187| 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) |188| 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 |189190Image 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.191192## 13. Prompt caching & provider-side state193194- **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.195- **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.196197## 14. Errors, rate limits, retries198199Error body: `{"code": "<string>", "error": "<message>"}` (not OpenAI's `{error:{message,type,code}}` — the OpenAI SDK will surface `e.error` = that object, `e.message` derived).200201| Case **(probed)** | HTTP | body |202|---|---|---|203| Invalid key | **400** | `{"code":"invalid-argument","error":"Incorrect API key provided. You can obtain an API key from https://console.x.ai."}` |204| No Authorization header | 401 | `{"code":"unauthenticated:no-credentials","error":"No credentials presented."}` |205| Unknown model | 400 | `{"code":"invalid-argument","error":"Model not found: grok-99"}` |206| Unsupported param | 400 | `{"code":"invalid-argument","error":"Model X does not support parameter presencePenalty."}` |207| Malformed body | 422 (text/plain) | serde message |208| Bad image | 400 | `{"code":"invalid_image","error":"…"}` |209| Bad `include` | 400 | `{"code":"400","error":"Argument not supported: …"}` |210| Live search | 410 | `{"error":"Live search is deprecated. …"}` (no `code`) |211| Multi-agent on chat completions | 400 (text/plain) | `Multi Agent requests are not allowed on chat completions` |212| Rate limit | 429 (docs) | exponential backoff recommended |213214Docs 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.215216**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).217218## 15. Lifecycle / aliases219220- 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.221- `-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.222- grok-4.20-multi-agent is **beta** ("potential breaking changes"), Responses-only, no client tools.223- Docs pages under `/docs/*` are gone; only `/developers/*` is maintained.224225## 16. Exact streaming code that worked226227OpenAI SDK (`openai@7.10.0`) — chat completions with reasoning + usage:228229```ts230import OpenAI from "openai";231const client = new OpenAI({ apiKey: process.env.XAI_API_KEY, baseURL: "https://api.x.ai/v1", timeout: 3_600_000, maxRetries: 0 });232233const stream = await client.chat.completions.create({234 model: "grok-4.6",235 messages: [{ role: "user", content: "Say hello in French, 5 words max." }],236 stream: true,237 stream_options: { include_usage: true },238 max_completion_tokens: 200,239 reasoning_effort: "low", // only 4.6 / 4.5 / 4.3240}, { headers: { "x-grok-conv-id": sessionId } });241242for await (const chunk of stream) {243 const d = (chunk.choices[0]?.delta ?? {}) as any;244 if (d.reasoning_content) onThinking(d.reasoning_content);245 if (d.content) onText(d.content);246 if (d.tool_calls) onToolCalls(d.tool_calls); // arrives complete in one chunk247 if (chunk.choices[0]?.finish_reason) onFinish(chunk.choices[0].finish_reason);248 if (chunk.usage) onUsage(chunk.usage); // last chunk, choices: []249}250```251252Raw 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).253254## 17. Probe results table255256| # | Probe | Model(s) | Result |257|---|---|---|---|258| 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 |259| 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 |260| 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]` |261| 02 | param matrix (21 variants) | 6 models | see §9; penalties rejected everywhere; `stop` only on non-reasoning; `reasoning_effort` per §8; unknown params ignored |262| 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 |263| 04 | `response_format: json_schema` strict | 6 models | valid JSON matching schema on all |264| 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 |265| 06 | invalid key / no auth / unknown model / bad body / retired slugs | — | 400 invalid-argument / 401 / 400 / 422 text / 200 served by grok-4.3 |266| 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 |267| 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 |268| 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` |269| 10 | `tools:[{type:"live_search", sources:[{type:"web"}]}]` on chat | grok-4.3 | 410 Live search deprecated |270271## Documentation pages used (all fetched 2026-09-08)272273- https://docs.x.ai/docs/overview (redirect hub) · https://docs.x.ai/developers/quickstart · https://docs.x.ai/developers/community · https://docs.x.ai/developers/release-notes274- https://docs.x.ai/developers/models · https://docs.x.ai/developers/grok-4-6 · https://docs.x.ai/developers/pricing · https://docs.x.ai/developers/rate-limits · https://docs.x.ai/developers/migration/may-15-retirement275- https://docs.x.ai/developers/rest-api-reference/inference/chat-completions · https://docs.x.ai/developers/rest-api-reference/inference/responses · https://docs.x.ai/developers/rest-api-reference/inference/models · https://docs.x.ai/developers/rest-api-reference/inference/legacy · https://docs.x.ai/developers/rest-api-reference/inference/other276- https://docs.x.ai/developers/model-capabilities/text/generate-text · …/text/streaming · …/text/reasoning · …/text/structured-outputs · …/text/multi-agent · …/text/comparison (Responses vs Chat Completions migration) · https://docs.x.ai/developers/model-capabilities/legacy/chat-completions · https://docs.x.ai/developers/model-capabilities/images/understanding277- https://docs.x.ai/developers/tools/overview · …/tools/function-calling · …/tools/web-search · …/tools/x-search · …/tools/code-execution · …/tools/remote-mcp · …/tools/citations · …/tools/streaming-and-sync · …/tools/tool-usage-details278- https://docs.x.ai/developers/advanced-api-usage/prompt-caching (+ /how-it-works, /usage-and-pricing, /best-practices) · …/batch-api · …/priority-processing · …/deferred-chat-completions · …/context-compaction · https://docs.x.ai/developers/files · https://docs.x.ai/developers/debugging279- https://docs.x.ai/sitemap.xml (to discover the new paths) · https://ai-sdk.dev/providers/ai-sdk-providers/xai (Vercel provider)280- 404 at audit time: `/docs/guides/responses`, `/docs/api-reference`, `/docs/guides/migration`, `/docs/key-information/rate-limits`, `/docs/guides/prompt-caching`, `/developers/rest-api-reference/inference/legacy-deprecated`, `/developers/rest-api-reference/inference/account`, `/developers/advanced-api-usage/rate-limits`, `/developers/advanced-api-usage/migration`, `/developers/advanced-api-usage/error-handling`, `/developers/model-capabilities/text/chat-completions`.281