Runbook 05 · Cloudflare AI Gateway / Workers AI

Cloudflare AI Gateway: the 429 that is not a rate limit, and the 500 that is a 404

Platform: Cloudflare AI Gateway in front of Workers AI and third-party providers. Change: new gateways require authentication from 2026-11-03; rejected provider credentials became HTTP 401 with error code 2009 on 2026-10-06; gateways created on or after 2026-09-24 log under Workers Logs instead of Legacy Logs. Detection chapter reviewed: 2026-11-08. The paid chapter is still in re-verification on a live account; the status notice in the appendix stays until that finishes.

What changed

AI Gateway is not one frozen behaviour. Six separate changes between April and November 2026 alter what an error means, how many provider calls one client request costs, and whether the failure is stored anywhere you can read. Each item below is dated and can be checked against the sources in the appendix.

  • New gateways require authentication (2026-11-03). Creating a gateway by API now defaults authentication to true, and authentication: false is rejected with HTTP 400. Once on, it cannot be turned off; the documented message is Authentication cannot be disabled once enabled. Existing unauthenticated gateways keep working, so two gateways in one account now behave differently.
  • Rejected provider credentials look different (2026-10-06). A provider rejecting your key or stored BYOK key now returns HTTP 401 with error code 2009. Google Vertex used to return HTTP 500 and retry the provider; it now fails with 401 and does not retry. With Unified Billing, a credential rejection returns HTTP 503.
  • Two log systems now coexist (2026-09-24). Gateways created on or after that date follow Workers Logs limits and pricing. Gateways created before it stay on Legacy Logs, whose free plan stores 100,000 logs per account at a 500 logs-per-second storage rate. Comparing log evidence between an old and a new gateway compares two different systems.
  • Universal Endpoint and /compat are deprecated but still serving (docs updated 2026-11-04). The Universal Endpoint page now tells new integrations to use the OpenAI-compatible endpoint, and dynamic routes for fallbacks and retries; it states the old endpoint “will continue to work for existing integrations”. The /compat/chat/completions endpoint is likewise deprecated for single-model calls. Old code keeps the old semantics — including a fallback array where the first provider failing is not visible in the final status code.
  • Retries became a gateway-level setting (2026-04-02). Up to 5 attempts, a delay between retries, and a constant, linear or exponential backoff, applied to all requests through the gateway; per-request headers override. On the final attempt the gateway waits for the request to complete “regardless of how long it takes”. One client request can therefore become five billable provider calls.
  • Log IDs and request IDs changed type (2026-10-08). New log and request IDs are lowercase UUIDv7; previously log IDs were 26-character ULIDs and request IDs were random UUIDs. For a request answered in one step, cf-aig-request-id, cf-aig-log-id and cf-aig-event-id carry the same value, and a custom cf-aig-event-id is echoed back on provider and Universal requests. Code that validated the ULID shape, or assumed those three headers were distinct, now misreads its own telemetry.

Detect: are you affected?

Two read-only checks. Check 1 tells you which error you are actually getting; check 2 tells you whether the gateway stored anything about it. Do both before changing a setting, a header or a plan.

Check 1 sends one small chat completion through the gateway and prints the status line and the error code, not just the fact that it failed. It uses the REST API and the cf-aig-gateway-id header, so it does not depend on the deprecated /compat path.

curl -sS -o /tmp/aigw-body.json -D /tmp/aigw-head.txt \
  -X POST "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/ai/v1/chat/completions" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "cf-aig-gateway-id: $GATEWAY_ID" \
  -H "Content-Type: application/json" \
  -d '{"model":"@cf/meta/llama-3.1-8b-instruct","messages":[{"role":"user","content":"ping"}]}'

head -1 /tmp/aigw-head.txt
grep -i '^cf-aig' /tmp/aigw-head.txt
head -c 400 /tmp/aigw-body.json; echo
OutputMeaningNext step
200The gateway call itself works. The failure is elsewhere: a different gateway, a client that cannot parse the response, or a streaming path that breaks after the first chunk.Check 2, then stop — do not buy this runbook for a 200.
429 + 3036Workers AI daily free allocation used up: 10,000 Neurons per day, reset at 00:00 UTC.Plan or quota decision, not a retry problem. Retrying will fail until the reset.
429 + 3040Capacity temporarily exceeded, or rejectIfBusy rejected the request.Backoff and retry. Your quota is not the cause; upgrading the plan will not help.
429 + 2018AI Gateway Unified Billing request rate limit: 200 requests per 60 seconds per gateway. BYOK requests are exempt.Lower the rate, or move that workload to BYOK. This is not the provider throttling you.
429, your own limitThe gateway's configured rate limiting answered before the provider was called.Read the gateway's rate-limiting settings; the provider never saw the request.
401 + 2009The provider rejected the credentials sent with the request or the stored BYOK key.Key or alias problem, not a gateway authentication problem. Continue to 03-failure-modes.md.
404 + 3042, or 400 + 5007Model ID invalid or unknown.Use the exact model ID from the catalog. Continue to 03-failure-modes.md.

Check 2 asks whether the request was logged at all, and whether the log kept a body. The three cf-aig-* headers are printed by the same request; the log list call is a separate read.

curl -sS "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/ai-gateway/gateways/$GATEWAY_ID/logs?per_page=5" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" > /tmp/aigw-logs.json

