{"slug":"cloudflare-ai-gateway-setup","title":"How to create a Cloudflare AI Gateway, with authentication on","body":"An AI Gateway is a URL you send AI requests to instead of sending them to OpenAI, Anthropic, xAI or Cloudflare's own models directly; Cloudflare forwards the request, returns the provider's answer unchanged, and keeps a row recording the model, the token counts, an estimated cost, the duration and the status code. It is not a model, not a router that picks a model for you, and not a billing account on its own — it is a proxy with a ledger, and everything else it does (caching, retries, rate limits, spend caps) is switched on per gateway or per request.\n\n## Evidence status\n\n**Observed** marks first-party measurements or runtime receipts from the named environment.\n**Derived** marks arithmetic calculated from cited inputs. **Specified** marks vendor or standards\ndocumentation. **Implemented** and **deployed** name code and live-state evidence, respectively.\n**Reproduced** means the stated procedure was rerun. **Externally attested** marks operator reports;\nthose reports show that an experience occurred, not that it is universal.\n\n## The gateway called `default` exists before you create anything\n\nIf a request omits the gateway id, the id `default` is used, and if no gateway by that name exists it is created on the first authenticated request. The settings it is born with are not the ones most guides assume:\n\n| Setting on the auto-created `default` gateway | Value |\n| --- | --- |\n| Authentication | On |\n| Log collection | On |\n| Caching | Off (TTL of 0) |\n| Rate limiting | Off |\n\nCaching is off, so any claim that the gateway will cut your bill by serving repeats describes a feature you have not enabled. Authentication is on, so the first thing that happens to a client which cannot send a second HTTP header is a `401`. Auto-creation applies only to the id `default`; any other id must be created first.\n\n[[embed:source:s1]]\n\n## Prerequisites, with the exact labels\n\n1. **A Cloudflare account.** The free plan is enough — AI Gateway's core features cost nothing on every plan.\n2. **Your account id.** Every dashboard URL is `https://dash.cloudflare.com/<ACCOUNT_ID>/...`; the 32-character hex string after the hostname is it. `wrangler whoami` prints the same value. It goes in every request URL.\n3. **Where the product lives.** Sidebar → **AI** → **AI Gateway**. Direct link: `https://dash.cloudflare.com/?to=/:account/ai/ai-gateway`.\n4. **An API token**, from `https://dash.cloudflare.com/profile/api-tokens` → **Create Token** → **Create Custom Token** → **Get started**. Under **Permissions** the rows read exactly `Account` · `AI Gateway` · `Read | Edit | Run`. Under **Account Resources** pick your account, then **Continue to summary** → **Create Token**. It is displayed once.\n\nThe three permissions are not interchangeable. `Read` lists gateways and reads settings and logs through the API. `Edit` creates, updates and deletes gateways, and is required to set `cache_ttl` or rate limits by API rather than by clicking. `Run` sends inference through `gateway.ai.cloudflare.com`, and is what the `cf-aig-authorization` header carries. None can be narrowed to one gateway:\n\n> The `AI Gateway Read`, `Run`, and `Edit` permissions cannot be restricted to a single gateway — unlike R2, which supports per-bucket scoping. Any token with `AI Gateway Run` can send requests through every gateway in the account, including any configured with stored provider keys through Bring Your Own Keys (BYOK), consuming those credentials.\n\nA leaked `Run` token is therefore an account-wide credential that can spend any provider key you have stored. Isolation between tenants means separate Cloudflare accounts, or a Worker binding instead of a URL.\n\n[[embed:source:s2]]\n\n## Creating one: five clicks, or one POST\n\n**Dashboard.** **AI** → **AI Gateway** → **Create Gateway** → type a **Gateway name** (64-character limit; the name becomes the gateway id and appears in every request URL) → **Create**.\n\n**API**, with a token carrying `AI Gateway` · `Edit`:\n\n```bash\ncurl -X POST \"https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/ai-gateway/gateways\" \\\n  -H \"Authorization: Bearer <TOKEN>\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\"id\":\"coding\",\"cache_ttl\":0,\"collect_logs\":true,\n       \"rate_limiting_interval\":60,\"rate_limiting_limit\":120,\"rate_limiting_technique\":\"sliding\"}'\n```\n\nWhat comes back is the gateway's full configuration, the same object `GET /ai-gateway/gateways` returns. This is the live record for the authenticated gateway on the account measured throughout this page, account id removed:\n\n```json\n{\n  \"id\": \"default\",\n  \"created_at\": \"2026-06-11 17:31:56\",\n  \"modified_at\": \"2026-07-26 02:19:03\",\n  \"rate_limiting_interval\": 60,\n  \"rate_limiting_limit\": 120,\n  \"rate_limiting_technique\": \"sliding\",\n  \"cache_ttl\": 0,\n  \"log_management\": 10000000,\n  \"log_management_strategy\": \"DELETE_OLDEST\",\n  \"authentication\": true,\n  \"collect_logs\": true,\n  \"cache_invalidate_on_update\": false,\n  \"logpush\": false,\n  \"wholesale\": true,\n  \"zdr\": false,\n  \"store_id\": \"\",\n  \"is_default\": true,\n  \"workers_ai_billing_mode\": \"postpaid\",\n  \"retry_max_attempts\": null,\n  \"retry_delay\": null,\n  \"retry_backoff\": null\n}\n```\n\nRead it as a checklist. `authentication: true`: every request needs a credential. `cache_ttl: 0`: nothing is cached unless a request asks. `rate_limiting_limit: 120` with `interval: 60`, `technique: \"sliding\"`: no more than 120 requests in any trailing 60 seconds. `log_management: 10000000` with `DELETE_OLDEST`: the ten-millionth log evicts the first rather than stopping collection.\n\n[[embed:source:s16]]\n\n## Three URL shapes, and two of them are deprecated\n\n| Shape | URL | Status | Auth |\n| --- | --- | --- | --- |\n| Provider-native | `https://gateway.ai.cloudflare.com/v1/<ACCOUNT_ID>/<GATEWAY_ID>/<PROVIDER>/<provider path>` | Current | Provider's own key, plus `cf-aig-authorization` when the gateway is authenticated |\n| OpenAI-compatible on the gateway host | `https://gateway.ai.cloudflare.com/v1/<ACCOUNT_ID>/<GATEWAY_ID>/compat/chat/completions` | Deprecated, still works | Same |\n| Cloudflare REST API | `https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/ai/v1/chat/completions` | Current, recommended for new work | `Authorization: Bearer <TOKEN>` |\n\nThe old **Universal Endpoint** — `POST https://gateway.ai.cloudflare.com/v1/<ACCOUNT_ID>/<GATEWAY_ID>` with an array of provider objects as the body, each carrying `provider`, `endpoint`, `headers` and `query`, tried in order as fallbacks — is deprecated too; fallback and retry work now points at Dynamic Routing.\n\nModel support across the four REST endpoints is not uniform:\n\n| Endpoint | Body format | Third-party models | Workers AI `@cf/` models |\n| --- | --- | --- | --- |\n| `POST /ai/run` | `{\"model\":…,\"input\":{…}}` | Yes | Yes, with `cf-aig-gateway-id` |\n| `POST /ai/v1/chat/completions` | OpenAI chat completions | Yes | Yes, with `cf-aig-gateway-id` |\n| `POST /ai/v1/responses` | OpenAI Responses | Yes | Model-dependent |\n| `POST /ai/v1/messages` | Anthropic Messages | Yes | **No** |\n\nThird-party models are named `author/model` (`openai/gpt-4.1`, `xai/grok-3`). Cloudflare-hosted models are `@cf/author/model` and reach a gateway only when the request carries `cf-aig-gateway-id`; without it the call still runs, it is simply not logged, cached or rate-limited by any gateway.\n\n[[embed:source:s3]]\n\n## A token the REST API accepts is refused by the provider-native host\n\nThe two hosts do not check the same thing, and the failure gives no hint.\n\n**A. Provider-native host, authenticated gateway, no `cf-aig-authorization`:**\n\n```bash\ncurl -sS -X POST \"https://gateway.ai.cloudflare.com/v1/<ACCOUNT_ID>/default/workers-ai/v1/chat/completions\" \\\n  -H \"Authorization: Bearer <TOKEN>\" -H 'content-type: application/json' \\\n  -d '{\"model\":\"@cf/moonshotai/kimi-k2.7-code\",\"max_tokens\":8,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}'\n```\n\n```json\n{\"success\":false,\"result\":[],\"messages\":[],\"error\":[{\"code\":2009,\"message\":\"Unauthorized\"}],\n \"name\":\"AiGatewayError\",\"httpCode\":401,\"internalCode\":2009,\"message\":\"Unauthorized\"}\n```\n\n**B.** The same call with `-H \"cf-aig-authorization: Bearer <TOKEN>\"` added returns a byte-identical body. The token was a valid Cloudflare token lacking `AI Gateway` · `Run`, so a missing header and an under-permissioned token are indistinguishable from the response. Check the header first, then the permissions.\n\n**C. REST host, same token, same gateway, same model, no `cf-aig-authorization` at all:**\n\n```bash\ncurl -sS -X POST \"https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/ai/v1/chat/completions\" \\\n  -H \"Authorization: Bearer <TOKEN>\" -H \"cf-aig-gateway-id: default\" \\\n  -H 'content-type: application/json' \\\n  -d '{\"model\":\"@cf/moonshotai/kimi-k2.7-code\",\"max_tokens\":12,\"messages\":[{\"role\":\"user\",\"content\":\"Say OK\"}]}'\n```\n\n```json\n{\"id\":\"id-1785040709314\",\"object\":\"chat.completion\",\"created\":1785040709,\n \"model\":\"@cf/moonshotai/kimi-k2.7-code\",\n \"usage\":{\"prompt_tokens\":10,\"completion_tokens\":12,\"total_tokens\":22,\n          \"prompt_tokens_details\":{\"cached_tokens\":0},\"neurons\":5.227272987365723}}\n```\n\nHTTP 200, logged on the authenticated gateway. The rule: `AI Gateway` · `Run` is a requirement of the `gateway.ai.cloudflare.com` host, not of authenticated gateways as such.\n\n[[embed:source:s17]]\n\n## Authentication breaks every client whose whole config is a base URL and a key\n\nWith authentication on, a provider-native request must carry `cf-aig-authorization: Bearer <TOKEN>` alongside whatever credential the provider itself wants. Cloudflare's behaviour table:\n\n| Authentication | Header | Result |\n| --- | --- | --- |\n| On | Present | Succeeds |\n| On | Absent | Fails |\n| Off | Present | Succeeds |\n| Off | Absent | Succeeds |\n\nA client whose entire configuration is a base URL and an API key has no slot for a second header. Cloudflare's own container-based coding agent hit exactly that:\n\n> When Authenticated Gateway is enabled on the AI Gateway, all requests from Moltworker fail because the required `cf-aig-authorization` header is never sent.\n\nThe documented escape hatch — a Worker **binding**, pre-authenticated by account identity and needing no header — is unavailable when the calling binary lives inside a container that only received a base URL.\n\nThe neighbouring failure is the shape of the *first* header rather than the absence of the second. Anthropic's API wants `x-api-key`; a client hardcoding `Authorization: Bearer` gets a `401` that reads like a bad key:\n\n> cc-switch currently sends `Authorization: Bearer <key>` for all upstream providers. Cloudflare AI Gateway requires `x-api-key: <anthropic_key>` instead, causing 401 errors when trying to use it as a proxy.\n\nThat project's fix: detect any base URL containing `gateway.ai.cloudflare.com`, switch to an `x-api-key` strategy, add an optional `cf-aig-authorization` from an environment variable. When choosing a client, that pair of features is what to look for.\n\n[[embed:source:s10]]\n[[embed:source:s13]]\n\n## The `cf-aig-*` headers, their defaults, and what each changes\n\nA request-level header always wins over the gateway setting; the gateway setting is the default when no header is sent.\n\n| Header | Default | Observable effect |\n| --- | --- | --- |\n| `cf-aig-gateway-id` | none | Routes a REST API call through the named gateway. Required for `@cf/` models to be logged by a gateway at all. |\n| `cf-aig-authorization` | none | `Bearer <TOKEN>` for the `gateway.ai.cloudflare.com` hosts when authentication is on. Missing or under-permissioned gives `401 code 2009`. |\n| `cf-aig-metadata` | none | Up to 5 JSON entries stored on the log row, filterable in the dashboard, queryable as `metadataRaw`. |\n| `cf-aig-cache-ttl` | gateway `cache_ttl`, `0` on a new gateway | Seconds to keep the response. Minimum 60, maximum one month. |\n| `cf-aig-skip-cache` | `false` | `true` bypasses the cache for that request. |\n| `cf-aig-cache-key` | none | Replaces the default hashed key. With no gateway caching enabled, such a response lives 5 minutes. |\n| `cf-aig-max-attempts` / `cf-aig-retry-delay` / `cf-aig-backoff` | gateway retry settings, unset on a new gateway | Up to 5 attempts, delay up to 5000 ms, backoff `constant`, `linear` or `exponential`. On the last attempt the gateway waits however long the provider takes. |\n| `cf-aig-request-timeout` | none | Milliseconds before the request errors or falls back. Measured from the first byte, so a slow stream does not trip it. |\n| `cf-aig-collect-log` | gateway `collect_logs` | `false` drops the whole row, metrics included. |\n| `cf-aig-collect-log-payload` | `true` when logging is on | `false` keeps the metrics and drops the stored request and response bodies. |\n| `cf-aig-custom-cost` | none | Overrides the cost figure on the row with your negotiated rates. |\n| `cf-aig-cache-status` (response) | — | `HIT` or `MISS`. See the caveat below. |\n| `cf-aig-step` (response) | — | Which fallback step answered; `0` is the primary. |\n\n[[embed:source:s4]]\n\n## The cache key is a hash of the whole request, which is why real traffic never hits it\n\nCloudflare builds the key by concatenating provider, endpoint path, model, the provider authentication header and **the full request body**, then hashing with SHA-256. Any byte that moves, moves the key.\n\nAn operator who put production support-bot traffic through a gateway measured the consequence:\n\n> I was routing support-bot traffic through Cloudflare AI Gateway and  noticed the cache hit rate was near zero despite the product being  mature.\n\nHe pulled 500 consecutive misses, stripped each to the user message, and found 89% mapped to fewer than 30 distinct intents. Request ids, trace ids, timestamps and session wrappers riding in the body moved the keys — not the questions. His answer was a Worker that canonicalises the request before the gateway sees it, with Vectorize for near-matches and a Durable Object per hot key.\n\nThe mechanism reproduces in five requests, same gateway, `cf-aig-cache-ttl: 300` on all five:\n\n| Request | Body | Result |\n| --- | --- | --- |\n| 1 | `{\"model\":\"@cf/moonshotai/kimi-k2.7-code\",\"max_tokens\":8,\"messages\":[{\"role\":\"user\",\"content\":\"cache probe alpha\"}]}` | miss (first fill) |\n| 2 | identical to 1 | **hit** |\n| 3 | identical to 1 | **hit** |\n| 4 | same user message, system message `requestId=<uuid>` | miss |\n| 5 | same user message, system message `requestId=<uuid>` (a different uuid) | miss |\n\nTwo hits out of five, and both misses in the second pair carried a question the gateway had already answered. One 36-character field was enough.\n\nThree things follow. Strip request ids, timestamps and session wrappers from the body, or move them into `cf-aig-metadata`, where they are logged but not hashed. Use `cf-aig-cache-key` to pin the key to the part of the request that determines the answer. Expect no semantic matching: Cloudflare says caching \"applies only to identical requests\" and that semantic search is future work.\n\nOne measured gap. On the `api.cloudflare.com` REST host the two cached responses came back **without** a `cf-aig-cache-status` header, though the documentation says to read that header to tell a hit from a miss. Their headers were `date`, `content-type`, `content-length`, `api-version`, `cf-ai-neurons`, `cf-auditlog-id`, `server` and `cf-ray` — no `cf-aig-*` at all — while the analytics records both as `cached: 1`. On that host the analytics is the only cache signal.\n\n[[embed:source:s11]]\n[[embed:source:s5]]\n[[embed:source:s18]]\n\n## Rate limiting is what stands between a leaked token and your balance\n\nBecause a `Run` token reaches every gateway on the account, and Unified Billing spends real credits, the gateway limit is the blast-radius control. **AI** → **AI Gateway** → your gateway → **Settings** → **Rate-limiting**, then set a count, a period, and fixed or sliding.\n\nThe two techniques differ. With ten requests per ten minutes starting at 12:00, a fixed window runs 12:00–12:10 then 12:10–12:20, so ten requests at 12:09 and ten more at 12:11 all succeed; a sliding window rejects the second batch because it counts the trailing ten minutes. Over the limit is `429 Too Many Requests` and the request is not forwarded.\n\nThe gateway measured here runs 120 per 60 seconds, sliding. Over the 30 days to 2026-07-26 that produced exactly one `429` in 5,252 requests across the account — no obstruction to normal work, and a cap of 172,800 requests a day on a runaway loop instead of no cap at all. Unified Billing adds a ceiling you cannot raise: 200 requests per 60 seconds per gateway.\n\n[[embed:source:s6]]\n\n## Logs keep the prompt and the completion, and they are how you audit spend\n\nA row carries the user prompt, the model response, the provider, the timestamp, the status, token usage, cost, duration and the client's user agent. Bodies are stored unless the request sends `cf-aig-collect-log-payload: false`, which keeps the metrics and drops the text.\n\n| Limit | Value |\n| --- | --- |\n| Logs stored, Workers Paid | 10,000,000 per gateway |\n| Logs stored, Workers Free | 100,000 across all gateways |\n| Size of one log | 10 MB |\n| Log write rate | 500 per second per gateway |\n| Custom metadata | 5 entries per request |\n| Cacheable request size | 25 MB |\n| Gateways per account | 10 free, 20 paid |\n\nRetention is a count, not a clock: logs are kept until the gateway hits its storage limit, then either the oldest are deleted (`log_management_strategy: \"DELETE_OLDEST\"`) or new logs stop being saved.\n\nTwo ways to read them without the dashboard. The Logs API, `GET /accounts/<ACCOUNT_ID>/ai-gateway/gateways/<GATEWAY_ID>/logs`, needs `AI Gateway` · `Read`. The GraphQL dataset `aiGatewayRequestsAdaptiveGroups` needs only account analytics access and produced every measurement here:\n\n```bash\ncurl -sS -X POST https://api.cloudflare.com/client/v4/graphql \\\n  -H \"Authorization: Bearer <TOKEN>\" -H 'content-type: application/json' \\\n  -d '{\"query\":\"query { viewer { accounts(filter:{accountTag:\\\"<ACCOUNT_ID>\\\"}) {\n        aiGatewayRequestsAdaptiveGroups(limit:20, filter:{date_geq:\\\"2026-07-26\\\", gateway:\\\"default\\\"}) {\n          count dimensions { datetimeMinute model provider statusCode cached durationMs\n                             tokensIn tokensOut cost } } } } }\"}'\n```\n\nSix real rows from that gateway:\n\n| Time (UTC) | Model | Provider | Status | Cached | Duration ms | Tokens in | Tokens out | Cost USD |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n| 2026-07-26T04:35 | `@cf/zai-org/glm-4.7-flash` | workers-ai | 200 | 0 | 7413 | 46 | 587 | 0.00023756 |\n| 2026-07-26T04:35 | `@cf/zai-org/glm-5.2` | workers-ai | 200 | 0 | 2335 | 53 | 135 | 0.0006682 |\n| 2026-07-26T04:35 | `minimax/m3` | minimax | 200 | 0 | 1145 | 217 | 68 | 0.00011934 |\n| 2026-07-26T04:36 | `anthropic/claude-sonnet-4-5` | unknown | 500 | 0 | 677 | 0 | 0 | 0 |\n| 2026-07-26T04:37 | `anthropic/claude-sonnet-5` | anthropic | 200 | 0 | 406 | 16 | 4 | 0.000072 |\n| 2026-07-26T04:37 | `anthropic/claude-sonnet-5` | unknown | 400 | 0 | 316 | 0 | 0 | 0 |\n\nRead the failures too. A model id that is not on the catalogue produces `provider: \"unknown\"`, a `500` and a cost of zero — an attempt logged that never reached a provider. The `400` on the last line is the same shape.\n\nMetadata is stored verbatim, so send something useful. From the same gateway, session identifier removed:\n\n```json\n{\"via\":\"claude-code\",\"shim\":\"api/aig\",\"model_asked\":\"kimi\",\"session\":\"<REDACTED>\",\"tools\":9}\n```\n\nThat is what makes cost attributable per agent instead of per account.\n\n[[embed:source:s7]]\n[[embed:source:s14]]\n\n## What it costs, and the one number people report as wrong\n\nThe gateway is free on every plan: dashboard analytics, caching and rate limiting are core features at no charge, and persistent logs are included within the storage limits above. Logpush is Workers Paid only — 10 million requests a month, then $0.05 per million. Unified Billing adds 5% to credit purchases, so a $100 top-up is charged as $105, while per-token inference is the provider's own rate with no markup.\n\nMeasured spend on the authenticated gateway, 30 days to 2026-07-26: 107 requests, 651,894 input tokens, 8,342 output tokens, **$0.44830454**. The second gateway on the same account carried the bulk: 5,145 requests, $32.19921439. Both are the gateway's own cost column summed by GraphQL.\n\nThat column is where the complaint sits. Cloudflare labels it an estimate:\n\n> The cost metric is an **estimation** based on the number of tokens sent and received in requests. While this metric can help you monitor and predict cost trends, refer to your provider's dashboard for the most **accurate** cost details.\n\nA customer running production traffic on flagship image models says it is not merely imprecise:\n\n> because Cloudflare AI Gateway is reporting inaccurate/wrong price for flagship models such as Nano Banana 2 and Nano Banana pro (I run production app using those). Been reporting it on discord and twitter, and they don't care.\n\nBoth can be true, and together they set the rule: use the cost column for trends and per-agent attribution, reconcile against the provider's invoice before billing anyone, and if you have negotiated rates send `cf-aig-custom-cost` so the column reflects your contract rather than list price.\n\n[[embed:source:s8]]\n[[embed:source:s9]]\n\n## The hop costs less than the noise\n\nNo Cloudflare-published overhead figure exists; the circulating numbers are community estimates of roughly 10 to 50 ms, and cross-vendor benchmarks are not comparable because \"almost every one of these benchmarks clocks proxy forwarding against a mock upstream, which removes the one variable that dominates real requests: the model provider's own response time.\" Measured here with one model, one prompt, `max_tokens: 1`, `cf-aig-skip-cache: true`, six `curl -w \"%{time_total}\"` runs each way, the only difference being the `cf-aig-gateway-id` header: median **0.741 s** through the gateway against **0.648 s** without, both arms spanning roughly 0.5 to 1.3 seconds. The 93 ms gap sits inside run-to-run variance at n=6 — evidence the hop is too small to separate from jitter, not evidence of a 93 ms tax.\n\n[[embed:source:s15]]\n\n## Where the traffic egresses is a failure mode nobody writes down\n\nCloudflare answers from the point of presence nearest the caller, and the provider sees the request coming from there. If the provider blocks a country Cloudflare has a PoP in, requests fail non-deterministically depending on where they land:\n\n> this made Cloudflare's AI Gateway an unusable product, as they hilariously put a node in HK so you never know when your Anthropic request is randomly going to fail defeating the whole purpose behind the product.\n\nNo gateway setting pins egress. The mitigations are the REST API host, which terminates on Cloudflare's API rather than the anycast gateway edge; Unified Billing, so the provider sees Cloudflare as the customer; or a Worker with fixed placement calling the provider directly.\n\nAgainst that, for people whose providers are not geo-blocking them the setup really is small:\n\n> We used cloudflare's AI gateway which is pretty simple. Set one up, get the proxy URL and set it through the env var, very plug-and-play\n\nBoth reports concern the same product a year apart and neither cancels the other. The gateway is one environment variable of work and a real geographic gamble on Anthropic traffic.\n\n[[embed:source:s12]]\n[[embed:source:s19]]\n\n## Symptom, cause, fix\n\n| Symptom | Cause | Fix |\n| --- | --- | --- |\n| `402` with `{\"errors\":[{\"message\":\"Gateway authentication is required to use unified billing. Enable authentication on your gateway or provide your own API key (BYOK).\",\"code\":2021}]}` | A third-party model routed through a gateway with `authentication: false`. `@cf/` models on the same gateway keep working, which disguises it. | Turn **Authenticated Gateway** on, or supply your own provider key. |\n| `401` with `{\"code\":2009,\"message\":\"Unauthorized\"}` from `gateway.ai.cloudflare.com` | Either no `cf-aig-authorization`, or a token without `AI Gateway` · `Run`. The body is identical either way. | Add the header; if it is already there, re-mint the token with `Run`. |\n| `401` from a client that worked against Anthropic yesterday | The client sends `Authorization: Bearer` where the Anthropic-native path wants `x-api-key`. | Use a client that switches header shape on `gateway.ai.cloudflare.com`, or send `x-api-key` yourself. |\n| A custom provider request reaches `/v1/<path>` upstream when `base_url` has no `/v1` | Reported against `cloudflare/ai`: with `base_url https://api.exa.ai` and slug `exa-provider`, `POST …/custom-exa-provider/search` arrived at `/v1/search`. | Until it is fixed, set `base_url` so the prepended `/v1` lands on the real path, and verify against the upstream's access log. |\n| A dynamic route to a Workers AI model returns `400` on the universal endpoint but `200` on `/compat/chat/completions` | Reported against `ai-gateway-provider`: same model, prompt and OpenAI-shaped body, two outcomes; reproduced with a raw universal-endpoint request, so it is not the SDK serialiser. | Send dynamic Workers AI routes to `/compat/chat/completions`, or move to the REST API host. |\n| Anthropic requests fail at random and succeed on retry | The request egressed from a PoP in a region Anthropic blocks. | Use the `api.cloudflare.com` REST host, or Unified Billing, rather than the anycast gateway host. |\n| `429` | Your gateway rate limit, your spend limit, or the 200-per-60s Unified Billing ceiling. | Raise the limit, wait out the window, or split traffic across gateways. |\n| Zero cache hits | `cache_ttl` is `0` on a new gateway, or the body carries a value that changes per request. | Set a TTL, and strip or relocate ids and timestamps. |\n| No rows in **Logs** | `collect_logs` off, `cf-aig-collect-log: false`, storage limit reached with `DELETE_OLDEST` off, or a `@cf/` model called on the REST API without `cf-aig-gateway-id`. | Check the gateway settings, then the header. |\n\n[[embed:source:s20]]\n[[embed:source:s21]]\n\n## Setup ends where billing and model choice begin\n\nCredits, the 5% fee and what Unified Billing does and does not cover: [Cloudflare Unified Billing](/a/cloudflare-unified-billing). Which `@cf/` models are worth pointing a coding agent at and what they cost: [Workers AI coding models](/a/workers-ai-coding-models). Wiring a coding agent to a gateway end to end, including non-Anthropic models behind an Anthropic-shaped client: [Claude Code on Kimi, GLM or Grok through your own Cloudflare account](/a/claude-code-on-cloudflare-ai-gateway).\n","register":"essay","tags":["tooling","cloudflare","ai-gateway"],"style":{},"claims":[{"id":"c1","text":"The default gateway is created automatically on first authenticated use and begins with authentication and logging on, caching and rate limiting off.","section":"The gateway called default exists before you create anything","tier":"system","source_ids":["s1","s16"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c2","text":"AI Gateway Read, Edit and Run have distinct jobs and cannot be restricted to one gateway, so a Run token is an account-wide spending credential.","section":"Prerequisites, with the exact labels","tier":"system","source_ids":["s2"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c3","text":"Cloudflare exposes provider-native and account REST endpoints with different schemas, authentication, deprecation status and model support.","section":"Three URL shapes, and two of them are deprecated","tier":"system","source_ids":["s3"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c4","text":"In the live comparison, the provider-native host returned 401 code 2009 for a token without AI Gateway Run while the account REST host returned HTTP 200 and logged the request.","section":"A token the REST API accepts is refused by the provider-native host","tier":"system","source_ids":["s17"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c5","text":"Authenticated provider-native gateways require a second cf-aig-authorization header, and some base-URL clients cannot add it or send the provider's required first header shape.","section":"Authentication breaks every client whose whole config is a base URL and a key","tier":"system","source_ids":["s10","s13"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c6","text":"Per-request cf-aig headers override gateway defaults for metadata, caching, retries, timeouts, logging and cost attribution.","section":"The cf-aig headers, their defaults, and what each changes","tier":"system","source_ids":["s4"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c7","text":"The default cache keys the whole request, so identical requests can hit while semantically identical bodies containing different UUIDs miss.","section":"The cache key is a hash of the whole request","tier":"system","source_ids":["s11","s18","s5"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c8","text":"A sliding limit of 120 requests per 60 seconds produced one 429 in 5,252 measured account requests and caps a runaway loop at 172,800 requests per day.","section":"Rate limiting is what stands between a leaked token and your balance","tier":"system","source_ids":["s16","s6"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c9","text":"Gateway logs retain request, response, model, status, usage, cost and timing unless payload collection or log collection is disabled.","section":"Logs keep the prompt and the completion","tier":"system","source_ids":["s7"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c10","text":"GraphQL aiGatewayRequestsAdaptiveGroups exposed per-request status, cache, duration, token and cost dimensions used for the article's measurements.","section":"Logs keep the prompt and the completion","tier":"system","source_ids":["s14","s16"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c11","text":"Cloudflare labels cost as an estimate, and a production operator reports wrong estimates for specific flagship image models.","section":"What it costs, and the one number people report as wrong","tier":"system","source_ids":["s8","s9"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c12","text":"The measured authenticated gateway logged 107 requests, 651,894 input tokens, 8,342 output tokens and $0.44830454 over 30 days; a second gateway logged 5,145 requests and $32.19921439.","section":"What it costs, and the one number people report as wrong","tier":"system","source_ids":["s16"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c13","text":"A six-run-per-arm probe measured medians of 0.741 seconds through the gateway and 0.648 seconds without it, a difference inside the observed run-to-run spread.","section":"The hop costs less than the noise","tier":"system","source_ids":["s15"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c14","text":"Anycast egress can make provider geo-blocking intermittent, as one Anthropic operator reported for requests landing in Hong Kong.","section":"Where the traffic egresses is a failure mode","tier":"system","source_ids":["s12"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c15","text":"When client and provider constraints align, an operator found setup amounted to creating a gateway and changing one environment variable.","section":"Where the traffic egresses is a failure mode","tier":"system","source_ids":["s19"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c16","text":"A reported custom-provider bug prepends /v1 to an upstream path even when the configured base URL omits it.","section":"Symptom, cause, fix","tier":"system","source_ids":["s20"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."},{"id":"c17","text":"A reported dynamic Workers AI route fails through the universal endpoint while the same request succeeds through the compatibility endpoint.","section":"Symptom, cause, fix","tier":"system","source_ids":["s21"],"why_material":"This changes how the gateway should be configured or how its output should be interpreted."}],"sources":[{"id":"s1","type":"publisher_documentation","url":"https://developers.cloudflare.com/ai-gateway/get-started/","title":"Cloudflare AI Gateway — Get started","quote":"If no gateway with the ID default exists, one will be automatically created.","summary":"Documents the automatically created default gateway and its initial authentication, logging, caching, and rate-limit behavior.","publisher":"Cloudflare","date":"2026-07-26","claim_ids":["c1"]},{"id":"s2","type":"publisher_documentation","url":"https://developers.cloudflare.com/ai-gateway/configuration/authentication/","title":"Cloudflare AI Gateway — Authentication","quote":"The AI Gateway Read, Run, and Edit permissions cannot be restricted to a single gateway","summary":"Documents authenticated gateways, the cf-aig-authorization header, and account-wide AI Gateway permissions.","publisher":"Cloudflare","date":"2026-07-26","claim_ids":["c2"]},{"id":"s3","type":"publisher_documentation","url":"https://developers.cloudflare.com/ai-gateway/usage/rest-api/","title":"Cloudflare AI Gateway — REST API","quote":"Workers AI models (@cf/) do not support this schema.","summary":"Documents the account REST endpoints, request schemas, and model compatibility boundaries.","publisher":"Cloudflare","date":"2026-07-26","claim_ids":["c3"]},{"id":"s4","type":"publisher_documentation","url":"https://developers.cloudflare.com/ai-gateway/glossary/","title":"Cloudflare AI Gateway — Header glossary","quote":"Custom metadata allows you to tag requests with user IDs or other identifiers","summary":"Reference for cf-aig request and response headers, their defaults, and their effects.","publisher":"Cloudflare","date":"2026-07-26","claim_ids":["c6"]},{"id":"s5","type":"publisher_documentation","url":"https://developers.cloudflare.com/ai-gateway/features/caching/","title":"Cloudflare AI Gateway — Caching","quote":"Caching applies only to identical requests.","summary":"Documents the exact-request cache, TTL controls, custom keys, and cache-status response header.","publisher":"Cloudflare","date":"2026-07-26","claim_ids":["c7"]},{"id":"s6","type":"publisher_documentation","url":"https://developers.cloudflare.com/ai-gateway/features/rate-limiting/","title":"Cloudflare AI Gateway — Rate limiting","quote":"Once the rate limit is exceeded, requests will be blocked with a 429 status code.","summary":"Documents fixed and sliding windows and the HTTP response after the configured cap is reached.","publisher":"Cloudflare","date":"2026-07-26","claim_ids":["c8"]},{"id":"s7","type":"publisher_documentation","url":"https://developers.cloudflare.com/ai-gateway/observability/logging/","title":"Cloudflare AI Gateway — Logging","quote":"AI Gateway logs contain detailed information about requests and responses.","summary":"Documents stored request and response data, payload collection controls, and API access to log rows.","publisher":"Cloudflare","date":"2026-07-26","claim_ids":["c9"]},{"id":"s8","type":"publisher_documentation","url":"https://developers.cloudflare.com/ai-gateway/observability/costs/","title":"Cloudflare AI Gateway — Costs","quote":"The cost metric is an estimation based on the number of tokens sent and received in requests.","summary":"Cloudflare's qualification of gateway cost data as an estimate rather than an invoice.","publisher":"Cloudflare","date":"2026-07-26","claim_ids":["c11"]},{"id":"s9","type":"hn","url":"https://news.ycombinator.com/item?id=47806253","title":"Comment on Cloudflare's AI Platform","quote":"because Cloudflare AI Gateway is reporting inaccurate/wrong price for flagship models such as Nano Banana 2 and Nano Banana pro (I run production app using those).","summary":"A production operator reports incorrect price estimates for flagship image models despite repeated reports.","author":"sf_tristanb","publisher":"Hacker News","date":"2026-04-17","claim_ids":["c11"]},{"id":"s10","type":"github_issue","url":"https://github.com/cloudflare/moltworker/issues/74","title":"Authenticated AI Gateway support in Moltworker","quote":"When Authenticated Gateway is enabled on the AI Gateway, all requests from Moltworker fail because the required `cf-aig-authorization` header is never sent.","summary":"Concrete client failure when a base-URL integration has no way to add the gateway's second credential header.","author":"daical","publisher":"GitHub","date":"2026-01-30","claim_ids":["c5"]},{"id":"s11","type":"hn","url":"https://news.ycombinator.com/item?id=47301851","title":"Comment on AI Gateway cache hit rate","quote":"I was routing support-bot traffic through Cloudflare AI Gateway and  noticed the cache hit rate was near zero despite the product being  mature.","summary":"A production cache audit found 500 consecutive misses even though 89% of messages collapsed into fewer than 30 intents.","author":"idrisitanzil","publisher":"Hacker News","date":"2026-03-08","claim_ids":["c7"]},{"id":"s12","type":"hn","url":"https://news.ycombinator.com/item?id=42862073","title":"Comment on AI Gateway geographic egress","quote":"this made Cloudflare's AI Gateway an unusable product, as they hilariously put a node in HK so you never know when your Anthropic request is randomly going to fail defeating the whole purpose behind the product.","summary":"An operator reports intermittent Anthropic failures caused by gateway traffic egressing through Hong Kong.","author":"maeil","publisher":"Hacker News","date":"2025-01-29","claim_ids":["c14"]},{"id":"s13","type":"github_issue","url":"https://github.com/farion1231/cc-switch/issues/2311","title":"Cloudflare AI Gateway authentication support in cc-switch","quote":"cc-switch currently sends `Authorization: Bearer <key>` for all upstream providers. Cloudflare AI Gateway requires `x-api-key: <anthropic_key>` instead, causing 401 errors when trying to use it as a proxy.","summary":"Concrete Anthropic-native header mismatch and the proposed two-header client behavior.","author":"bdim404","publisher":"GitHub","date":"2026-04-24","claim_ids":["c5"]},{"id":"s14","type":"publisher_documentation","url":"https://developers.cloudflare.com/analytics/graphql-api/","title":"Cloudflare GraphQL Analytics API","quote":"The GraphQL Analytics API is the engine that powers all the product analytics on the Cloudflare dashboard.","summary":"The query surface used to read aiGatewayRequestsAdaptiveGroups for request, token, cost, cache, status, and duration measurements.","publisher":"Cloudflare","date":"2026-07-26","claim_ids":["c10"]},{"id":"s15","type":"first_party_measurement","url":"https://miscsubjects.com/a/cloudflare-ai-gateway-setup","title":"Gateway latency A/B probe","quote":"median 0.741 s through the gateway against 0.648 s without","summary":"Harness: six curl time_total runs per arm, one model, one prompt, max_tokens 1, cache bypassed; the gateway-id header was the only changed input.","publisher":"miscsubjects.com","date":"2026-07-26","claim_ids":["c13"]},{"id":"s16","type":"first_party_measurement","url":"https://developers.cloudflare.com/api/resources/ai_gateway/methods/create/","title":"Live gateway configuration and 30-day account measurement","quote":"\"rate_limiting_limit\": 120","summary":"Authenticated API read of the measured gateway plus a 30-day GraphQL aggregation: 5,252 account requests, one 429, and gateway-level token and cost totals.","publisher":"Cloudflare API","date":"2026-07-26","claim_ids":["c1","c10","c12","c8"]},{"id":"s17","type":"first_party_measurement","url":"https://developers.cloudflare.com/ai-gateway/configuration/authentication/","title":"Gateway-host versus REST-host authentication probe","quote":"\"code\":2009,\"message\":\"Unauthorized\"","summary":"Same token, gateway and model tested against both hosts: provider-native host rejected a token without Run while the REST host returned HTTP 200 and logged the request.","publisher":"Cloudflare","date":"2026-07-26","claim_ids":["c4"]},{"id":"s18","type":"first_party_measurement","url":"https://developers.cloudflare.com/ai-gateway/features/caching/","title":"Five-request exact-cache probe","quote":"miss, hit, hit, miss, miss","summary":"Five controlled requests showed identical bodies hitting after fill and UUID-bearing variants missing; GraphQL supplied cache truth when the REST response omitted cf-aig-cache-status.","publisher":"Cloudflare","date":"2026-07-26","claim_ids":["c7"]},{"id":"s19","type":"hn","url":"https://news.ycombinator.com/item?id=46100225","title":"Comment on AI Gateway setup","quote":"We used cloudflare's AI gateway which is pretty simple. Set one up, get the proxy URL and set it through the env var, very plug-and-play","summary":"Positive operator report that the base-URL setup can be almost frictionless when authentication and provider constraints fit the client.","author":"0xblacklight","publisher":"Hacker News","date":"2025-11-30","claim_ids":["c15"]},{"id":"s20","type":"github_issue","url":"https://github.com/cloudflare/ai/issues/476","title":"Custom provider endpoint prepends /v1","quote":"Gateway appears to prepend `/v1` to the upstream path even when the custom provider `base_url` is configured without `/v1`.","summary":"A reproducible custom-provider path mutation and its upstream-observed failure mode.","author":"matijagrcic","publisher":"GitHub","date":"2026-04-02","claim_ids":["c16"]},{"id":"s21","type":"github_issue","url":"https://github.com/cloudflare/ai/issues/617","title":"Dynamic Workers AI route fails on universal endpoint","quote":"A documented `ai-gateway-provider` dynamic route targeting Workers AI fails when it is sent through the AI Gateway universal endpoint.","summary":"A raw-request reproduction showing a dynamic Workers AI route failing on universal while the compatibility endpoint succeeds.","author":"Aqua-123","publisher":"GitHub","date":"2026-07-14","claim_ids":["c17"]}],"prov":{"model":"Opus 5 (Claude Code)","action":"write"}}