#!/usr/bin/env python3 """Render docs/endpoints/index.md and docs/endpoints/by-status.md from generated/endpoints.json for the four providers (openai, anthropic, xai, gemini). Re-runnable; no manual edits in the outputs.""" import json, collections ROOT = "/Users/simon-pierreboucher/Desktop/doc-api" TODAY = "2026-09-18" E = json.load(open(f"{ROOT}/generated/endpoints.json")) PROVIDERS = ["openai", "anthropic", "xai", "gemini"] PROV_LABEL = {"openai": "OpenAI", "anthropic": "Anthropic", "xai": "xAI", "gemini": "Gemini"} def esc(s): return str(s).replace("|", "\\|").replace("\n", " ") def auth_short(a): if a is None: return "—" if isinstance(a, dict): scheme = a.get("scheme") or "" kt = a.get("key_type") or "" hdr = a.get("header") or "" bits = [b for b in [scheme, kt] if b] s = " / ".join(bits) or hdr or json.dumps(a)[:60] if a.get("scopes"): s += f" (scopes: {', '.join(a['scopes'][:2])}{'…' if len(a['scopes'])>2 else ''})" return s[:110] a = str(a) return a if len(a) <= 110 else a[:107] + "…" def beta_short(b): if not b: return "—" b = str(b) return b if len(b) <= 60 else b[:57] + "…" def verif(r): v = r.get("verification") or {} m = v.get("method") or "—" res = v.get("result") hs = v.get("http_status") if m == "docs_only" and not res: return "docs only" if m == "—" and not res and hs in (None, 0): return "—" s = m if res: s += f" · {res}" if hs not in (None, 0): s += f" · HTTP {hs}" return s def bucket(r): st = set(r["status"]) if "RETIRED" in st: return "RETIRED" if "DEPRECATED" in st: return "DEPRECATED" if "LEGACY" in st: return "LEGACY" if "FAILED_VERIFICATION" in st: return "FAILED_VERIFICATION" if "ACCOUNT_RESTRICTED" in st: return "ACCOUNT_RESTRICTED" if "LIVE_VERIFIED" in st: return "LIVE_VERIFIED" if "LIVE_DISCOVERED" in st: return "LIVE_DISCOVERED" if "BETA" in st or "PREVIEW" in st: return "BETA_OR_PREVIEW_DOCUMENTED_ONLY" if "UNVERIFIED" in st: return "UNVERIFIED" return "DOCUMENTED_ONLY" fam_order = collections.OrderedDict() for prov in PROVIDERS: fam_order[prov] = sorted({r["api_family"] for r in E if r["provider"] == prov}) status_counter = collections.Counter(s for r in E for s in r["status"]) per_prov = {p: sum(1 for r in E if r["provider"] == p) for p in PROVIDERS} L = [] L.append("# Endpoint catalogue — OpenAI + Anthropic + xAI + Gemini (rendered from `generated/endpoints.json`)\n") L.append(f"**Status:** generated from `generated/endpoints.json` ({len(E)} endpoint records: " + ", ".join(f"{PROV_LABEL[p]} {per_prov[p]}" for p in PROVIDERS) + "). Each row carries the statuses recorded by the domain agents; “Verification” is the record's `verification` block (method · result · HTTP). This page is the human twin of `generated/endpoints.csv`; a status-grouped view is in [by-status.md](by-status.md).") L.append("**Sources:** per record `sources[]` in `generated/endpoints.json` (OpenAI reference https://developers.openai.com/api/reference/… and OpenAPI `sources/openai/openapi/openapi-master.yaml`; Anthropic reference https://platform.claude.com/docs/en/api/…; xAI reference https://docs.x.ai/developers/rest-api-reference/… and OpenAPI `sources/xai/openapi/openapi.json`; Gemini reference https://ai.google.dev/api/… and discovery documents `sources/gemini/openapi/discovery-v1beta.json` / `discovery-v1.json`).") L.append(f"**Last verified:** {TODAY}\n") L.append("## Legend\n") L.append("| Column | Meaning |\n|---|---|") L.append("| Status | atlas vocabulary: `DOCUMENTED` in current docs · `LIVE_VERIFIED` called successfully with our key · `LIVE_DISCOVERED` seen live, weak docs · `BETA`/`PREVIEW` gated or pre-GA · `GA` (Gemini fragments only: explicitly marked generally available) · `LEGACY` maintained but superseded · `DEPRECATED` retirement announced · `RETIRED` gone · `ACCOUNT_RESTRICTED` documented, our key got 401/403/404-for-access (xAI: Management API key we do not have, ACL-gated models; Gemini: paid-tier-only models on a free-tier key) · `UNVERIFIED` not tried · `FAILED_VERIFICATION` tried, unexpected failure · `DOCUMENTATION_INCOMPLETE` reference lacks a shape |") L.append("| Auth | credential accepted: OpenAI Bearer project/Admin key; Anthropic `x-api-key` or Bearer, Admin/Compliance keys, cloud IAM; xAI `Authorization: Bearer xai-…` (inference key) or a **Management key** for `management-api.x.ai`; Gemini `x-goog-api-key` (never in the URL) or OAuth Bearer, `Authorization: Bearer ` on `/v1beta/openai/*` |") L.append("| Beta header | header value required to reach the surface (`OpenAI-Beta: …` or `anthropic-beta: …`); `—` = none. xAI has **no** version or beta headers; Gemini gates features by **URL version** (`/v1beta` vs `/v1`) and `-preview` model ids, not by header |") L.append("| Verification | `live_api · success · HTTP 200` = exercised; `docs_only` = not called in this run; `restricted` = 401/403 (or access-404) with our key; `failure` = call made, unexpected status (see record `request_note`); `not_tested` = Gemini/xAI records deliberately skipped (cost, destructive, needs a peer) |") L.append("\n### Status totals (a record may carry several statuses)\n") L.append("| Status | Total | " + " | ".join(PROV_LABEL[p] for p in PROVIDERS) + " |\n|---|---|" + "---|" * len(PROVIDERS)) for s, n in status_counter.most_common(): L.append(f"| `{s}` | {n} | " + " | ".join(str(sum(1 for r in E if r['provider']==p and s in r['status'])) for p in PROVIDERS) + " |") L.append("\n## Counts per provider → api_family\n") L.append("| Provider | api_family | Endpoints | LIVE_VERIFIED | ACCOUNT_RESTRICTED | BETA / PREVIEW | DEPRECATED/RETIRED |\n|---|---|---|---|---|---|---|") for prov, fams in fam_order.items(): for f in fams: rs = [r for r in E if r["provider"] == prov and r["api_family"] == f] L.append(f"| {prov} | `{f}` | {len(rs)} | {sum('LIVE_VERIFIED' in r['status'] for r in rs)} | {sum('ACCOUNT_RESTRICTED' in r['status'] for r in rs)} | {sum(('BETA' in r['status']) or ('PREVIEW' in r['status']) for r in rs)} | {sum(('DEPRECATED' in r['status']) or ('RETIRED' in r['status']) for r in rs)} |") L.append(f"| **{prov} total** | {len(fams)} families | **{per_prov[prov]}** | {sum(1 for r in E if r['provider']==prov and 'LIVE_VERIFIED' in r['status'])} | {sum(1 for r in E if r['provider']==prov and 'ACCOUNT_RESTRICTED' in r['status'])} | {sum(1 for r in E if r['provider']==prov and (('BETA' in r['status']) or ('PREVIEW' in r['status'])))} | {sum(1 for r in E if r['provider']==prov and (('DEPRECATED' in r['status']) or ('RETIRED' in r['status'])))} |") L.append(f"| **all providers** | {sum(len(f) for f in fam_order.values())} families | **{len(E)}** | {sum('LIVE_VERIFIED' in r['status'] for r in E)} | {sum('ACCOUNT_RESTRICTED' in r['status'] for r in E)} | {sum(('BETA' in r['status']) or ('PREVIEW' in r['status']) for r in E)} | {sum(('DEPRECATED' in r['status']) or ('RETIRED' in r['status']) for r in E)} |") L.append("\n### Reading the four surfaces\n") L.append("| Provider | Base URL(s) | Path style | Families worth knowing |\n|---|---|---|---|") L.append("| openai | `https://api.openai.com/v1` (+ regional `us.|eu.|au.|jp.|in.api.openai.com`, `api.chatgpt.com` for workspace agents) | REST nouns (`/v1/responses`, `/v1/agents/sessions`), WebSocket `wss://api.openai.com/v1/{responses,realtime}` | `responses`, `chat_completions`, `agents-platform/*`, `realtime`, `live`, `admin` (124) |") L.append("| anthropic | `https://api.anthropic.com/v1` (+ Bedrock, Vertex, Foundry, Claude Platform on AWS hosts under `cloud:*`) | REST nouns (`/v1/messages`, `/v1/sessions`), beta surfaces gated by `anthropic-beta` | `messages`, `messages-batches`, `managed-agents` (96), `admin` (100), `compliance` (36) |") L.append("| xai | `https://api.x.ai/v1` (global), `https://us.api.x.ai/v1` (US-pinned, grok-4.6, 1.1× price), `https://eu-west-1.api.x.ai/v1` (LIVE_DISCOVERED), `https://management-api.x.ai` (separate key), gRPC `api.x.ai:443` | OpenAI-shaped REST (`/v1/responses`, `/v1/chat/completions`, `/v1/batches`) + Anthropic-shaped `/v1/messages`; WebSocket `wss://api.x.ai/v1/{realtime,stt,tts}` | `responses`, `chat_completions` (LEGACY), `collections`, `files`, `voice` (17), `management` (21, ACCOUNT_RESTRICTED) |") L.append("| gemini | `https://generativelanguage.googleapis.com/v1beta` (also `/v1` stable subset, `/upload/v1beta/*` resumable uploads, `/v1beta/openai/*` compatibility layer), WebSocket `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent` | Google RPC-style verbs on resources (`/v1beta/models/{model}:generateContent`, `:streamGenerateContent`, `:countTokens`, `:embedContent`, `:predictLongRunning`), resource collections (`/v1beta/cachedContents`, `/v1beta/fileSearchStores`, `/v1beta/interactions`, `/v1beta/batches`) | `generate-content`, `interactions`, `live`, `file-search` (12), `tuning` (12, RETIRED/LEGACY), `legacy-palm` (7), `openai-compat` (7) |") def method_rank(m): return {"GET":0,"POST":1,"PATCH":2,"PUT":3,"DELETE":4,"WS":5,"WSS":6,"SIP":7}.get(m,8) for prov, fams in fam_order.items(): L.append(f"\n# {PROV_LABEL[prov]} endpoints ({per_prov[prov]})\n") for f in fams: rs = [r for r in E if r["provider"] == prov and r["api_family"] == f] rs.sort(key=lambda r: (r["path"].split("?")[0], method_rank(r["method"]))) L.append(f"\n## {prov} · `{f}` ({len(rs)})\n") L.append("| Method | Path | Name | Status | Auth | Beta header | Verification |") L.append("|---|---|---|---|---|---|---|") for r in rs: L.append(f"| `{esc(r['method'])}` | `{esc(r['path'])}` | {esc(r['name'])} | {' · '.join('`'+s+'`' for s in r['status'])} | {esc(auth_short(r.get('auth')))} | {esc(beta_short(r.get('beta_header')))} | {esc(verif(r))} |") L.append("\n---\nRelated: [by-status view](by-status.md) · [feature matrix](../comparisons/features.md) · `generated/endpoints.csv` · `generated/parameters.json` (per-endpoint parameters, key = `endpoint` field `METHOD /path`).\n") open(f"{ROOT}/docs/endpoints/index.md", "w").write("\n".join(L)) # ---------------- by-status order = ["LIVE_VERIFIED","LIVE_DISCOVERED","ACCOUNT_RESTRICTED","BETA_OR_PREVIEW_DOCUMENTED_ONLY","DOCUMENTED_ONLY","UNVERIFIED","LEGACY","DEPRECATED","RETIRED","FAILED_VERIFICATION"] titles = { "LIVE_VERIFIED": "LIVE_VERIFIED — called successfully with this atlas's keys", "LIVE_DISCOVERED": "LIVE_DISCOVERED — observed live (e.g. 404/405 probe proves the route) with weak/absent docs", "ACCOUNT_RESTRICTED": "ACCOUNT_RESTRICTED — documented; our key received 401/403 or an access-denial (needs Admin/Compliance key, WIF token, xAI Management key or ACL, Gemini paid tier, or gated access)", "BETA_OR_PREVIEW_DOCUMENTED_ONLY": "BETA / PREVIEW — documented behind a beta header, a `/v1beta` path or a preview program, not called in this run", "DOCUMENTED_ONLY": "DOCUMENTED only — in current docs, not called (mostly mutations or sub-resources)", "UNVERIFIED": "UNVERIFIED — documented, deliberately not tried (cost, needs browser/SIP peer, destructive)", "LEGACY": "LEGACY — still served, superseded", "DEPRECATED": "DEPRECATED — retirement announced (see docs/openai/deprecations.md, docs/anthropic/deprecations.md, docs/xai/deprecations-and-release-notes.md, docs/gemini/deprecations-and-changelog.md)", "RETIRED": "RETIRED — gone (404/400/501) or redirected to a replacement", "FAILED_VERIFICATION": "FAILED_VERIFICATION — tried; unexpected failure (reason in `verification.request_note`)", } B = [] B.append("# Endpoints grouped by status — four providers\n") B.append(f"**Status:** generated from `generated/endpoints.json` ({len(E)} records: " + ", ".join(f"{PROV_LABEL[p]} {per_prov[p]}" for p in PROVIDERS) + "). Each endpoint is placed in exactly one bucket by precedence RETIRED > DEPRECATED > LEGACY > FAILED_VERIFICATION > ACCOUNT_RESTRICTED > LIVE_VERIFIED > LIVE_DISCOVERED > BETA/PREVIEW > UNVERIFIED > DOCUMENTED-only; the full status list is shown in the row. Full catalogue with auth/beta columns: [index.md](index.md).") B.append(f"**Sources:** as `generated/endpoints.json` · **Last verified:** {TODAY}\n") buckets = collections.defaultdict(list) for r in E: buckets[bucket(r)].append(r) B.append("| Bucket | " + " | ".join(PROV_LABEL[p] for p in PROVIDERS) + " | Total |\n|---|" + "---|" * len(PROVIDERS) + "---|") for b in order: rs = buckets.get(b, []) B.append(f"| {b} | " + " | ".join(str(sum(r['provider']==p for r in rs)) for p in PROVIDERS) + f" | {len(rs)} |") B.append(f"| **Total** | " + " | ".join(str(per_prov[p]) for p in PROVIDERS) + f" | **{len(E)}** |") B.append("\nHow to read the provider columns: the OpenAI and Anthropic catalogues include large Admin/Compliance families that a project key cannot call (hence many `ACCOUNT_RESTRICTED`); xAI's `management` family (21) needs a Management key this atlas does not hold; Gemini's `agents`, `credentials`, `environments`, `triggers`, `webhooks` families are the managed-agents preview (documented, mostly `not_tested`), and its `tuning` / `legacy-palm` families are retired or legacy surfaces still present in the discovery document.\n") for b in order: rs = buckets.get(b, []) if not rs: continue B.append(f"\n## {titles[b]} ({len(rs)})\n") B.append("| Provider | api_family | Method | Path | Status | Verification / note |") B.append("|---|---|---|---|---|---|") rs.sort(key=lambda r: (PROVIDERS.index(r["provider"]), r["api_family"], r["path"], method_rank(r["method"]))) for r in rs: v = r.get("verification") or {} note = v.get("request_note") or "" vs = verif(r) if b in ("FAILED_VERIFICATION","LIVE_DISCOVERED","ACCOUNT_RESTRICTED","RETIRED") and note: vs += " — " + (note if len(note) <= 140 else note[:137] + "…") B.append(f"| {r['provider']} | `{esc(r['api_family'])}` | `{esc(r['method'])}` | `{esc(r['path'])}` | {' · '.join('`'+s+'`' for s in r['status'])} | {esc(vs)} |") B.append("\n---\nRelated: [full catalogue](index.md) · [feature matrix](../comparisons/features.md) · [FAQ](../faq.md).\n") open(f"{ROOT}/docs/endpoints/by-status.md", "w").write("\n".join(B)) print("index rows", len(E), {p: per_prov[p] for p in PROVIDERS}, {b: len(v) for b, v in buckets.items()})