Python 88.3%
TypeScript 7.6%
Shell 4.1%
1#!/usr/bin/env python32"""docs/faq.md — answers computed from generated/*.json for the four providers (openai, anthropic, xai, gemini). Re-runnable."""3import json, collections, re4ROOT = "/Users/simon-pierreboucher/Desktop/doc-api"5TODAY = "2026-09-18"6M = json.load(open(f"{ROOT}/generated/models.json"))7E = json.load(open(f"{ROOT}/generated/endpoints.json"))8P = json.load(open(f"{ROOT}/generated/parameters.json"))9T = json.load(open(f"{ROOT}/generated/tools.json"))10S = json.load(open(f"{ROOT}/generated/streaming-events.json"))11W = json.load(open(f"{ROOT}/generated/webhook-events.json"))12PR = json.load(open(f"{ROOT}/generated/pricing.json"))13BH = json.load(open(f"{ROOT}/generated/fragments/headers/anthropic-beta-headers.json"))14EX = [r for r in json.load(open(f"{ROOT}/generated/examples-manifest.json")) if isinstance(r, dict) and r.get("file")]15PROVIDERS = ["openai", "anthropic", "xai", "gemini"]16MID = {r["id"]: r for r in M}1718def esc(s): return str(s).replace("|", "\\|").replace("\n", " ")19def st(r): return " · ".join(f"`{s}`" for s in r["status"])20def yn(v):21 if v is True: return "✔"22 if v is False: return "✘"23 if isinstance(v, str) and v.lower().startswith("supported"): return "✔*"24 return "?"25def truthy(v): return v is True or (isinstance(v, str) and v.lower().startswith("supported"))26def statuses_short(r):27 ex = [s for s in r["status"] if s not in ("DOCUMENTED","LIVE_VERIFIED","LIVE_DISCOVERED")]28 return f"`{r['id']}`" + (" (" + "/".join(ex) + ")" if ex else "")29def active(r):30 return "RETIRED" not in r["status"]31def price_rows(prov, svc_pat=None, dim_pat=None, tier=None):32 out = []33 for r in PR:34 if r["provider"] != prov: continue35 if svc_pat and not re.search(svc_pat, str(r["model_or_service"]), re.I): continue36 if dim_pat and not re.search(dim_pat, str(r["dimension"]), re.I): continue37 if tier and r.get("tier") != tier: continue38 out.append(r)39 return out40def fmt_price(r):41 if r is None or r.get("price") is None: return "—"42 p = r["price"]43 ps = f"${p:,.4f}".rstrip("0").rstrip(".") if isinstance(p, (int, float)) else str(p)44 return f"{ps} {r['unit']}"4546L = []47L.append("# FAQ — answering the owner's questions from the atlas data (4 providers)\n")48L.append(f"**Status:** every table on this page is **computed** from `generated/*.json` on {TODAY} (the jq/python used is shown so the answer can be re-derived after the next build); generator `scripts/generators/synth/faq.py`. Statuses are the record statuses. Where a question needs interpretation, the interpretation is stated. Providers: `openai`, `anthropic`, `xai`, `gemini`.")49L.append("**Sources:** `generated/models.json`, `generated/tools.json`, `generated/parameters.json`, `generated/endpoints.json`, `generated/streaming-events.json`, `generated/webhook-events.json`, `generated/pricing.json`, `generated/fragments/headers/anthropic-beta-headers.json`, `generated/examples-manifest.json`, plus the docs pages linked per answer.")50L.append(f"**Last verified:** {TODAY}\n")51L.append("Questions: [1](#q1) tool X + structured outputs + caching (Anthropic) · [2](#q2) image + reasoning + web search + streaming (OpenAI) · [2b](#q2b) the same combination on all four providers · [3](#q3) exact JSON for feature Y · [4](#q4) SSE / WebSocket events per call type · [5](#q5) endpoint that creates an agent session · [6](#q6) features needing a beta header / beta path · [7](#q7) which model supports combination Z · [8](#q8) which provider offers X and at what price · [9](#q9) where things live in the atlas\n")5253# ---------------- Q154L.append("## Q1. Which Anthropic model accepts tool X **with** structured outputs **and** prompt caching? <a id=\"q1\"></a>\n")55L.append("Interpretation: model records where `capabilities.structured_outputs_json`, `capabilities.strict_tool_use` and `capabilities.prompt_caching` are all `true`, crossed with the versioned tool `type`s listed in each model's `tools[]`. Retired ids excluded. All 13 active Claude models satisfy the SO + caching precondition, so the answer is really the tool column.\n")56L.append("```bash\njq -r '.[] | select(.provider==\"anthropic\" and (.status|index(\"RETIRED\")|not) and .capabilities.structured_outputs_json==true and .capabilities.prompt_caching==true) | [.id, ([.tools[].type]|join(\",\"))] | @tsv' generated/models.json\n```\n")57fams = [("web_search","web_search"),("web_fetch","web_fetch"),("code_exec","code_execution"),("tool_search","tool_search_tool"),("mcp_toolset","mcp_toolset"),("computer toolset GA","computer_toolset_20260801"),("computer beta","computer_2025"),("browser","browser_toolset"),("bash","bash_"),("text_editor","text_editor"),("memory","memory_"),("advisor","advisor_")]58L.append("| Model | Status | SO | strict | cache min tok | 1h TTL | " + " | ".join(n for n,_ in fams) + " |")59L.append("|---|---|---|---|---|---|" + "---|"*len(fams))60for r in M:61 if r["provider"] != "anthropic" or r.get("kind") != "snapshot" or "RETIRED" in r["status"]: continue62 c = r.get("capabilities") or {}63 if not (c.get("structured_outputs_json") and c.get("prompt_caching")): continue64 types = [t["type"] for t in (r.get("tools") or []) if isinstance(t, dict)]65 cells = ["✔" if any(t.startswith(pref) for t in types) else "✘" for _, pref in fams]66 L.append(f"| `{r['id']}` | {st(r)} | {yn(c.get('structured_outputs_json'))} | {yn(c.get('strict_tool_use'))} | {c.get('min_cacheable_tokens','—')} | {yn(c.get('prompt_caching_1h_ttl'))} | " + " | ".join(cells) + " |")67L.append("\nCaveats from the records: `strict` is rejected on toolsets (`mcp_toolset`, `computer_toolset_20260801`, `browser_toolset_20260801`) and on programmatic callers; changing `output_config.format` invalidates the prompt cache (docs/anthropic/prompt-caching.md); `tool_choice` `any`/`tool` is 400 on Fable 5.1 / Mythos 5.1; `mcp_toolset` and `advisor_20260301` still need beta headers (`mcp-client-2025-11-20`, `advisor-tool-2026-03-01`); Mythos ids are `ACCOUNT_RESTRICTED` (invite only). Tool-level compatibility lists: `generated/compatibility/anthropic-tool-model-matrix.json`, `generated/compatibility/model-tool-matrix.json`.\n")6869# ---------------- Q270L.append("## Q2. Which OpenAI models accept image input + reasoning + web search + streaming? <a id=\"q2\"></a>\n")71L.append("Interpretation: `capabilities.image_in`, `capabilities.reasoning`, `capabilities.tool_web_search` and `capabilities.streaming` all `true` in `generated/models.json`. Web search here means the hosted `web_search` tool on `/v1/responses` (Chat Completions only has search models). Retired ids are listed separately.\n")72L.append("```bash\njq -r '.[] | select(.provider==\"openai\" and .capabilities.image_in==true and .capabilities.reasoning==true and .capabilities.tool_web_search==true and .capabilities.streaming==true) | [.id, (.status|join(\",\")), (.context_window|tostring), (.capabilities.reasoning_effort_values//[]|join(\"/\"))] | @tsv' generated/models.json\n```\n")73L.append("| Model | Status | Context | Max out | Effort values | Structured outputs | Prompt caching | Code interp. | Computer | MCP | Std price in / out |")74L.append("|---|---|---|---|---|---|---|---|---|---|---|")75retired = []76for r in M:77 if r["provider"] != "openai": continue78 c = r.get("capabilities") or {}79 if not (c.get("image_in") is True and c.get("reasoning") is True and c.get("tool_web_search") is True and c.get("streaming") is True): continue80 if "RETIRED" in r["status"]:81 retired.append(r["id"]); continue82 p = ((r.get("pricing") or {}).get("standard") or {})83 ev = c.get("reasoning_effort_values")84 L.append(f"| `{r['id']}` | {st(r)} | {r.get('context_window') or '—'} | {r.get('max_output') or '—'} | {'/'.join(ev) if ev else '(model default only)'} | {yn(c.get('structured_outputs'))} | {yn(c.get('prompt_caching'))} | {yn(c.get('tool_code_interpreter'))} | {yn(c.get('tool_computer_use'))} | {yn(c.get('tool_mcp'))} | {p.get('input','—')} / {p.get('output','—')} |")85L.append(f"\nAlso matching but **RETIRED** (excluded): {', '.join('`'+x+'`' for x in retired)}. Caveats: `o4-mini`, `o3-2025-04-16`, `gpt-5-*-2025-08-07` snapshots are `DEPRECATED` with shutdown dates (see docs/openai/deprecations.md); web search is not supported with `gpt-5` `reasoning.effort: minimal` and may degrade with `none` (docs/tools/openai/web-search.md); `gpt-5.6-cyber`, `gpt-daybreak-*` are gated (`ACCOUNT_RESTRICTED`/`DOCUMENTED`).\n")8687# ---------------- Q2b cross-provider88L.append("## Q2b. The same combination on all four providers — image input + reasoning + vendor web search + structured outputs + streaming <a id=\"q2b\"></a>\n")89L.append("Interpretation per provider (capability keys differ): **openai** `image_in`, `reasoning`, `tool_web_search`, `structured_outputs`, `streaming`; **anthropic** `vision`, (`thinking_adaptive` or `thinking_extended_manual_budget`), `web_search`, `structured_outputs_json` (streaming is universal); **xai** `image_input`, `reasoning`, `web_search`, `structured_outputs`, `streaming`; **gemini** `image_input`, `thinking` (true or a 'Supported…' string), `google_search_grounding`, `structured_output` (streaming is universal). Retired ids, aliases and id-only records excluded; xAI `retired_redirect`/`legacy` kinds and Gemini `alias`/`agent` kinds excluded.\n")90L.append("```bash\njq -r '.[] | select(.provider==\"xai\" and .kind==\"model\" and .capabilities.image_input==true and .capabilities.reasoning==true and .capabilities.web_search==true and .capabilities.structured_outputs==true) | .id' generated/models.json\njq -r '.[] | select(.provider==\"gemini\" and (.kind==\"stable\" or .kind==\"preview\") and (.status|index(\"RETIRED\")|not) and .capabilities.image_input==true and (.capabilities.thinking==true or (.capabilities.thinking|type==\"string\" and startswith(\"Supported\"))) and .capabilities.google_search_grounding==true and .capabilities.structured_output==true) | .id' generated/models.json\n```\n")91L.append("| Provider | Matching models (status other than DOCUMENTED/LIVE_*) | Reasoning control | Web search tool | Structured output parameter |\n|---|---|---|---|---|")92def combo_openai(c, r): return c.get("image_in") is True and c.get("reasoning") is True and c.get("tool_web_search") is True and c.get("structured_outputs") is True and c.get("streaming") is True93def combo_ant(c, r): return c.get("vision") is True and (c.get("thinking_adaptive") is True or c.get("thinking_extended_manual_budget") is True) and c.get("web_search") is True and c.get("structured_outputs_json") is True94def combo_xai(c, r): return r.get("kind") == "model" and c.get("image_input") is True and c.get("reasoning") is True and c.get("web_search") is True and c.get("structured_outputs") is True and c.get("streaming") is True95def combo_gem(c, r): return r.get("kind") in ("stable","preview") and c.get("image_input") is True and truthy(c.get("thinking")) and c.get("google_search_grounding") is True and c.get("structured_output") is True96combos_cross = [97 ("openai", combo_openai, "`reasoning.effort` (Responses) / `reasoning_effort` (Chat)", "`tools[type=web_search]`", "`text.format {type: json_schema}`"),98 ("anthropic", combo_ant, "`thinking {type: adaptive \\| enabled}` + `output_config.effort`", "`tools[type=web_search_20260318]`", "`output_config.format {type: json_schema}`"),99 ("xai", combo_xai, "`reasoning.effort` / `reasoning_effort` (low…xhigh; `none` LIVE_DISCOVERED on grok-4.3; 400 on 4.20-reasoning / build)", "`tools[type=web_search]` ($5/1k) + `x_search`", "`text.format {type: json_schema}` / `response_format.json_schema`"),100 ("gemini", combo_gem, "`generationConfig.thinkingConfig {thinkingLevel \\| thinkingBudget, includeThoughts}`", "`tools[{googleSearch:{}}]` (ACCOUNT_RESTRICTED on this key; 5,000 free/month on 3.x then $14/1k)", "`generationConfig.responseMimeType: application/json` + `responseJsonSchema` / `responseSchema`"),101]102for prov, pred, rc, ws, so in combos_cross:103 ids = [r for r in M if r["provider"] == prov and active(r) and r.get("kind") not in ("alias","id_only","retired_redirect","legacy","agent","service") and pred(r.get("capabilities") or {}, r)]104 L.append(f"| {prov} | {', '.join(statuses_short(r) for r in ids) or '— none'} | {rc} | {ws} | {so} |")105L.append("\nReading notes: OpenAI snapshots (`gpt-5.4-2026-03-05`…) appear as separate callable ids; Anthropic aliases (`claude-haiku-4-5`) are excluded (capabilities live on the snapshot); Gemini image-output models (`gemini-3.1-flash-image`, `gemini-3-pro-image`) support Google Search grounding and thinking but not structured output, so they drop out; `gemini-2.5-*` match on paper but are `ACCOUNT_RESTRICTED` ('no longer available to new users'); xAI `grok-4.20-0309-non-reasoning` drops out on `reasoning: false`.\n")106107# ---------------- Q3108L.append("## Q3. What is the exact JSON to call feature Y? <a id=\"q3\"></a>\n")109L.append("Three lookups, in order of precision:\n")110L.append(f"1. **Parameter rows** — `generated/parameters.json` ({len(P):,} rows) is keyed by `endpoint` (`METHOD /path`) and dotted `parameter` path (Gemini uses the REST camelCase names, e.g. `generationConfig.thinkingConfig.thinkingLevel`), with `type`, `required`, `default`, `enum`, `beta_header`, `status`, `description`.\n")111L.append("```bash\n# every parameter of the Anthropic structured-output block\njq '[.[] | select(.provider==\"anthropic\" and .endpoint==\"POST /v1/messages\" and (.parameter|startswith(\"output_config.format\")))]' generated/parameters.json\n# the OpenAI equivalent\njq '[.[] | select(.provider==\"openai\" and .endpoint==\"POST /v1/responses\" and (.parameter|startswith(\"text.format\")))]' generated/parameters.json\n# xAI Responses reasoning + caching knobs\njq '[.[] | select(.provider==\"xai\" and .endpoint==\"POST /v1/responses\" and (.parameter|test(\"^(reasoning|prompt_cache_key|store|previous_response_id|max_turns)\")))]' generated/parameters.json\n# Gemini thinking and structured-output knobs\njq '[.[] | select(.provider==\"gemini\" and .endpoint==\"POST /v1beta/models/{model}:generateContent\" and (.parameter|test(\"thinkingConfig|responseJsonSchema|responseMimeType|cachedContent\")))]' generated/parameters.json\n# all tool entry shapes accepted by OpenAI Responses\njq -r '.[] | select(.provider==\"openai\" and .endpoint==\"POST /v1/responses\" and (.parameter|test(\"^tools\\\\[\\\\]\\\\(\"))) | .parameter' generated/parameters.json\n```\n")112L.append(f"2. **Tool records** — `generated/tools.json` has `parameters_schema`, `result_shape`, `examples{{curl,python,typescript}}` per exact tool `type` ({len(T)} records: OpenAI {sum(1 for t in T if t['provider']=='openai')}, Anthropic {sum(1 for t in T if t['provider']=='anthropic')}, xAI {sum(1 for t in T if t['provider']=='xai')}, Gemini {sum(1 for t in T if t['provider']=='gemini')}).\n")113L.append("```bash\njq '.[] | select(.type==\"web_search_20260318\") | {parameters_schema, result_shape, beta_header, examples}' generated/tools.json\njq '.[] | select(.provider==\"xai\" and .type==\"x_search\") | {parameters_schema, result_shape, billing}' generated/tools.json\njq '.[] | select(.provider==\"gemini\" and .type==\"googleSearch\") | {parameters_schema, result_shape, billing, limitations}' generated/tools.json\n```\n")114L.append("3. **Runnable examples** — `generated/examples-manifest.json` maps features to files under `examples/<provider>/<area>/` with their verification status. Selection:\n")115L.append("| Feature | OpenAI | Anthropic | xAI | Gemini |\n|---|---|---|---|---|")116def ex(path):117 if not path or path == "—": return "—"118 if not path.startswith("examples/") or path.endswith("/"): return f"`{path}`"119 r = next((e for e in EX if e["file"] == path), None)120 return f"`{path}` ({r['status']})" if r else f"`{path}` (not in manifest)"121quads = [122 ("Minimal call", "examples/openai/responses/", "examples/anthropic/messages/", "examples/xai/responses/minimal.py", "examples/gemini/generate-content/minimal.py"),123 ("Structured outputs", "examples/openai/responses/structured_output.py", "examples/anthropic/structured-output/", "examples/xai/structured-output/json_schema.sh", "examples/gemini/structured-output/json_schema.py"),124 ("Streaming", "examples/openai/responses/streaming.py", "examples/anthropic/streaming/sdk_stream.py", "examples/xai/streaming/responses_stream.py", "examples/gemini/streaming/stream.py"),125 ("Multi-turn state", "examples/openai/responses/previous_response_id.py", "examples/shared/tool-loop/anthropic_tool_loop.py", "examples/xai/responses/compact.py", "examples/gemini/generate-content/system_multiturn.py"),126 ("Tool loop", "examples/shared/tool-loop/openai_tool_loop.py", "examples/shared/tool-loop/anthropic_tool_loop.py", "examples/xai/responses/tool_loop.py", "examples/shared/tool-loop/gemini_tool_loop.py"),127 ("Web search", "examples/openai/tools/web-search/", "examples/anthropic/web-search/web_search.py", "examples/xai/tools/web-search/web_search.py", "examples/gemini/tools/google-search/grounding.py"),128 ("Code execution", "examples/openai/tools/code-interpreter/", "examples/anthropic/code-execution/code_execution.py", "examples/xai/tools/code-execution/code_interpreter.py", "examples/gemini/tools/code-execution/code_execution.py"),129 ("MCP", "examples/openai/tools/mcp-and-connectors/", "examples/anthropic/mcp/mcp_connector.py", "examples/xai/tools/mcp/deepwiki.py", "docs/tools/gemini/mcp.md"),130 ("File search / RAG", "docs/openai/vector-stores.md", "docs/anthropic/citations.md", "examples/xai/tools/collections-search/file_search.py", "examples/gemini/file-search/file_search_lifecycle.py"),131 ("Batch", "examples/openai/batch/batch-lifecycle.py", "examples/anthropic/batch/batch_lifecycle.py", "examples/xai/batches/lifecycle.py", "examples/gemini/batch/batch_inline.py"),132 ("Agents / sessions", "examples/openai/agents/", "examples/anthropic/agents/create_agent_session_stream.py", "examples/xai/responses/tool_loop.py", "examples/gemini/interactions/interactions_basic.py"),133 ("Prompt / context caching", "docs/openai/prompt-caching.md §5 (live pair)", "examples/anthropic/prompt-caching/", "docs/xai/prompt-caching.md", "examples/gemini/context-caching/cache_lifecycle.py"),134 ("Reasoning / thinking", "examples/openai/responses/reasoning_summary.py", "examples/anthropic/thinking/", "docs/xai/reasoning.md", "examples/gemini/thinking/thinking.py"),135 ("Image input", "examples/openai/responses/image_input.py", "examples/anthropic/vision/", "examples/xai/responses/image_input.sh", "examples/gemini/multimodal/inline_image_pdf.py"),136 ("Realtime / Live voice", "docs/openai/realtime.md", "—", "docs/xai/voice.md", "examples/gemini/live/live_ws_audio.py"),137 ("Image generation", "docs/openai/images.md", "—", "docs/xai/images.md", "examples/gemini/image-generation/generate.py"),138 ("Embeddings", "docs/openai/embeddings.md", "—", "docs/xai/index.md (ACCOUNT_RESTRICTED)", "examples/gemini/embeddings/embed.py"),139 ("Skills", "docs/openai/skills-api.md", "examples/anthropic/skills/skills_lifecycle.py", "docs/xai/skills-api.md (ACCOUNT_RESTRICTED)", "—"),140 ("Webhook verification", "examples/openai/webhooks/offline_test.py", "—", "docs/xai/voice.md (SIP `realtime.call.incoming`)", "docs/gemini/interactions-api.md (`webhook_config`)"),141]142for feat, a, b, c, d in quads:143 L.append(f"| {feat} | {ex(a)} | {ex(b)} | {ex(c)} | {ex(d)} |")144L.append("\nMinimal verified bodies (from the live probes recorded in the docs):\n")145L.append("```json\n// OpenAI — POST /v1/responses (structured output, LIVE_VERIFIED gpt-5.4-nano)\n{\"model\":\"gpt-5.4-nano\",\"input\":\"Reply with OK.\",\"max_output_tokens\":32,\n \"text\":{\"format\":{\"type\":\"json_schema\",\"name\":\"ok_reply\",\"strict\":true,\n \"schema\":{\"type\":\"object\",\"properties\":{\"answer\":{\"type\":\"string\"}},\"required\":[\"answer\"],\"additionalProperties\":false}}}}\n```\n```json\n// Anthropic — POST /v1/messages (structured output, LIVE_VERIFIED claude-haiku-4-5-20251001)\n{\"model\":\"claude-haiku-4-5-20251001\",\"max_tokens\":100,\n \"messages\":[{\"role\":\"user\",\"content\":\"Reply with OK.\"}],\n \"output_config\":{\"format\":{\"type\":\"json_schema\",\n \"schema\":{\"type\":\"object\",\"properties\":{\"answer\":{\"type\":\"string\"}},\"required\":[\"answer\"],\"additionalProperties\":false}}}}\n```\n```json\n// xAI — POST /v1/responses (structured output, LIVE_VERIFIED grok-4.3; reasoning tokens are billed on every call)\n{\"model\":\"grok-4.3\",\"input\":\"Reply with OK.\",\"max_output_tokens\":64,\"reasoning\":{\"effort\":\"low\"},\n \"text\":{\"format\":{\"type\":\"json_schema\",\"name\":\"ok_reply\",\"strict\":true,\n \"schema\":{\"type\":\"object\",\"properties\":{\"answer\":{\"type\":\"string\"}},\"required\":[\"answer\"],\"additionalProperties\":false}}}}\n```\n```json\n// Gemini — POST /v1beta/models/gemini-3.5-flash-lite:generateContent (structured output, LIVE_VERIFIED; header x-goog-api-key)\n{\"contents\":[{\"role\":\"user\",\"parts\":[{\"text\":\"Reply with OK.\"}]}],\n \"generationConfig\":{\"maxOutputTokens\":64,\"thinkingConfig\":{\"thinkingLevel\":\"minimal\"},\n \"responseMimeType\":\"application/json\",\n \"responseJsonSchema\":{\"type\":\"object\",\"properties\":{\"answer\":{\"type\":\"string\"}},\"required\":[\"answer\"]}}}\n```\n")146L.append("Per-topic parameter mappings with full JSON quads: [state-management](comparisons/state-management.md), [tool-execution](comparisons/tool-execution.md), [caching-and-reasoning](comparisons/caching-and-reasoning.md), [streaming](comparisons/streaming.md), [agents-platforms](comparisons/agents-platforms.md), [realtime-and-media](comparisons/realtime-and-media.md).\n")147148# ---------------- Q4149L.append("## Q4. Which SSE / WebSocket events can arrive during this call? <a id=\"q4\"></a>\n")150L.append(f"Computed from `generated/streaming-events.json` ({len(S)} records: " + ", ".join(f"{p} {sum(1 for r in S if r['provider']==p)}" for p in PROVIDERS) + "; `api` field = call type, `direction` = server→client unless noted). Event names are listed exactly as recorded; ✔ marks events observed live in this run (`LIVE_VERIFIED`).\n")151L.append("```bash\njq -r '.[] | select(.provider==\"xai\" and .api==\"responses\") | .event' generated/streaming-events.json\njq -r '.[] | select(.provider==\"gemini\" and .api==\"live\" and .direction==\"server→client\") | .event' generated/streaming-events.json\n```\n")152groups = collections.OrderedDict()153for r in S:154 groups.setdefault((r["provider"], r["api"], r["direction"]), []).append(r)155order = [("openai","responses","server→client"),("openai","responses-websocket","server→client"),("openai","responses-websocket","client→server"),("openai","chat_completions","server→client"),("openai","completions","server→client"),("openai","agents (Agents API sessions; SSE)","server→client"),("openai","agents (POST /v1/agents/sessions/{session_id}/events)","client→server"),("openai","realtime","server→client"),("openai","realtime","client→server"),("openai","realtime-translation","server→client"),("openai","realtime-translation","client→server"),("openai","live","server→client"),("openai","live","client→server"),("openai","audio-transcriptions","server→client"),("openai","audio-speech","server→client"),("openai","POST /v1/images/generations","server→client"),("openai","POST /v1/images/edits","server→client"),156 ("anthropic","POST /v1/messages (stream=true)","server→client"),("anthropic","messages","server→client"),("anthropic","managed-agents","server→client"),("anthropic","managed-agents","client→server"),("anthropic","POST /v1/complete (stream=true)","server→client"),157 ("xai","responses","server→client"),("xai","chat_completions","server→client"),("xai","messages","server→client"),("xai","realtime","server→client"),("xai","realtime","client→server"),("xai","realtime","server→client (webhook)"),("xai","tts","client→server"),("xai","tts","server→client"),("xai","stt","client→server"),("xai","stt","server→client"),158 ("gemini","generate-content","server→client"),("gemini","interactions","server→client"),("gemini","live","server→client"),("gemini","live","client→server"),("gemini","lyria-realtime","server→client"),("gemini","lyria-realtime","client→server")]159L.append("| Provider | Call type (`api`) | Direction | # | Events (✔ = LIVE_VERIFIED) |\n|---|---|---|---|---|")160seen = set()161for key in order + [k for k in groups if k not in order]:162 if key not in groups or key in seen: continue163 seen.add(key)164 rs = groups[key]165 names, dedup = [], set()166 for r in rs:167 n = r["event"]168 if n in dedup: continue169 dedup.add(n)170 names.append(("✔" if "LIVE_VERIFIED" in (r.get("status") or []) else "") + f"`{esc(n)}`")171 L.append(f"| {key[0]} | `{esc(key[1])}` | {key[2]} | {len(dedup)} | {', '.join(names)} |")172L.append("\nHow to read the four core streams: **OpenAI Responses** is `event: <type>` + `data:` with `sequence_number`, terminal `response.completed|failed|incomplete|error`, no `[DONE]`; **Anthropic Messages** is `event: <type>` + `data:` (`data.type == event`), fixed skeleton `message_start → content_block_start/delta/stop × N → message_delta → message_stop`, `ping` anywhere, no `[DONE]`; **xAI** reuses both shapes — `/v1/responses` emits the OpenAI Responses event names (with `sequence_number`, no `[DONE]`), `/v1/chat/completions` emits data-only `chat.completion.chunk` + `data: [DONE]`, and `/v1/messages` emits the Anthropic skeleton (no `ping`); **Gemini** `streamGenerateContent` has **no event names at all** — a JSON array of `GenerateContentResponse` chunks by default or `data:` lines with `?alt=sse` (thought parts flagged `thought: true`, end detected by `finishReason`), while the Interactions API uses named events (`interaction.start … content.delta … interaction.complete`). Details: [streaming comparison](comparisons/streaming.md).\n")173L.append("The Anthropic `messages` group is the *advanced* fragment (thinking/citations/compaction variants of the same core events); the `POST /v1/messages (stream=true)` group is the core catalogue — both describe one stream. Voice WebSockets (OpenAI Realtime/Live, xAI realtime/tts/stt, Gemini Live/Lyria RealTime) are bidirectional message families, not SSE.\n")174175# ---------------- Q5176L.append("## Q5. Which endpoint creates an agent session on each provider? <a id=\"q5\"></a>\n")177L.append("```bash\njq '.[] | select((.provider==\"openai\" and .path==\"/v1/agents/sessions\") or (.provider==\"anthropic\" and .path==\"/v1/sessions\") or (.provider==\"gemini\" and .path==\"/v1beta/interactions\") or (.provider==\"xai\" and .path==\"/v1/responses\")) | select(.method==\"POST\") | {provider, method, path, status, beta_header, verification}' generated/endpoints.json\n```\n")178L.append("| Provider | Endpoint | Beta gate | Status | Live result | Required body | Notes |\n|---|---|---|---|---|---|---|")179def ep(prov, method, path):180 return next((r for r in E if r["provider"]==prov and r["method"]==method and r["path"]==path), None)181rows5 = [182 ("openai", ep("openai","POST","/v1/agents/sessions"), "`environment` (required: `none` \\| `openai_hosted` \\| `self_hosted`), `agent` or `agent_id`, `input` (required when environment is `none`), `stream`, `vault_ids`, `metadata`", "201 `agent.session`; first turn starts immediately when `input` is given; send later input via `POST …/sessions/{id}/events` (`agent.session.input.message`); SSE with `stream:true` or `GET …/events?stream=true`"),183 ("anthropic", ep("anthropic","POST","/v1/sessions"), "`agent` (id or `{type:agent,id,version}` or `agent_with_overrides`), `environment_id` (required), optional `initial_events[]` (≤50), `resources[]`, `vault_ids[]`, `budget`, `title`, `metadata`", "200 `session` (`sesn_…`); starts `idle` unless `initial_events`; send `user.message` via `POST /v1/sessions/{id}/events`; stream `GET /v1/sessions/{id}/events/stream`; agents and environments must exist first (`POST /v1/agents`, `POST /v1/environments`)"),184 ("xai", ep("xai","POST","/v1/responses"), "`model`, `input`; agentic behaviour comes from `tools[]` (server-side `web_search`, `x_search`, `code_interpreter`, `file_search`, `mcp`, `image_generation` + your `function`/`shell` tools), `max_turns`, `store` (default true), `previous_response_id`", "xAI has **no separate agent/session resource**: the Responses call itself runs the server-side agentic loop (bounded by `max_turns`) and the stored response (`store:true`, 30-day retention) is the session — continue with `previous_response_id`, inspect with `GET /v1/responses/{id}` / `…/input_items`, shrink with `POST /v1/responses/compact`. `grok-4.20-multi-agent-0309` (BETA) fans out to 4 or 16 agents inside one call. Grok Build is a CLI product, not an API"),185 ("gemini", ep("gemini","POST","/v1beta/interactions"), "`model` **or** `agent` (`deep-research-preview-04-2026`, `deep-research-max-preview-04-2026`, `antigravity-preview-09-2026`, or a custom agent id from `POST /v1beta/agents`), `input`, optional `previous_interaction_id`, `store` (default true), `stream`, `background`, `tools[]`, `generation_config`, `agent_config`, `environment`, `webhook_config`", "200 `interaction` (`id`, `status`, `steps[]`/`outputs`); stateful chaining via `previous_interaction_id`; poll/resume with `GET /v1beta/interactions/{id}`, `POST …/cancel`, `DELETE`; long runs with `background:true` (Deep Research); sandboxed agents need `POST /v1beta/environments` (LIVE_VERIFIED) and can be scheduled with `/v1beta/triggers` (PREVIEW). `/v1/interactions` is documented GA but UNVERIFIED here. The **Live API** WebSocket (`BidiGenerateContent`, `setup` message) is the other session-shaped surface (voice)"),186]187for prov, r, body, notes in rows5:188 if not r: continue189 v = r.get("verification") or {}190 gate = f"`{r.get('beta_header')}`" if r.get("beta_header") else ("none (no beta headers on xAI)" if prov=="xai" else ("`/v1beta` path version" if prov=="gemini" else "—"))191 L.append(f"| {prov} | `{r['method']} {r['path']}` | {gate} | {st(r)} | {v.get('result')} HTTP {v.get('http_status')} | {body} | {notes} |")192L.append("\nPrerequisite creates: OpenAI `POST /v1/agents` (LIVE_VERIFIED 201) or an inline `agent`; Anthropic `POST /v1/agents` (LIVE_VERIFIED 200) **and** `POST /v1/environments` (LIVE_VERIFIED 200); Gemini `POST /v1beta/agents` (custom managed agents, PREVIEW, not tested) and `POST /v1beta/environments` (LIVE_VERIFIED). Not to be confused with: OpenAI **Conversations** (`POST /v1/conversations` — a state container for Responses, not an agent), **Realtime** client secrets (`POST /v1/realtime/client_secrets`), **ChatKit** sessions, the retired **Assistants** threads/runs; xAI `POST /v1/realtime/client_secrets` (voice session token); Gemini `POST /v1beta/auth_tokens` (ephemeral Live token) and `cachedContents` (a prefix cache, not a session). Full comparison: [agents-platforms](comparisons/agents-platforms.md).\n")193194# ---------------- Q6195L.append("## Q6. Which feature needs a beta header (or a beta path / preview model)? <a id=\"q6\"></a>\n")196L.append("The four providers gate pre-GA features differently: **Anthropic** by `anthropic-beta` header values (per parameter/tool/endpoint), **OpenAI** by `OpenAI-Beta` header values (per surface), **Gemini** by the **URL version** (`/v1beta` vs `/v1`) and `-preview` / `-exp` model ids (no header exists), **xAI** by nothing visible — no version or beta headers; gated features answer 403 ('alpha users') or 404 (ACL) and are recorded `ACCOUNT_RESTRICTED`.\n")197L.append("### Anthropic — `anthropic-beta` values that are still gating (status BETA / PREVIEW / DOCUMENTED-only), from `generated/fragments/headers/anthropic-beta-headers.json`, cross-referenced with the `beta_header` field of `generated/parameters.json`, `tools.json` and `endpoints.json`\n")198pb = collections.defaultdict(set)199for r in P:200 b = r.get("beta_header")201 if b and r["provider"]=="anthropic":202 for part in str(b).replace(" (or ", ",").replace(")", "").split(","):203 part = part.strip()204 if part: pb[part].add(f"{r['endpoint']} → `{r['parameter']}`")205eb = collections.defaultdict(set)206for r in E:207 b = r.get("beta_header")208 if b and r["provider"]=="anthropic":209 eb[str(b)].add(f"{r['method']} {r['path']}")210tb = collections.defaultdict(set)211for r in T:212 if r.get("beta_header"): tb[r["beta_header"]].add(r["type"])213L.append("| Header value | Feature | Status | Models / scope | Gated parameters (sample) | Gated endpoints | Gated tool types |\n|---|---|---|---|---|---|---|")214for v in BH["values"]:215 stt = v.get("status") or []216 if not any(s in ("BETA","PREVIEW","DOCUMENTED","LIVE_DISCOVERED") for s in stt): continue217 name = v["name"]218 params = sorted(pb.get(name, set()))219 ps = "; ".join(esc(p) for p in params[:3]) + (f" … (+{len(params)-3})" if len(params) > 3 else "")220 eps = sorted(eb.get(name, set()))221 es = "; ".join(f"`{e}`" for e in eps[:3]) + (f" … (+{len(eps)-3})" if len(eps) > 3 else "")222 models = v.get("models")223 if isinstance(models, list): models = ", ".join(models[:4]) + (" …" if len(models) > 4 else "")224 L.append(f"| `{name}` | {esc(v.get('feature',''))} | {' · '.join('`'+s+'`' for s in stt)} | {esc(models or '—')} | {ps or '—'} | {es or '—'} | {', '.join('`'+t+'`' for t in sorted(tb.get(name, set()))) or '—'} |")225L.append("\nGraduated (header now optional — `LEGACY`): " + ", ".join(f"`{v['name']}`" for v in BH["values"] if "LEGACY" in (v.get("status") or [])) + ". Retired / deprecated: " + ", ".join(f"`{v['name']}`" for v in BH["values"] if any(s in ("RETIRED","DEPRECATED") for s in (v.get("status") or []))) + ".\n")226L.append("Live findings worth knowing: `mcp_servers` without a header → 400 that names `mcp-client-2026-09-15`, which is itself rejected (use `mcp-client-2025-11-20`); memory-store calls reject the pair `agent-memory-2026-07-22` + `managed-agents-2026-04-01`; `speed` and `context_management` are rejected with 400 'Extra inputs are not permitted' without their headers; `output_format` (legacy) → 400 even with `structured-outputs-2025-11-13`.\n")227L.append("### OpenAI — `OpenAI-Beta` values (from `generated/endpoints.json` / `headers.json`)\n")228L.append("| Header value | Surface | Endpoints | Status |\n|---|---|---|---|")229oeb = collections.defaultdict(list)230for r in E:231 b = r.get("beta_header")232 if b and r["provider"]=="openai": oeb[str(b)].append(r)233for k, rs in sorted(oeb.items()):234 sts = sorted({s for r in rs for s in r["status"]})235 L.append(f"| `{esc(k)}` | {esc(rs[0]['api_family'])} | {len(rs)} (e.g. `{rs[0]['method']} {rs[0]['path']}`) | {' · '.join('`'+s+'`' for s in sts)} |")236L.append("\nEverything else on OpenAI (Responses, hosted tools incl. MCP, structured outputs, prompt caching, Batch, Files, Webhooks, Realtime GA) needs **no** beta header; `POST /v1/responses?beta=true` exposes a beta schema (`multi_agent`) with an optional body-level `openai-beta[]`. `OpenAI-Beta: realtime=v1` is legacy (the beta endpoints return 404).\n")237L.append("### Gemini — `/v1beta` vs `/v1`, `-preview` models, BETA/PREVIEW endpoints (from `generated/endpoints.json`, `models.json`)\n")238gv1 = sorted({r["path"] for r in E if r["provider"]=="gemini" and r["path"].startswith("/v1/")})239gfam_beta_only = sorted({r["api_family"] for r in E if r["provider"]=="gemini" and r["path"].startswith("/v1beta/")} - {r["api_family"] for r in E if r["provider"]=="gemini" and r["path"].startswith("/v1/")})240L.append("```bash\n# Gemini surfaces that exist only under /v1beta\njq -r '[.[] | select(.provider==\"gemini\" and (.path|startswith(\"/v1beta/\")))] | group_by(.api_family) | .[] | \"\\(.[0].api_family)\\t\\(length)\"' generated/endpoints.json\n# preview / experimental Gemini model ids still served\njq -r '.[] | select(.provider==\"gemini\" and (.kind==\"preview\" or .kind==\"experimental\") and (.status|index(\"RETIRED\")|not)) | .id' generated/models.json\n```\n")241L.append(f"- **Stable `/v1` surface** ({len(gv1)} paths recorded): " + ", ".join(f"`{p}`" for p in gv1) + ". Everything else — including **explicit context caching (`cachedContents`), Files, File Search stores, Batch, embeddings, `countTokens`, Live API (`v1beta` WebSocket), ephemeral `auth_tokens`, Veo `predictLongRunning`, the OpenAI-compatibility layer and the managed-agents preview (`agents`, `environments`, `credentials`, `triggers`)** — is **v1beta-only** (families: " + ", ".join(f"`{f}`" for f in gfam_beta_only) + "). `/v1/cachedContents` returns 404.")242gbeta_eps = [r for r in E if r["provider"]=="gemini" and ("BETA" in r["status"] or "PREVIEW" in r["status"])]243fam_counts = collections.Counter(r["api_family"] for r in gbeta_eps)244L.append(f"- **Endpoints recorded BETA/PREVIEW** ({len(gbeta_eps)}): " + ", ".join(f"`{f}` ({n})" for f, n in sorted(fam_counts.items())) + ". `POST /v1beta/interactions` is BETA + LIVE_VERIFIED; its `/v1/interactions` twin is documented GA but UNVERIFIED.")245gprev = [r for r in M if r["provider"]=="gemini" and r.get("kind") in ("preview","experimental") and active(r)]246L.append(f"- **Preview / experimental model ids still served** ({len(gprev)}): " + ", ".join(statuses_short(r) for r in gprev) + ". Preview models 'may be used in production' but carry more restrictive limits and can be deprecated with two weeks' notice; `-latest` aliases (`gemini-flash-latest`, `gemini-pro-latest`, `gemini-flash-lite-latest`) are hot-swapped.")247L.append("- **Gated by account tier rather than header**: Pro models and paid-only media models return 429 `limit: 0` on the free tier (`gemini-3.1-pro-preview`, `gemini-pro-latest`, `cachedContents` create — all `ACCOUNT_RESTRICTED` here); Gemini 2.5 models return 404 'no longer available to new users'.\n")248L.append("### xAI — no beta headers; gating is by ACL / alpha program (from `generated/endpoints.json`, `tools.json`, `headers.json`)\n")249xres = [r for r in E if r["provider"]=="xai" and "ACCOUNT_RESTRICTED" in r["status"]]250xfam = collections.Counter(r["api_family"] for r in xres)251L.append(f"- `headers.json` records **no versioning / beta headers** for xAI: features are selected by body fields (`service_tier`, `reasoning_effort`, `store`, `prompt_cache_key`, `include`) or by base URL (`us.api.x.ai`, `management-api.x.ai`).")252L.append(f"- **ACCOUNT_RESTRICTED endpoints** ({len(xres)}): " + ", ".join(f"`{f}` ({n})" for f, n in sorted(xfam.items())) + " — the Management API needs a separate Management key (401 code 16 with an inference key), `/v1/embeddings` and `grok-embedding-small` return 404 for this team, `/v1/skills` returns 404, custom-voice creation is Enterprise-only, and Collections are documented on `management-api.x.ai` although the same paths answered on `api.x.ai` (LIVE_DISCOVERED).")253xtools = [t for t in T if t["provider"]=="xai" and ("ACCOUNT_RESTRICTED" in t["status"] or "RETIRED" in t["status"])]254L.append("- **Tools**: " + "; ".join(f"`{t['type']}` → {' · '.join('`'+s+'`' for s in t['status'])}" for t in xtools) + ". `tool_search` (+ `defer_loading`) answers 403 'only available for alpha users'; `live_search` / `search_parameters` / `web_search_options` answer 410.")255xbeta = [r for r in M if r["provider"]=="xai" and any(s in ("BETA","PREVIEW") for s in r["status"])]256L.append("- **Models flagged BETA/PREVIEW**: " + ", ".join(statuses_short(r) for r in xbeta) + " (multi-agent is Responses-only; Grok Build is 'early access'). Regional host `eu-west-1.api.x.ai` is undocumented (LIVE_DISCOVERED).\n")257258# ---------------- Q7259L.append("## Q7. Which model supports combination Z? <a id=\"q7\"></a>\n")260L.append("Recipe: filter `generated/models.json` on `capabilities.*` booleans. Key families — **OpenAI**: `image_in`, `reasoning`, `structured_outputs`, `function_calling`, `prompt_caching`, `tool_web_search`, `tool_code_interpreter`, `tool_mcp`, `tool_computer_use`, `tool_hosted_shell`, `tool_apply_patch`, `tool_skills`, `tool_tool_search`, `fine_tuning`, `batch`, `audio_in/out`, `realtime`; **Anthropic**: `vision`, `pdf_input`, `structured_outputs_json`, `strict_tool_use`, `prompt_caching`, `web_search`, `web_fetch`, `code_execution`, `programmatic_tool_calling`, `computer_use_toolset_ga`, `browser_use`, `mcp_connector`, `agent_skills`, `tool_search`, `effort_parameter`, `thinking_adaptive`, `context_1m_default_no_beta`, `fast_mode_speed_fast`, `batch_api`, `zero_data_retention_eligible`; **xAI**: `reasoning`, `reasoning_can_be_disabled`, `reasoning_effort_levels[]`, `function_calling`, `structured_outputs`, `prompt_caching_automatic`, `batch_api`, `priority_processing`, `context_compaction`, `websocket_responses`, `web_search`, `x_search`, `code_execution`, `collections_search`, `remote_mcp`, `image_generation_tool`, `files_attachments`, `encrypted_reasoning_content`, `anthropic_messages_compat`; **Gemini**: `thinking`, `thinking_level_param`, `thinking_levels[]`, `thought_signatures`, `structured_output`, `function_calling`, `parallel_function_calling`, `compositional_function_calling`, `google_search_grounding`, `google_maps_grounding`, `url_context`, `code_execution`, `computer_use`, `file_search`, `context_caching_explicit`, `context_caching_implicit`, `batch_api`, `flex_inference`, `priority_inference`, `live_api`, `interactions_api`, `openai_compatible_chat`, `audio_input`, `video_input`, `pdf_input`, `image_output`, `audio_output`, `embeddings`, `tuning`. Flat matrices: `generated/compatibility/model-capability-matrix.json` (+ `.csv`), `gemini-feature-model-matrix.json`, `gemini-tool-model-matrix.json`.\n")261L.append("```bash\n# generic pattern\njq -r '.[] | select(.provider==\"anthropic\" and .capabilities.context_1m_default_no_beta==true and .capabilities.computer_use_toolset_ga==true and .capabilities.web_search==true and (.status|index(\"RETIRED\")|not)) | .id' generated/models.json\njq -r '.[] | select(.provider==\"gemini\" and .capabilities.live_api==true and .capabilities.function_calling==true and .capabilities.google_search_grounding==true and (.status|index(\"RETIRED\")|not)) | .id' generated/models.json\n```\n")262combos = []263def add(title, prov, pred, kinds=None):264 ids = []265 for r in M:266 if r["provider"] != prov or not active(r): continue267 if kinds and r.get("kind") not in kinds: continue268 if prov == "anthropic" and r.get("kind") != "snapshot": continue269 if pred(r.get("capabilities") or {}, r): ids.append(r)270 combos.append((title, prov, ids))271add("Z1 (OpenAI): ≥1M context + structured outputs + prompt caching + web search + code interpreter + computer use", "openai", lambda c,r: (r.get("context_window") or 0) >= 1000000 and c.get("structured_outputs") is True and c.get("prompt_caching") is True and c.get("tool_web_search") is True and c.get("tool_code_interpreter") is True and c.get("tool_computer_use") is True)272add("Z1 (Anthropic): 1M context (no header) + structured outputs + web search + code execution + computer-use toolset GA", "anthropic", lambda c,r: c.get("context_1m_default_no_beta") is True and c.get("structured_outputs_json") is True and c.get("web_search") is True and c.get("code_execution") is True and c.get("computer_use_toolset_ga") is True)273add("Z1 (xAI): ≥1M context + structured outputs + automatic prompt caching + web_search + code_execution + batch", "xai", lambda c,r: (r.get("context_window") or 0) >= 1000000 and c.get("structured_outputs") is True and c.get("prompt_caching_automatic") is True and c.get("web_search") is True and c.get("code_execution") is True and c.get("batch_api") is True, kinds=("model",))274add("Z1 (Gemini): ≥1M context + structured output + explicit context caching + Google Search grounding + code execution + computer use", "gemini", lambda c,r: (r.get("context_window") or 0) >= 1000000 and c.get("structured_output") is True and c.get("context_caching_explicit") is True and c.get("google_search_grounding") is True and c.get("code_execution") is True and truthy(c.get("computer_use")), kinds=("stable","preview"))275add("Z2 (OpenAI): reasoning can be switched off (`effort: none` accepted) + function calling + image input", "openai", lambda c,r: "none" in (c.get("reasoning_effort_values") or []) and c.get("function_calling") is True and c.get("image_in") is True)276add("Z2 (Anthropic): thinking can be disabled + effort parameter + tool use", "anthropic", lambda c,r: c.get("thinking_can_be_disabled") is True and c.get("effort_parameter") is True and c.get("tool_use") is True)277add("Z2 (xAI): reasoning can be disabled (`reasoning_effort: none`) or is absent + function calling + image input", "xai", lambda c,r: (c.get("reasoning_can_be_disabled") is True or c.get("reasoning") is False) and c.get("function_calling") is True and c.get("image_input") is True, kinds=("model",))278add("Z2 (Gemini): `thinkingLevel` parameter with `minimal` accepted + function calling + image input", "gemini", lambda c,r: c.get("thinking_level_param") is True and isinstance(c.get("thinking_levels"), list) and "minimal" in c.get("thinking_levels") and c.get("function_calling") is True and c.get("image_input") is True, kinds=("stable","preview"))279add("Z3 (OpenAI): fine-tuning supported (self-serve winding down, 2027-01-06)", "openai", lambda c,r: c.get("fine_tuning") is True)280add("Z3 (Gemini): tuning supported on the Developer API", "gemini", lambda c,r: c.get("tuning") is True, kinds=("stable","preview"))281add("Z4 (OpenAI): audio in + audio out (chat or realtime)", "openai", lambda c,r: c.get("audio_in") is True and c.get("audio_out") is True)282add("Z4 (Anthropic): `speed: fast` supported", "anthropic", lambda c,r: c.get("fast_mode_speed_fast") is True)283add("Z4 (xAI): speech-to-speech + function calling + web/x search in the voice session", "xai", lambda c,r: c.get("speech_to_speech") is True and c.get("function_calling") is True and c.get("web_search") is True)284add("Z4 (Gemini): Live API (audio in + audio out) + function calling + Google Search", "gemini", lambda c,r: c.get("live_api") is True and c.get("audio_input") is True and c.get("audio_output") is True and c.get("function_calling") is True and c.get("google_search_grounding") is True, kinds=("stable","preview"))285add("Z5 (OpenAI): flex tier + batch + 24h extended cache retention documented", "openai", lambda c,r: ((r.get("availability") or {}).get("service_tiers") or {}).get("flex") is True and c.get("batch") is True and c.get("extended_prompt_cache_retention_24h") is True)286add("Z5 (Anthropic): zero-data-retention eligible + programmatic tool calling + MCP connector", "anthropic", lambda c,r: c.get("zero_data_retention_eligible") is True and c.get("programmatic_tool_calling") is True and c.get("mcp_connector") is True)287add("Z5 (xAI): priority processing + WebSocket Responses + context compaction + remote MCP", "xai", lambda c,r: c.get("priority_processing") is True and c.get("websocket_responses") is True and c.get("context_compaction") is True and c.get("remote_mcp") is True, kinds=("model",))288add("Z5 (Gemini): flex + priority + batch tiers + implicit caching", "gemini", lambda c,r: c.get("flex_inference") is True and c.get("priority_inference") is True and c.get("batch_api") is True and c.get("context_caching_implicit") is True, kinds=("stable","preview"))289add("Z6 (OpenAI): skills + hosted shell + apply_patch + tool search (full coding toolset)", "openai", lambda c,r: c.get("tool_skills") is True and c.get("tool_hosted_shell") is True and c.get("tool_apply_patch") is True and c.get("tool_tool_search") is True)290add("Z6 (Anthropic): per-message effort (beta) + task budgets (beta) + adaptive thinking", "anthropic", lambda c,r: c.get("per_message_effort_beta") is True and c.get("task_budgets_beta") is True and c.get("thinking_adaptive") is True)291add("Z6 (xAI): x_search + collections_search + image_generation tool + files attachments (full Grok agent toolset)", "xai", lambda c,r: c.get("x_search") is True and c.get("collections_search") is True and c.get("image_generation_tool") is True and c.get("files_attachments") is True, kinds=("model",))292add("Z6 (Gemini): Google Maps grounding + URL context + File Search + thought signatures (full Gemini 3 toolset)", "gemini", lambda c,r: c.get("google_maps_grounding") is True and c.get("url_context") is True and truthy(c.get("file_search")) and c.get("thought_signatures") is True, kinds=("stable","preview"))293add("Z7 (Gemini): free tier + Google Search grounding + structured output (zero-cost prototyping)", "gemini", lambda c,r: isinstance((r.get("pricing") or {}), dict) and str((r.get("pricing") or {}).get("free_tier","")).lower().startswith(("input/output","free")) and c.get("google_search_grounding") is True and c.get("structured_output") is True, kinds=("stable","preview"))294add("Z7 (xAI): OpenAI-compatible chat + Anthropic-compatible `/v1/messages` + deferred completions", "xai", lambda c,r: c.get("anthropic_messages_compat") is True and c.get("deferred_completions") is True and c.get("function_calling") is True, kinds=("model",))295L.append("| Combination | Provider | Matching models (status) |\n|---|---|---|")296for title, prov, ids in combos:297 L.append(f"| {esc(title)} | {prov} | {', '.join(statuses_short(r) for r in ids) or '— none'} |")298L.append("\nSnapshot ids (e.g. `gpt-5.4-2026-03-05`) share their alias's capabilities and appear in the raw query output; the table above keeps them because they are distinct callable ids. Capability flags marked `\"unknown\"` in the source never match a `== true` filter — check the model page before excluding a model on that basis (many Gemini media/agent records and Gemma 4 carry `\"unknown\"` for tool flags). Gemini `computer_use` / `file_search` may be the string `\"Supported (Preview)\"` / `\"Supported (AI Studio only)\"` — treated as true above (✔*).\n")299300# ---------------- Q8 prices301L.append("## Q8. Which provider offers X, and at what price? <a id=\"q8\"></a>\n")302L.append("Computed from `generated/pricing.json` (tool/service rows) and the `pricing` blocks of `generated/models.json`. Units are quoted as recorded; '—' = no documented surface. Statuses are those of the underlying tool/model record.\n")303L.append("```bash\n# every per-call tool price across providers\njq -r '.[] | select(.unit|test(\"call|search|request|prompt\";\"i\")) | [.provider, .model_or_service, .dimension, (.price|tostring), .unit, (.tier//\"\")] | @tsv' generated/pricing.json\n```\n")304def first(rows): return rows[0] if rows else None305oai_ws = first(price_rows("openai", r"^tool:Web search$", r"^Web search \(all models\)"))306ant_ws = first(price_rows("anthropic", r"^web_search$", r"^search$"))307xai_ws = first(price_rows("xai", r"^tool:web_search$"))308xai_xs = first(price_rows("xai", r"^tool:x_search$"))309xai_xp = first(price_rows("xai", r"^tool:x_search \(posts\)$"))310xai_xpr = first(price_rows("xai", r"^tool:x_search \(profiles\)$"))311gem_ws3 = first(price_rows("gemini", r"^tool:google_search$", None, "standard"))312gem_ws25 = next((r for r in price_rows("gemini", r"^tool:google_search$", None, "standard") if "grounded prompts" in r["unit"]), None)313gem_maps = first(price_rows("gemini", r"^tool:google_maps$", None, "standard"))314oai_ci = first(price_rows("openai", r"^tool:Containers$"))315ant_ci = first(price_rows("anthropic", r"^code_execution$", r"^container_hour$"))316xai_ci = first(price_rows("xai", r"^tool:code_execution"))317gem_ci = first(price_rows("gemini", r"^tool:code_execution$"))318oai_fs = first(price_rows("openai", r"^tool:File search$", r"^Tool call$")); oai_fss = first(price_rows("openai", r"^tool:File search$", r"^Storage$"))319xai_fs = first(price_rows("xai", r"^tool:collections_search")); xai_fss = first(price_rows("xai", r"^collections$", r"^storage_gb_day$")); xai_att = first(price_rows("xai", r"^tool:attachment_search$"))320gem_fs = first(price_rows("gemini", r"^tool:file_search$"))321gem_url = first(price_rows("gemini", r"^tool:url_context$"))322oai_img_low = first(price_rows("openai", r"^gpt-image-2$", r"^image_output low 1024x1024$")); oai_img_high = first(price_rows("openai", r"^gpt-image-2$", r"^image_output high 1024x1024$"))323xai_img = MID["grok-imagine-image"]["pricing"]; xai_img2 = MID["grok-imagine-image-2.0"]["pricing"]324gem_img = MID["gemini-3.1-flash-image"]["pricing"]; gem_imgp = MID["gemini-3-pro-image"]["pricing"]; gem_imgl = MID["gemini-3.1-flash-lite-image"]["pricing"]325oai_rt_in = first(price_rows("openai", r"^gpt-realtime-2.1$", r"^audio_input$")); oai_rt_out = first(price_rows("openai", r"^gpt-realtime-2.1$", r"^audio_output$")); oai_live = first(price_rows("openai", r"^gpt-live-1$"))326xai_voice = MID["grok-voice-think-fast-2.0"]["pricing"]; gem_live = MID["gemini-3.8-live"]["pricing"]327oai_tts = first(price_rows("openai", r"^gpt-4o-mini-tts$", r"^audio_output$")); oai_tts_in = first(price_rows("openai", r"^gpt-4o-mini-tts$", r"^text_input$"))328xai_tts = first(price_rows("xai", r"^text-to-speech$")); gem_tts = MID["gemini-3.1-flash-tts-preview"]["pricing"]329oai_stt = first(price_rows("openai", r"^gpt-transcribe$")); xai_stt = first(price_rows("xai", r"^speech-to-text", None, "rest")); xai_stts = first(price_rows("xai", r"^speech-to-text", None, "streaming")); gem_stt = MID["gemini-3.5-transcribe"]["pricing"]330oai_emb_s = first(price_rows("openai", r"^text-embedding-3-small$")); oai_emb_l = first(price_rows("openai", r"^text-embedding-3-large$")); gem_emb = MID["gemini-embedding-2"]["pricing"]331oai_vid = first(price_rows("openai", r"^sora-2$", r"per_second|video|second")); xai_vid = MID["grok-imagine-video"]["pricing"]; xai_vid15 = MID["grok-imagine-video-1.5"]["pricing"]; gem_veo = MID["veo-3.1-generate-preview"]["pricing"]; gem_veof = MID["veo-3.1-fast-generate-preview"]["pricing"]; gem_veol = MID["veo-3.1-lite-generate-preview"]["pricing"]332gem_music = MID["lyria-3.5"]["pricing"]; gem_clip = MID["lyria-3-clip-preview"]["pricing"]333def money(x): return f"${x:,.4f}".rstrip("0").rstrip(".") if isinstance(x,(int,float)) else str(x)334L.append("| Capability | OpenAI | Anthropic | xAI | Gemini |\n|---|---|---|---|---|")335L.append(f"| Web search (per 1k) | {fmt_price(oai_ws)} (`web_search`; preview $25/1k on non-reasoning models) | {fmt_price(ant_ws)} (`web_search_2026*`; failed searches free) | {fmt_price(xai_ws)} (`web_search`, image search included) | Gemini 3.x: {fmt_price(gem_ws3)} after 5,000 free/month (billed per **query**); Gemini 2.5: {fmt_price(gem_ws25)} after 1,500 RPD free; tool `googleSearch` ACCOUNT_RESTRICTED on this key |")336L.append(f"| Social / maps search | — | — | `x_search` {fmt_price(xai_xs)} until 2026-09-21, then {fmt_price(xai_xp)} + {fmt_price(xai_xpr)} | `googleMaps` {fmt_price(gem_maps)} (Gemini 3.x, 5,000 free/month); $25 / 1k grounded prompts on 2.5 |")337L.append(f"| URL fetch / context | — (web search opens pages) | `web_fetch` $0 per fetch (content tokens) | — (`open_page` action of web search) | `urlContext` {fmt_price(gem_url)} + content billed as input tokens |")338L.append(f"| Code execution | {fmt_price(oai_ci)} (1 GB; 4 GB $0.12, 16 GB $0.48, 64 GB $1.92; per-minute billing since 2026-06-02) | {fmt_price(ant_ci)} after 1,550 free container-hours/org/month; free alongside web_search/web_fetch 20260209+ | {fmt_price(xai_ci)} (`code_interpreter` / `code_execution`) | {fmt_price(gem_ci)} — `codeExecution` has no fee; code + results billed as tokens |")339L.append(f"| Managed RAG / file search | {fmt_price(oai_fs)} + storage {fmt_price(oai_fss)} (1 GB free) | — (you retrieve; `search_result` blocks) | `file_search` / `collections_search` {fmt_price(xai_fs)} + storage {fmt_price(xai_fss)}; implicit `attachment_search` {fmt_price(xai_att)} | `fileSearch` indexing {fmt_price(gem_fs)} once; storage and queries free; retrieved chunks billed as input |")340L.append(f"| Image generation (per image) | gpt-image-2: {fmt_price(oai_img_low)} (low 1024²) … {fmt_price(oai_img_high)} (high 1024²); token rates $5 text in / $8 image in / $30 image out per 1M | — | grok-imagine-image {money(xai_img['per_image_default'])}; grok-imagine-image-2.0 {money(min(x['price'] for x in xai_img2['matrix']))}–{money(max(x['price'] for x in xai_img2['matrix']))} (quality × resolution); grok-imagine-image-quality $0.05 (DEPRECATED) | gemini-3.1-flash-lite-image {money(list(gem_imgl['per_image'].values())[0])} (1K); gemini-3.1-flash-image {money(min(gem_img['per_image'].values()))}–{money(max(gem_img['per_image'].values()))} (0.5K–4K); gemini-3-pro-image {money(gem_imgp['per_image']['1K/2K (1120 tok)'])} (1K/2K) / {money(gem_imgp['per_image']['4K (2000 tok)'])} (4K); batch 50 % |")341L.append(f"| Realtime voice (audio minutes) | gpt-realtime-2.1 audio {fmt_price(oai_rt_in)} in / {fmt_price(oai_rt_out)} out (≈ $0.06 in + $0.24 out per minute at ~10 tok/s... token-based); gpt-live-1 {fmt_price(oai_live)} | — | grok-voice-think-fast-2.0 **${xai_voice['audio_per_minute']} / min** (${xai_voice['audio_per_hour']} / h) audio sent or received + ${xai_voice['text_input_per_message']} per text item | gemini-3.8-live audio in ${gem_live['input_audio']} / 1M (≈ ${gem_live['input_audio_per_min']} / min), audio out ${gem_live['output_audio']} / 1M (≈ ${gem_live['output_audio_per_min']} / min); free tier available |")342L.append(f"| Text-to-speech | gpt-4o-mini-tts {fmt_price(oai_tts_in)} text in + {fmt_price(oai_tts)} audio out; tts-1 $15 / 1M chars | — | {fmt_price(xai_tts)} (`POST /v1/tts`, 28 voices, `language` required) | gemini-3.1-flash-tts-preview ${gem_tts['input_text']} text in + ${gem_tts['output_audio']} audio out per 1M (25 tok/s ≈ ${gem_tts['output_audio']*25*60/1e6:.3f} / min); free tier |")343L.append(f"| Speech-to-text | gpt-transcribe {fmt_price(oai_stt)}; whisper-1 $0.006 / min (→ 2027-02-26) | — | {fmt_price(xai_stt)} REST, {fmt_price(xai_stts)} streaming (grok-voice-transcribe-2.0 / 1.0) | gemini-3.5-transcribe audio in ${gem_stt['input_audio']} / 1M (≈ ${gem_stt['input_audio_per_min']} / min) + text out ${gem_stt['output_text']} / 1M (blended {gem_stt['note']}); free tier |")344L.append(f"| Embeddings (per 1M tokens) | text-embedding-3-small {fmt_price(oai_emb_s)}, -large {fmt_price(oai_emb_l)} | — | `grok-embedding-small`: no published price (ACCOUNT_RESTRICTED, 404) | gemini-embedding-2 text ${gem_emb['input_text']} (batch ${gem_emb['batch']['input_text']}); image ${gem_emb['input_image']}, audio ${gem_emb['input_audio']}, video ${gem_emb['input_video']}; free tier |")345L.append(f"| Video generation (per second) | sora-2 $0.10 (720p), sora-2-pro $0.30–$0.70 — **shutdown 2026-09-24** | — | grok-imagine-video ${xai_vid['per_second']}; grok-imagine-video-1.5 ${xai_vid15['per_second']} | Veo 3.1 ${gem_veo['per_second']['720p']} (720p/1080p) / ${gem_veo['per_second']['4k']} (4K); Veo 3.1 Fast ${gem_veof['per_second']['720p']} / ${gem_veof['per_second']['1080p']} / ${gem_veof['per_second']['4k']}; Veo 3.1 Lite ${gem_veol['per_second']['720p']} / ${gem_veol['per_second']['1080p']} |")346L.append(f"| Music generation | — | — | — | lyria-3.5 ${gem_music['per_request']} {gem_music['unit']}; lyria-3-clip-preview ${gem_clip['per_request']} {gem_clip['unit']} |")347L.append("| Token counting | free (`POST /v1/responses/input_tokens`) | free (`POST /v1/messages/count_tokens`) | free (`POST /v1/tokenize-text`) | free (`:countTokens`) |")348L.append("\nProvider-specific billing quirks that change the arithmetic: xAI bills reasoning tokens on every Grok call and applies its **≥200k-token long-context rate to the whole request**; Gemini's 3.6–3.8 Flash prices double on 2027-01-01 and its free tier covers most Flash/Live/TTS/embedding models; Anthropic and OpenAI charge cache **writes** (1.25×) on their newest models while xAI and Gemini implicit caching have none (Gemini explicit caches pay storage per token-hour). Full tables and cost models: [pricing comparison](comparisons/pricing.md).\n")349350# ---------------- Q9351L.append("## Q9. Where do I look for … ? <a id=\"q9\"></a>\n")352L.append("| Question | File(s) |\n|---|---|")353L.append(f"| Every endpoint, with auth/beta/verification | `generated/endpoints.json` ({len(E)} records) · [docs/endpoints/index.md](endpoints/index.md) · [by-status](endpoints/by-status.md) |")354L.append(f"| Every parameter of an endpoint | `generated/parameters.json` ({len(P):,} rows) filtered by `endpoint` |")355L.append(f"| Model facts (context, output, cutoff, prices, capabilities, cloud ids) | `generated/models.json` ({len(M)} records) · [docs/comparisons/models.md](comparisons/models.md) · docs/models/{{openai,anthropic,xai,gemini}}-models.md |")356L.append(f"| Tool definitions & result shapes | `generated/tools.json` ({len(T)} records) · docs/tools/{{openai,anthropic,xai,gemini}}/*.md |")357L.append(f"| Prices | `generated/pricing.json` ({len(PR):,} rows) · [docs/comparisons/pricing.md](comparisons/pricing.md) · docs/{{openai,anthropic,xai,gemini}}/pricing.md |")358L.append("| Errors and retry rules | `generated/errors.json` · docs/errors/{openai,anthropic,gemini}.md · docs/xai/authentication-headers-errors.md |")359L.append("| Headers (request/response/webhook) | `generated/headers.json` · `generated/fragments/headers/{anthropic-beta-headers,anthropic-headers,openai-headers,xai-headers,gemini-headers}.json` |")360L.append(f"| Streaming & webhook events | `generated/streaming-events.json` ({len(S)}) · `generated/webhook-events.json` ({len(W)}; OpenAI + Anthropic only — xAI has the single SIP `realtime.call.incoming`, Gemini Interactions use `webhook_config` / `/v1/webhooks`) |")361L.append("| Deprecations / retirements | `generated/deprecations.json` · docs/openai/deprecations.md · docs/anthropic/deprecations.md · docs/xai/deprecations-and-release-notes.md · docs/gemini/deprecations-and-changelog.md |")362L.append("| Feature-by-feature comparison (4 providers) | [docs/comparisons/features.md](comparisons/features.md) · `generated/compatibility/cross-provider-feature-matrix.json` |")363L.append("| Rate limits | `generated/rate-limits.json` · docs/openai/rate-limits.md · docs/anthropic/rate-limits.md · docs/xai/rate-limits.md · docs/gemini/rate-limits.md |")364L.append("| SDKs | `generated/sdks.json` · docs/openai/sdks.md · docs/anthropic/sdks.md · docs/xai/sdks.md · docs/gemini/sdks.md |")365L.append("| Cloud platform availability | `generated/compatibility/anthropic-feature-platform-matrix.json` · docs/anthropic/cloud-providers.md · docs/openai/data-residency-and-regions.md · docs/gemini/vertex-vs-gemini-api.md · docs/xai/sdks.md (Vertex Model Garden / Foundry) |")366L.append("| OpenAI-compatibility layers | docs/xai/chat-completions.md · docs/xai/messages-compat.md · docs/gemini/openai-compatibility.md |")367L.append("| Runnable examples and their status | `generated/examples-manifest.json` · `examples/` |")368L.append("| Live calls made by this atlas (cost, status) | `reports/live-requests.jsonl` |")369open(f"{ROOT}/docs/faq.md", "w").write("\n".join(L))370print("faq.md written", len(L), "lines")371