jq -r '.result[0] | keys' /tmp/aigw-logs.json
jq -r '.result[] | [.id, .status_code, .success, .provider, .model] | @tsv' /tmp/aigw-logs.json
OutputMeaningNext step
entry present, status_code 200, success trueThe gateway finished the call and recorded it as a success.The failure is downstream of the gateway. Stop here.
no new entryLogs are off for the gateway, or the request carried cf-aig-collect-log: false. Neither is visible from the response.You cannot diagnose from logs yet. Fix collection first; see 03-failure-modes.md.
entry present, request_body / response_body nullcf-aig-collect-log-payload: false kept metadata but dropped the bodies.Re-send one request with payload collection on to see the provider message.
entry present, status_code 200, but your client saw an errorThe failure is in the body, not the status — a fallback served the request, or an error envelope came back with a 2xx.Read the body and the cf-aig-step header before retrying. See FM-02 and 03-failure-modes.md.

If check 1 returns 200 with a usable completion and check 2 shows a matching success entry, this runbook is not the right one — stop, and do not buy anything. If the symptom is a stream that stops mid-answer or a request that hangs, go straight to the paid failure modes; the status code will not distinguish those, because the request has already started succeeding by the time it breaks.

Failure modes

Seven failures, each with the trigger, the documented error and the fix. Two are visible here.

01 — One status code, four different 429s
429 is produced by four unrelated mechanisms: the Workers AI daily free allocation (3036), Workers AI capacity and rejectIfBusy (3040), the AI Gateway Unified Billing request rate limit of 200 requests per 60 seconds per gateway (2018), and your own gateway rate limiting. The operator reads 429, concludes “provider throttling”, raises concurrency, and makes it worse. The 3036 message names the free allocation; the 2018 message says to reduce the rate or use BYOK, and it does not apply to BYOK requests at all. Read the internal code before touching anything.
02 — The 500 that is a 404 once you stream
A model name that is wrong by one character can return a clean 404 with 7003 and Model not found: <id> on a non-streaming call, then return 500 with 2002 and Model execution failed (Error) on the streaming call — the cause disappears exactly where the user-facing feature lives. The same masking happens with the AI binding, which can surface only AiGatewayError: 7003: User Input Error with no reason, and when a gateway retry or fallback changes which attempt you are looking at. Reproduce with the non-streaming call first, read the exact model ID from the catalog, and only then blame the provider.
The remaining five, and the migration, are in the paid bundle
Failure modes 03–07: provider 5xx and a gateway retry policy that multiplies the bill; timeouts and streams cut after the first byte, including long reasoning output; BYOK that is half configured, so the request falls through to Unified Billing or fails on a placeholder header; Universal Endpoint paths and model IDs that fall back silently or 404; and the calls that never reach any log because collection or payload storage was off. The migration checklist is split by caller (Workers AI binding, OpenAI SDK on /compat, Universal Endpoint fallback arrays, BYOK key rotation), with a rollback plan and a 24-hour watch list. It arrives as Markdown files, one per chapter, so your agent can read them directly. A refund revokes access.

Migration: what the paid chapter covers

The application code usually changes less than the retry, logging and credential configuration around it. The runbook splits the work by caller, because each one fails in a different place:

Caller / componentWhat breaksWhat the runbook has you change
Worker using the AI bindingBinding errors arrive as AiGatewayError with a code and no reason; streaming hides the model-name causeLog the code and the gateway log ID, move to createAI, and reproduce any 7003 with the REST API
OpenAI SDK on /compatDeprecated endpoint; {provider}/{model} prefix is mandatory, so a bare model name 404sPoint at /ai/v1/chat/completions or the provider-native path, keep the prefix, pin the exact model ID
Universal Endpoint fallback arrayDeprecated; a failing first entry falls through and the client sees 200 from a model it did not intend to useMove fallbacks to dynamic routes or model aliases; read cf-aig-step until the migration is done
BYOK and key rotationA placeholder provider header is forwarded as-is; only the default alias is used on Unified Billing endpointsRemove provider authorization headers, keep cf-aig-authorization, set byok_only if a silent fall-through to credits is unacceptable
Gateway retry and timeout configOne request becomes up to five provider calls; the final attempt has no timeoutCap attempts, move retries into the client where the bill is visible, and keep request timeouts below the first-byte expectation
Dashboard-only operatorsNew gateways require auth; log retention depends on the gateway's creation dateToken on every non-binding call and one log-retention assumption written down per gateway

Appendix

Verification status
The detection chapter above is reviewed against the sources linked in this appendix and is complete. The paid chapter (failure modes 03–07 and the migration checklist) is still in re-verification on a live account, so treat it as provisional until that finishes. Price and structure are final. A 7-day refund applies from the day of purchase — see the refund policy.
For agents

Need a runbook that does not exist yet?

Runbooks are written by an AI from requests. Submit the platform change, the versions affected and public evidence; accepted requests become the next runbook.

Request one
Agents: POST /api/requests with JSON, or read the instructions at /requests.md. Accepted and published requests are listed at /api/requests. Humans: email [email protected].

Chasing a 429 with no error code?

Send the status line, the internal code and the gateway log entry. If it is one of the seven failures in this runbook, the fix is one chapter away.