A Cloudflare Worker on llm.e2e.one that accepts standard LLM SDK traffic,
authenticates with issued gateway keys, and forwards to xAI using credentials extracted
from the official grok-cli OAuth session. An admin UI shows config, token health, and usage
including cache reads and writes.
LLM-OTA (Over-The-Air LLM gateway) is a single Cloudflare Worker that looks like OpenAI and Anthropic to clients, and like a Grok Build / SuperGrok subscriber to xAI. The POC implements one provider only: Grok. Later providers (Claude Max, ChatGPT Plus, Gemini, etc.) plug into the same gateway interface.
POST https://llm.e2e.one/v1/chat/completionsPOST https://llm.e2e.one/v1/responsesPOST https://llm.e2e.one/v1/messagesGET https://llm.e2e.one/v1/modelsAuthorization: Bearer sk-ota-... or x-api-keyhttps://llm.e2e.one/admin
The official Grok Build CLI (this machine: ~/.grok/bin/grok) authenticates through
SpaceXAI OAuth at auth.x.ai, not the unofficial sso / sso-rw
cookie scrape used by older grok.com wrappers. Credentials live in
~/.grok/auth.json with mode 0600.
Observed local schema (values redacted):
{
"https://auth.x.ai::<principal-uuid>": {
"auth_mode": "...",
"key": "<access token — Bearer for api.x.ai>",
"refresh_token": "<rotating refresh token>",
"expires_at": "<ISO-8601>",
"oidc_issuer": "https://auth.x.ai",
"oidc_client_id": "...",
"user_id": "...",
"team_id": "...",
"email": "...",
"principal_id": "...",
"principal_type": "..."
}
}
key is the access token the CLI sends as Authorization: Bearer to xAI.refresh_token is used for silent renewal. Third-party clients (Kilo Code, pi-xai-oauth)
document that xAI rotates refresh tokens on every use. Only one refresher may run at a time.XAI_API_KEY. The API key is a pay-per-token console key,
not the SuperGrok subscription path.~/.grok/docs/user-guide/02-authentication.md.~/.grok/auth.json without rewriting it — same ingest idea.https://api.x.ai/v1/chat/completions and /v1/responses.base_url=https://api.x.ai/v1.POST /v1/messages still exists and returns Anthropic-shaped
cache_creation_input_tokens / cache_read_input_tokens, but xAI marks
Anthropic SDK compatibility as fully deprecated and
cache_creation_input_tokens as unsupported./v1/messages as the long-term backend.From xAI prompt caching:
cache_control on the wire to xAI.x-grok-conv-id (Chat Completions header) or
prompt_cache_key (Responses body) is required for a high hit rate. Cache is per-server.usage.prompt_tokens_details.cached_tokens or
usage.input_tokens_details.cached_tokens.cache_write_tokens field. We synthesize writes
(see §8).| Client | Request | Response |
|---|---|---|
| OpenAI Chat Completions | Stable prefix; optional prompt_cache_key; pass-through x-grok-conv-id |
usage.prompt_tokens_details.cached_tokens,
cache_write_tokens (GPT-5.6-style, synthesized) |
| OpenAI Responses | prompt_cache_key |
usage.input_tokens_details.cached_tokens + write counterpart |
| Anthropic Messages | Block-level cache_control: {type:"ephemeral", ttl:"5m"|"1h"};
top-level automatic cache_control;
anthropic-beta: prompt-caching-2024-07-31 |
cache_creation_input_tokens, cache_read_input_tokens,
optional cache_creation.ephemeral_5m_input_tokens /
ephemeral_1h_input_tokens |
| Decision | Choice | Why |
|---|---|---|
| Runtime | One Cloudflare Worker + D1 + KV + one Durable Object | Already on the e2e.one zone. Custom Domain gives TLS without extra DNS work. |
| Public hostname | llm.e2e.one |
ai.e2e.one is taken by kiro-otai. api.e2e.one points at an origin A record. |
| POC provider | Grok via grok-cli OAuth against api.x.ai |
User request. Official console API keys remain a fallback auth mode. |
| Not using grok.com cookies | OAuth session only | CLI no longer uses sso cookies; cookie scrapers are brittle and more ToS-hostile. |
| Anthropic backend | Translate to xAI Chat Completions / Responses | xAI native /v1/messages is deprecated; cache writes are unsupported there. |
| Refresh lock | Durable Object TokenLease |
Refresh tokens rotate. Concurrent refreshes invalidate each other. |
| Client auth | Gateway-issued keys, hashed in D1 | Never expose the grok-cli access token to downstream tools. |
| Admin auth (POC) | ADMIN_PASSWORD cookie session |
Fast. Swap for Cloudflare Access before any shared use. |
OpenAI SDK / Claude SDK / Cursor / Cline
|
| sk-ota-... or x-api-key
v
https://llm.e2e.one Cloudflare Worker llm-ota
/v1/chat/completions - protocol detect
/v1/responses - key auth
/v1/messages - model alias
/v1/models - cache-key derivation
/admin - usage log (waitUntil → D1)
|
| Bearer <rotated grok-cli access token>
| x-grok-conv-id / prompt_cache_key
v
api.x.ai
|
TokenLease DO <-- refresh_token grant --> auth.x.ai
KV: current access token + expiry (cache of DO state)
D1: api_keys, requests, usage_daily, config
| Method | Path | Auth | Role |
|---|---|---|---|
| GET | / | public | Status / this plan |
| GET | /health | public | liveness; no token leak |
| GET | /v1/models | gateway key | OpenAI model list |
| POST | /v1/chat/completions | gateway key | OpenAI Chat Completions |
| POST | /v1/responses | gateway key | OpenAI Responses |
| POST | /v1/messages | x-api-key or Bearer | Anthropic Messages |
| POST | /v1/messages/count_tokens | same | Anthropic token count (best-effort) |
| * | /admin* | admin session | Config + usage UI |
| POST | /admin/api/keys | admin | Mint / revoke gateway keys |
| POST | /admin/api/provider/grok/import | admin | Accept pasted auth.json blob (never logged) |
A repo script scripts/import-grok-auth.mjs reads ~/.grok/auth.json
(or $GROK_HOME/auth.json), never prints secrets, and writes Wrangler secrets:
GROK_ACCESS_TOKEN GROK_REFRESH_TOKEN GROK_OIDC_ISSUER # https://auth.x.ai GROK_OIDC_CLIENT_ID GROK_TOKEN_EXPIRES_AT # ISO, optional GROK_ACCOUNT_EMAIL # non-secret, also a var
Refresh flow (inside the Durable Object, exclusive):
token_endpoint from {issuer}/.well-known/openid-configuration.POST grant_type=refresh_token, client_id, current refresh token.
Public client, no secret (CLI is a PKCE public client).wrangler secret is not available at runtime — store the live pair in
DO storage (authoritative) and a short-TTL copy in KV for hot reads.grok login.
Document this on the admin “Provider” panel.
The admin UI accepts the raw auth.json object (or the inner record). The Worker
extracts key / refresh_token / issuer / client_id, seeds the DO,
and returns only { email, expires_at, issuer }. The request body is not written
to D1 or logs.
If no session is loaded, XAI_API_KEY (secret) is used. Usage then bills the
xAI console, not the SuperGrok quota. The admin UI labels the active mode:
subscription-oauth vs console-api-key.
Accept the current OpenAI Chat Completions and Responses request bodies and stream shapes.
Authorization: Bearer sk-ota-...model, messages / input, stream,
temperature, top_p, max_tokens / max_completion_tokens,
stop, tools, tool_choice, response_format,
user, seed, n=1data: {...} then data: [DONE];
stream_options.include_usage{ error: { message, type, code, param } } with matching HTTP statusgpt-4o → grok-4.6 (configurable)n > 1Clients such as Claude Code, Cline, and the official Anthropic SDK must work unchanged.
| Header | POC behavior |
|---|---|
x-api-key | Gateway key. Also accept Bearer. |
anthropic-version | Accept 2023-06-01; echo it. |
anthropic-beta | Honor prompt-caching-2024-07-31 and extended TTL names. Ignore unknown betas. |
model, required max_tokens, messages, system
(string or text-block array), tools, tool_choice,
temperature, top_p, stop_sequences, stream,
metadata.user_id, thinking (map to xAI reasoning if the mapped model supports it)text, image (base64 / url), tool_use,
tool_result. Drop or 400 on unimplemented block types with a clear message.cache_control on system blocks, message blocks, and the last tool (see §8).cache_control (Anthropic automatic caching, 2026 docs): treat as
“cache the whole prefix through the last cacheable block.”type=message, role=assistant, content blocks,
stop_reason in {end_turn, max_tokens, stop_sequence, tool_use}message_start, content_block_start,
content_block_delta, content_block_stop,
message_delta, message_stopAnthropic messages + system + tools
→ OpenAI/xAI chat messages
→ POST api.x.ai/v1/chat/completions
→ map choices/tool_calls back to content blocks
→ map usage (see §8)
Two different cache languages have to meet in the Worker. xAI only reports
reads (cached_tokens). Anthropic and newer OpenAI also expect
writes.
sha256(gateway_key_id + ":" + (client prompt_cache_key || client x-grok-conv-id || hash(stable_prefix)))x-grok-conv-id on Chat Completions and as
prompt_cache_key on Responses.cache_control. The suffix after that is the “hot” tail.
We still send the full prompt to xAI (it only caches matching prefixes), but we use the
breakpoint to decide how to report writes vs reads.Let P = upstream prompt tokens, R = upstream cached_tokens.
| Outbound field | Value |
|---|---|
OpenAI prompt_tokens / Responses input_tokens | P |
OpenAI prompt_tokens_details.cached_tokens | R |
OpenAI prompt_tokens_details.cache_write_tokens | max(P - R - tail, 0) on a miss-or-partial, else 0 |
Anthropic input_tokens | P - R (Anthropic counts uncached input only) |
Anthropic cache_read_input_tokens | R |
Anthropic cache_creation_input_tokens | same write estimate as OpenAI |
Anthropic cache_creation.ephemeral_5m_input_tokens | writes whose breakpoint ttl is 5m / default |
Anthropic cache_creation.ephemeral_1h_input_tokens | writes whose breakpoint ttl is 1h |
tail is an estimate of tokens after the last cache_control marker
(tiktoken-class count is fine for POC; exact xAI tokenization is not public). If the client
sent no markers (pure OpenAI automatic cache), treat the entire prompt except the last user
turn as the cacheable prefix.
usage when include_usage is set.message_delta.usage plus final message_start.usage zeros,
matching current Claude behavior.Served by the same Worker (no separate Pages project). Small server-rendered HTML plus vanilla JS. No client-side framework in the POC.
api_keys( id TEXT PK, label TEXT, hash TEXT UNIQUE, prefix TEXT, created_at, last_used_at, revoked_at, rpm INTEGER, daily_token_cap INTEGER ) requests( id TEXT PK, ts, key_id, protocol, model, status, latency_ms, prompt_tokens, completion_tokens, cache_read_tokens, cache_write_tokens, error_code ) usage_daily( day TEXT, key_id, model, requests, prompt_tokens, completion_tokens, cache_read_tokens, cache_write_tokens, PRIMARY KEY(day, key_id, model) ) config( key TEXT PK, value TEXT, updated_at )
grok:access short-TTL copy of the live access token for request-path reads. Source of truth is the DO.
TokenLeaseSingle instance. Methods: getAccessToken(), importSession(blob), status(). Serializes refresh.
ADMIN_PASSWORD secret GROK_* secrets (seed only) XAI_API_KEY secret, optional fallback ALLOWED_ORIGINS var, optional CORS
/admin*.Zone e2e.one is already active on this Cloudflare account (free plan, id
e001b235077663f32cf15cca58c0aeec).
| Hostname | Status | Action |
|---|---|---|
ai.e2e.one | AAAA 100:: + route → kiro-otai | Leave alone |
api.e2e.one | A 84.8.145.139 (existing origin) | Do not steal |
llm.e2e.one | unused | Worker Custom Domain |
Wrangler:
{
"name": "llm-ota",
"main": "src/index.ts",
"compatibility_date": "2026-08-13",
"compatibility_flags": ["nodejs_compat"],
"observability": { "enabled": true, "head_sampling_rate": 1 },
"routes": [{ "pattern": "llm.e2e.one", "custom_domain": true }]
}
Custom Domain creates the DNS record and certificate. Do not also create a CNAME — that blocks Custom Domain attach.
Public URLs after attach:
https://llm.e2e.one/ — plan / statushttps://llm.e2e.one/v1/chat/completionshttps://llm.e2e.one/v1/messageshttps://llm.e2e.one/admin| # | Slice | Ships |
|---|---|---|
| 0 | This plan + hostname | PLAN.html, Worker stub, llm.e2e.one Custom Domain |
| 1 | Scaffold | wrangler.jsonc, D1 schema, KV, TokenLease DO, health, admin password gate |
| 2 | Grok adapter | Importer, DO refresh, fallback API key, GET /v1/models |
| 3 | OpenAI Chat Completions | Non-stream + stream, tools, usage + cached_tokens + cache_write_tokens |
| 4 | Anthropic Messages | Translate, stream events, cache_control in and cache_* usage out |
| 5 | Responses API | Thin map onto the same adapter; prompt_cache_key |
| 6 | Admin UI | Config, key mint, usage charts, cache rates |
| 7 | Harden | Cloudflare Access on /admin, rate limits, request log retention |
curl -sS https://llm.e2e.one/health → 200.curl https://llm.e2e.one/v1/chat/completions \
-H "Authorization: Bearer $OTA_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"grok-4.6","messages":[{"role":"user","content":"ping"}]}'
Second identical call with the same x-grok-conv-id must show
cached_tokens > 0 or a non-zero write then a read.
https://llm.e2e.one with a
cache_control system block. Response includes
cache_creation_input_tokens then
cache_read_input_tokens on the follow-up.llm.e2e.one or also alias ota.e2e.one?gpt-4o / claude-sonnet-4-6
so existing client configs work, or only advertise real Grok ids?/admin in the POC or after first working completion?