
Cloudflare
- 75 installs
- 15 repo stars
- Updated August 1, 2026
- connorads/dotfiles
This is a copy of cloudflare by cloudflare - installs and ranking accrue to the original listing.
Helps with ai & agent building tasks.
About
cloudflare is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- cloudflare
- AI & Agent Building
- AI-coding skill
Cloudflare by the numbers
- 75 all-time installs (skills.sh)
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/connorads/dotfiles --skill cloudflareAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 75 |
|---|---|
| repo stars | ★ 15 |
| Last updated | August 1, 2026 |
| Repository | connorads/dotfiles ↗ |
What it does
Helps with ai & agent building tasks.
Files
Cloudflare Platform Skill
Consolidated skill for building on the Cloudflare platform. Use decision trees below to find the right product, then load detailed references.
Your knowledge of Cloudflare APIs, types, limits, and pricing may be outdated. Prefer retrieval over pre-training — the references in this skill are starting points, not source of truth.
Retrieval Sources
Fetch the latest information before citing specific numbers, API signatures, or configuration options. Do not rely on baked-in knowledge or these reference files alone.
| Source | How to retrieve | Use for |
|---|---|---|
| Cloudflare docs | cloudflare-docs search tool or https://developers.cloudflare.com/ | Limits, pricing, API reference, compatibility dates/flags |
| Workers types | npm pack @cloudflare/workers-types or check node_modules | Type signatures, binding shapes, handler types |
| Wrangler config schema | node_modules/wrangler/config-schema.json | Config fields, binding shapes, allowed values |
| Product changelogs | https://developers.cloudflare.com/changelog/ | Recent changes to limits, features, deprecations |
When a reference file and the docs disagree, trust the docs. This is especially important for: numeric limits, pricing tiers, type signatures, and configuration options.
Quick Decision Trees
"I need to run code"
Need to run code?
├─ Serverless functions at the edge → workers/
├─ Full-stack web app with Git deploys → pages/
├─ Stateful coordination/real-time → durable-objects/
├─ Long-running multi-step jobs → workflows/
├─ Run containers → containers/
├─ Multi-tenant (customers deploy code) → workers-for-platforms/
├─ Scheduled tasks (cron) → cron-triggers/
├─ Lightweight edge logic (modify HTTP) → snippets/
├─ Process Worker execution events (logs/observability) → tail-workers/
└─ Optimize latency to backend infrastructure → smart-placement/"I need to store data"
Need storage?
├─ Key-value (config, sessions, cache) → kv/
├─ Relational SQL → d1/ (SQLite) or hyperdrive/ (existing Postgres/MySQL)
├─ Object/file storage (S3-compatible) → r2/
├─ Versioned file trees (repos, build outputs, checkpoints) → artifacts/
├─ Message queue (async processing) → queues/
├─ Vector embeddings (AI/semantic search) → vectorize/
├─ Strongly-consistent per-entity state → durable-objects/ (DO storage)
├─ Secrets management → secrets-store/
├─ Streaming ETL to R2 → pipelines/
└─ Persistent cache (long-term retention) → cache-reserve/"I need AI/ML"
Need AI?
├─ Run inference (LLMs, embeddings, images) → workers-ai/
├─ Vector database for RAG/search → vectorize/
├─ Build stateful AI agents → agents-sdk/
├─ Gateway for any AI provider (caching, routing) → ai-gateway/
└─ AI-powered search widget → ai-search/"I need networking/connectivity"
Need networking?
├─ Expose local service to internet → tunnel/
├─ TCP/UDP proxy (non-HTTP) → spectrum/
├─ WebRTC TURN server → turn/
├─ Private network connectivity → network-interconnect/
├─ Optimize routing → argo-smart-routing/
├─ Optimize latency to backend (not user) → smart-placement/
└─ Real-time video/audio → realtimekit/ or realtime-sfu/"I need security"
Need security?
├─ Web Application Firewall → waf/
├─ DDoS protection → ddos/
├─ Bot detection/management → bot-management/
├─ API protection → api-shield/
├─ CAPTCHA alternative → turnstile/
└─ Credential leak detection → waf/ (managed ruleset)"I need media/content"
Need media?
├─ Image optimization/transformation → images/
├─ Video streaming/encoding → stream/
├─ Browser automation/screenshots → browser-rendering/
└─ Third-party script management → zaraz/"I need analytics/metrics data"
Need analytics?
├─ Query across all Cloudflare products (HTTP, Workers, DNS, etc.) → graphql-api/
├─ Custom high-cardinality metrics from Workers → analytics-engine/
├─ Client-side (RUM) performance data → web-analytics/
├─ Workers Logs and real-time debugging → observability/
└─ Raw logs (Logpush to external tools) → Cloudflare docs"I need infrastructure-as-code"
Need IaC? → pulumi/ (Pulumi), terraform/ (Terraform), or api/ (REST API)Product Index
Compute & Runtime
| Product | Reference |
|---|---|
| Workers | references/workers/ |
| Pages | references/pages/ |
| Pages Functions | references/pages-functions/ |
| Durable Objects | references/durable-objects/ |
| Workflows | references/workflows/ |
| Containers | references/containers/ |
| Workers for Platforms | references/workers-for-platforms/ |
| Cron Triggers | references/cron-triggers/ |
| Tail Workers | references/tail-workers/ |
| Snippets | references/snippets/ |
| Smart Placement | references/smart-placement/ |
Storage & Data
| Product | Reference |
|---|---|
| KV | references/kv/ |
| D1 | references/d1/ |
| R2 | references/r2/ |
| Artifacts | references/artifacts/ |
| Queues | references/queues/ |
| Hyperdrive | references/hyperdrive/ |
| DO Storage | references/do-storage/ |
| Secrets Store | references/secrets-store/ |
| Pipelines | references/pipelines/ |
| R2 Data Catalog | references/r2-data-catalog/ |
| R2 SQL | references/r2-sql/ |
AI & Machine Learning
| Product | Reference |
|---|---|
| Workers AI | references/workers-ai/ |
| Vectorize | references/vectorize/ |
| Agents SDK | references/agents-sdk/ |
| AI Gateway | references/ai-gateway/ |
| AI Search | references/ai-search/ |
Networking & Connectivity
| Product | Reference |
|---|---|
| Tunnel | references/tunnel/ |
| Spectrum | references/spectrum/ |
| TURN | references/turn/ |
| Network Interconnect | references/network-interconnect/ |
| Argo Smart Routing | references/argo-smart-routing/ |
| Workers VPC | references/workers-vpc/ |
Security
| Product | Reference |
|---|---|
| WAF | references/waf/ |
| DDoS Protection | references/ddos/ |
| Bot Management | references/bot-management/ |
| API Shield | references/api-shield/ |
| Turnstile | references/turnstile/ |
Media & Content
| Product | Reference |
|---|---|
| Images | references/images/ |
| Stream | references/stream/ |
| Browser Rendering | references/browser-rendering/ |
| Zaraz | references/zaraz/ |
Real-Time Communication
| Product | Reference |
|---|---|
| RealtimeKit | references/realtimekit/ |
| Realtime SFU | references/realtime-sfu/ |
Developer Tools
| Product | Reference |
|---|---|
| Wrangler | references/wrangler/ |
| Miniflare | references/miniflare/ |
| C3 | references/c3/ |
| Observability | references/observability/ |
| GraphQL Analytics API | references/graphql-api/ |
| Analytics Engine | references/analytics-engine/ |
| Web Analytics | references/web-analytics/ |
| Sandbox | references/sandbox/ |
| Workerd | references/workerd/ |
| Workers Playground | references/workers-playground/ |
Infrastructure as Code
| Product | Reference |
|---|---|
| Pulumi | references/pulumi/ |
| Terraform | references/terraform/ |
| API | references/api/ |
Other Services
| Product | Reference |
|---|---|
| Email Routing | references/email-routing/ |
| Email Workers | references/email-workers/ |
| Static Assets | references/static-assets/ |
| Bindings | references/bindings/ |
| Cache Reserve | references/cache-reserve/ |
API Reference
Agent Classes
AIChatAgent
For AI chat with auto-streaming, message history, tools, resumable streaming.
import { AIChatAgent } from "@cloudflare/ai-chat";
import { openai } from "@ai-sdk/openai";
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
return this.streamText({
model: openai("gpt-4"),
messages: this.messages, // Auto-managed message history
tools: {
getWeather: {
description: "Get weather",
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => `Sunny, 72°F in ${city}`
}
},
onFinish, // Persist response to this.messages
});
}
}Agent (Base Class)
Full control for custom logic, WebSockets, email, and SQL.
import { Agent } from "agents";
export class MyAgent extends Agent<Env, State> {
// Lifecycle methods below
}Type params: Agent<Env, State, ConnState> - Env bindings, agent state, connection state
Lifecycle Hooks
onStart() { // Init/restart
this.sql`CREATE TABLE IF NOT EXISTS users (id TEXT, name TEXT)`;
}
async onRequest(req: Request) { // HTTP
const {pathname} = new URL(req.url);
if (pathname === "/users") return Response.json(this.sql<{id,name}>`SELECT * FROM users`);
return new Response("Not found", {status: 404});
}
async onConnect(conn: Connection<ConnState>, ctx: ConnectionContext) { // WebSocket
conn.accept();
conn.setState({userId: ctx.request.headers.get("X-User-ID")});
conn.send(JSON.stringify({type: "connected", state: this.state}));
}
async onMessage(conn: Connection<ConnState>, msg: WSMessage) { // WS messages
const m = JSON.parse(msg as string);
this.setState({messages: [...this.state.messages, m]});
this.connections.forEach(c => c.send(JSON.stringify(m)));
}
async onEmail(email: AgentEmail) { // Email routing
this.sql`INSERT INTO emails (from_addr,subject,body) VALUES (${email.from},${email.headers.get("subject")},${await email.text()})`;
}State, SQL, Scheduling
// State
this.setState({count: 42}); // Auto-syncs
this.setState({...this.state, count: this.state.count + 1});
// SQL (parameterized queries prevent injection)
this.sql`CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, name TEXT)`;
this.sql`INSERT INTO users (id,name) VALUES (${userId},${name})`;
const users = this.sql<{id,name}>`SELECT * FROM users WHERE id = ${userId}`;
// Scheduling
await this.schedule(new Date("2026-12-25"), "sendGreeting", {msg:"Hi"}); // Date
await this.schedule(60, "checkStatus", {}); // Delay (sec)
await this.schedule("0 0 * * *", "dailyCleanup", {}); // Cron
await this.cancelSchedule(scheduleId);RPC Methods (@callable)
import { Agent, callable } from "agents";
export class MyAgent extends Agent<Env> {
@callable()
async processTask(input: {text: string}): Promise<{result: string}> {
return { result: await this.env.AI.run("@cf/meta/llama-3.1-8b-instruct", {prompt: input.text}) };
}
}
// Client: const result = await agent.processTask({ text: "Hello" });
// Must return JSON-serializable valuesConnections & AI
// Connections (type: Agent<Env, State, ConnState>)
this.connections.forEach(c => c.send(JSON.stringify(msg))); // Broadcast
conn.setState({userId:"123"}); conn.close(1000, "Goodbye");
// Workers AI
const r = await this.env.AI.run("@cf/meta/llama-3.1-8b-instruct", {prompt});
// Manual streaming (prefer AIChatAgent)
const stream = await client.chat.completions.create({model: "gpt-4", messages, stream: true});
for await (const chunk of stream) conn.send(JSON.stringify({chunk: chunk.choices[0].delta.content}));Type-safe state: Agent<Env, State, ConnState> - third param types conn.state
MCP Integration
Model Context Protocol for exposing tools:
// Register & use MCP server
await this.mcp.registerServer("github", {
url: env.MCP_SERVER_URL,
auth: { type: "oauth", clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET }
});
const tools = await this.mcp.getAITools(["github"]);
return this.streamText({ model: openai("gpt-4"), messages: this.messages, tools, onFinish });Task Queue
await this.queue("processVideo", { videoId: "abc123" }); // Add task
const tasks = await this.dequeue(10); // Process up to 10Context & Cleanup
const agent = getCurrentAgent<MyAgent>(); // Get current instance
async destroy() { /* cleanup before agent destroyed */ }AI Integration
// Workers AI
const r = await this.env.AI.run("@cf/meta/llama-3.1-8b-instruct", {prompt});
// Manual streaming (prefer AIChatAgent for auto-streaming)
const stream = await client.chat.completions.create({model: "gpt-4", messages, stream: true});
for await (const chunk of stream) {
if (chunk.choices[0]?.delta?.content) conn.send(JSON.stringify({chunk: chunk.choices[0].delta.content}));
}Client Hooks (React)
// useAgent() - WebSocket connection + RPC
import { useAgent } from "agents/react";
const agent = useAgent({ agent: "MyAgent", name: "user-123" }); // name for idFromName
const result = await agent.processTask({ text: "Hello" }); // Call @callable methods
// agent.readyState: 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED
// useAgentChat() - AI chat UI
import { useAgentChat } from "@cloudflare/ai-chat/react";
const agent = useAgent({ agent: "ChatAgent" });
const { messages, input, handleInputChange, handleSubmit, isLoading, stop, clearHistory } =
useAgentChat({
agent,
maxSteps: 5, // Max tool iterations
resume: true, // Auto-resume on disconnect
onToolCall: async (toolCall) => {
// Client tools (human-in-the-loop)
if (toolCall.toolName === "confirm") return { ok: window.confirm("Proceed?") };
}
});
// status: "ready" | "submitted" | "streaming" | "error"Configuration
Wrangler Setup
{
"name": "my-agents-app",
"durable_objects": {
"bindings": [
{"name": "MyAgent", "class_name": "MyAgent"}
]
},
"migrations": [
{"tag": "v1", "new_sqlite_classes": ["MyAgent"]}
],
"ai": {
"binding": "AI"
}
}Environment Bindings
Type-safe pattern:
interface Env {
AI?: Ai; // Workers AI
MyAgent?: DurableObjectNamespace<MyAgent>;
ChatAgent?: DurableObjectNamespace<ChatAgent>;
DB?: D1Database; // D1 database
KV?: KVNamespace; // KV storage
R2?: R2Bucket; // R2 bucket
OPENAI_API_KEY?: string; // Secrets
GITHUB_CLIENT_ID?: string; // MCP OAuth credentials
GITHUB_CLIENT_SECRET?: string;
QUEUE?: Queue; // Queues
}Best practice: Define all DO bindings in Env interface for type safety.
Deployment
# Local dev
npx wrangler dev
# Deploy production
npx wrangler deploy
# Set secrets
npx wrangler secret put OPENAI_API_KEYAgent Routing
Recommended: Use route helpers
import { routeAgentRequest } from "agents";
export default {
fetch(request: Request, env: Env) {
return routeAgentRequest(request, env);
}
}Helper routes requests to agents automatically based on URL patterns.
Manual routing (advanced):
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
// Named ID (deterministic)
const id = env.MyAgent.idFromName("user-123");
// Random ID (from URL param)
// const id = env.MyAgent.idFromString(url.searchParams.get("id"));
const stub = env.MyAgent.get(id);
return stub.fetch(request);
}
}Multi-agent setup:
import { routeAgentRequest } from "agents";
export default {
fetch(request: Request, env: Env) {
const url = new URL(request.url);
// Route by path
if (url.pathname.startsWith("/chat")) {
return routeAgentRequest(request, env, "ChatAgent");
}
if (url.pathname.startsWith("/task")) {
return routeAgentRequest(request, env, "TaskAgent");
}
return new Response("Not found", { status: 404 });
}
}Email Routing
Code setup:
import { routeAgentEmail } from "agents";
export default {
fetch: (req: Request, env: Env) => routeAgentRequest(req, env),
email: (message: ForwardableEmailMessage, env: Env) => {
return routeAgentEmail(message, env);
}
}Dashboard setup:
Configure email routing in Cloudflare dashboard:
Destination: Workers with Durable Objects
Worker: my-agents-appThen handle in agent:
export class EmailAgent extends Agent<Env> {
async onEmail(email: AgentEmail) {
const text = await email.text();
// Process email
}
}AI Gateway (Optional)
// Enable caching/routing through AI Gateway
const response = await this.env.AI.run(
"@cf/meta/llama-3.1-8b-instruct",
{ prompt },
{
gateway: {
id: "my-gateway-id",
skipCache: false,
cacheTtl: 3600
}
}
);MCP Configuration (Optional)
For exposing tools via Model Context Protocol:
// wrangler.jsonc - Add MCP OAuth secrets
{
"vars": {
"MCP_SERVER_URL": "https://mcp.example.com"
}
}
// Set secrets via CLI
// npx wrangler secret put GITHUB_CLIENT_ID
// npx wrangler secret put GITHUB_CLIENT_SECRETThen register in agent code (see api.md MCP section).
Gotchas & Best Practices
Common Errors
"setState() not syncing"
Cause: Mutating state directly or not calling setState() after modifications Solution: Always use setState() with immutable updates:
// ❌ this.state.count++
// ✅ this.setState({...this.state, count: this.state.count + 1})"Message history grows unbounded (AIChatAgent)"
Cause: this.messages in AIChatAgent accumulates all messages indefinitely Solution: Manually trim old messages periodically:
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
// Keep only last 50 messages
if (this.messages.length > 50) {
this.messages = this.messages.slice(-50);
}
return this.streamText({ model: openai("gpt-4"), messages: this.messages, onFinish });
}
}"SQL injection vulnerability"
Cause: Direct string interpolation in SQL queries Solution: Use parameterized queries:
// ❌ this.sql`...WHERE id = '${userId}'`
// ✅ this.sql`...WHERE id = ${userId}`"WebSocket connection timeout"
Cause: Not calling conn.accept() in onConnect Solution: Always accept connections:
async onConnect(conn: Connection, ctx: ConnectionContext) { conn.accept(); conn.setState({userId: "123"}); }"Schedule limit exceeded"
Cause: More than 1000 scheduled tasks per agent Solution: Clean up old schedules and limit creation rate:
async checkSchedules() { if ((await this.getSchedules()).length > 800) console.warn("Near limit!"); }"AI Gateway unavailable"
Cause: AI service timeout or quota exceeded Solution: Add error handling and fallbacks:
try {
return await this.env.AI.run(model, {prompt});
} catch (e) {
console.error("AI error:", e);
return {error: "Unavailable"};
}"@callable method returns undefined"
Cause: Method doesn't return JSON-serializable value, or has non-serializable types Solution: Ensure return values are plain objects/arrays/primitives:
// ❌ Returns class instance
@callable()
async getData() { return new Date(); }
// ✅ Returns serializable object
@callable()
async getData() { return { timestamp: Date.now() }; }"Resumable stream not resuming"
Cause: Stream ID must be deterministic for resumption to work Solution: Use AIChatAgent (automatic) or ensure consistent stream IDs:
// AIChatAgent handles this automatically
export class ChatAgent extends AIChatAgent<Env> {
// Resumption works out of the box
}"MCP connection loss on hibernation"
Cause: MCP server connections don't survive hibernation Solution: Re-register servers in onStart() or check connection status:
onStart() {
// Re-register MCP servers after hibernation
await this.mcp.registerServer("github", { url: env.MCP_URL, auth: {...} });
}"Agent not found"
Cause: Durable Object binding missing or incorrect class name Solution: Verify DO binding in wrangler.jsonc and class name matches
Rate Limits & Quotas
| Resource/Limit | Value | Notes |
|---|---|---|
| CPU per request | 30s (std), 300s (max) | Set in wrangler.jsonc |
| Memory per instance | 128MB | Shared with WebSockets |
| Storage per agent | 10GB | SQLite storage |
| Scheduled tasks | 1000 per agent | Monitor with getSchedules() |
| WebSocket connections | Unlimited | Within memory limits |
| SQL columns | 100 | Per table |
| SQL row size | 2MB | Key + value |
| WebSocket message | 32MiB | Max size |
| DO requests/sec | ~1000 | Per unique DO instance; rate limit if needed |
| AI Gateway (Workers AI) | Model-specific | Check dashboard for limits |
| MCP requests | Depends on server | Implement retry/backoff |
Best Practices
State Management
- Use immutable updates:
setState({...this.state, key: newValue}) - Trim unbounded arrays (messages, logs) periodically
- Store large data in SQL, not state
SQL Usage
- Create tables in
onStart(), notonRequest() - Use parameterized queries: `
sqlWHERE id = ${id}(NOTsqlWHERE id = '${id}'`) - Index frequently queried columns
Scheduling
- Monitor schedule count:
await this.getSchedules() - Cancel completed tasks to stay under 1000 limit
- Use cron strings for recurring tasks
WebSockets
- Always call
conn.accept()inonConnect() - Handle client disconnects gracefully
- Broadcast to
this.connectionsefficiently
AI Integration
- Use
AIChatAgentfor chat interfaces (auto-streaming, resumption) - Trim message history to avoid token limits
- Handle AI errors with try/catch and fallbacks
Production Deployment
- Rate limiting: Implement request throttling for high-traffic agents (>1000 req/s)
- Monitoring: Log critical errors, track schedule count, monitor storage usage
- Graceful degradation: Handle AI service outages with fallbacks
- Message trimming: Enforce max history length (e.g., 100 messages) in AIChatAgent
- MCP reliability: Re-register servers on hibernation, implement retry logic
Patterns & Use Cases
AI Chat w/Tools
Server (AIChatAgent):
import { AIChatAgent } from "@cloudflare/ai-chat";
import { openai } from "@ai-sdk/openai";
import { tool } from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
return this.streamText({
model: openai("gpt-4"),
messages: this.messages, // Auto-managed
tools: {
getWeather: tool({
description: "Get current weather",
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => `Weather in ${city}: Sunny, 72°F`
}),
searchDocs: tool({
description: "Search documentation",
parameters: z.object({ query: z.string() }),
execute: async ({ query }) => JSON.stringify(
this.sql<{title, content}>`SELECT title, content FROM docs WHERE content LIKE ${'%' + query + '%'}`
)
})
},
onFinish,
});
}
}Client (React):
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
function ChatUI() {
const agent = useAgent({ agent: "ChatAgent" });
const { messages, input, handleInputChange, handleSubmit, isLoading } = useAgentChat({ agent });
return (
<div>
{messages.map(m => <div key={m.id}>{m.role}: {m.content}</div>)}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} disabled={isLoading} />
<button disabled={isLoading}>Send</button>
</form>
</div>
);
}Human-in-the-Loop (Client Tools)
Server defines tool, client executes:
// Server
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
return this.streamText({
model: openai("gpt-4"),
messages: this.messages,
tools: {
confirmAction: tool({
description: "Ask user to confirm",
parameters: z.object({ action: z.string() }),
execute: "client", // Client-side execution
})
},
onFinish,
});
}
}
// Client
const { messages } = useAgentChat({
agent,
onToolCall: async (toolCall) => {
if (toolCall.toolName === "confirmAction") {
return { confirmed: window.confirm(`Confirm: ${toolCall.args.action}?`) };
}
}
});Task Queue & Scheduled Processing
export class TaskAgent extends Agent<Env> {
onStart() {
this.schedule("*/5 * * * *", "processQueue", {}); // Every 5 min
this.schedule("0 0 * * *", "dailyCleanup", {}); // Daily
}
async onRequest(req: Request) {
await this.queue("processVideo", { videoId: (await req.json()).videoId });
return Response.json({ queued: true });
}
async processQueue() {
const tasks = await this.dequeue(10);
for (const task of tasks) {
if (task.name === "processVideo") await this.processVideo(task.data.videoId);
}
}
async dailyCleanup() {
this.sql`DELETE FROM logs WHERE created_at < ${Date.now() - 86400000}`;
}
}Manual WebSocket Chat
Custom protocols (non-AI):
export class ChatAgent extends Agent<Env> {
async onConnect(conn: Connection, ctx: ConnectionContext) {
conn.accept();
conn.setState({userId: ctx.request.headers.get("X-User-ID") || "anon"});
conn.send(JSON.stringify({type: "history", messages: this.state.messages}));
}
async onMessage(conn: Connection, msg: WSMessage) {
const newMsg = {userId: conn.state.userId, text: JSON.parse(msg as string).text, timestamp: Date.now()};
this.setState({messages: [...this.state.messages, newMsg]});
this.connections.forEach(c => c.send(JSON.stringify(newMsg)));
}
}Email Processing w/AI
export class EmailAgent extends Agent<Env> {
async onEmail(email: AgentEmail) {
const [text, from, subject] = [await email.text(), email.from, email.headers.get("subject") || ""];
this.sql`INSERT INTO emails (from_addr, subject, body) VALUES (${from}, ${subject}, ${text})`;
const { text: summary } = await generateText({
model: openai("gpt-4o-mini"), prompt: `Summarize: ${subject}\n\n${text}`
});
this.connections.forEach(c => c.send(JSON.stringify({type: "new_email", from, summary})));
if (summary.includes("urgent")) await this.schedule(0, "sendAutoReply", { to: from });
}
}Real-time Collaboration
export class GameAgent extends Agent<Env> {
initialState = { players: [], gameStarted: false };
async onConnect(conn: Connection, ctx: ConnectionContext) {
conn.accept();
const playerId = ctx.request.headers.get("X-Player-ID") || crypto.randomUUID();
conn.setState({ playerId });
const newPlayer = { id: playerId, score: 0 };
this.setState({...this.state, players: [...this.state.players, newPlayer]});
this.connections.forEach(c => c.send(JSON.stringify({type: "player_joined", player: newPlayer})));
}
async onMessage(conn: Connection, msg: WSMessage) {
const m = JSON.parse(msg as string);
if (m.type === "move") {
this.setState({
...this.state,
players: this.state.players.map(p => p.id === conn.state.playerId ? {...p, score: p.score + m.points} : p)
});
this.connections.forEach(c => c.send(JSON.stringify({type: "player_moved", playerId: conn.state.playerId})));
}
if (m.type === "start" && this.state.players.length >= 2) {
this.setState({...this.state, gameStarted: true});
this.connections.forEach(c => c.send(JSON.stringify({type: "game_started"})));
}
}
}Cloudflare Agents SDK
Cloudflare Agents SDK enables building AI-powered agents on Durable Objects with state, WebSockets, SQL, scheduling, and AI integration.
Core Value
Build stateful, globally distributed AI agents with persistent memory, real-time connections, scheduled tasks, and async workflows.
When to Use
- Persistent state + memory required
- Real-time WebSocket connections
- Long-running workflows (minutes/hours)
- Chat interfaces with AI models
- Scheduled/recurring tasks with state
- DB queries with agent state
What Type of Agent?
| Use Case | Class | Key Features |
|---|---|---|
| AI chat interface | AIChatAgent | Auto-streaming, tools, message history, resumable |
| MCP tool provider | Agent + MCP | Expose tools to AI systems |
| Custom logic/routing | Agent | Full control, WebSockets, email, SQL |
| Real-time collaboration | Agent | WebSocket state, broadcasts |
| Email processing | Agent | onEmail() handler |
Quick Start
AI Chat Agent:
import { AIChatAgent } from "@cloudflare/ai-chat";
import { openai } from "@ai-sdk/openai";
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
return this.streamText({
model: openai("gpt-4"),
messages: this.messages,
onFinish,
});
}
}Base Agent:
import { Agent } from "agents";
export class MyAgent extends Agent<Env> {
onStart() {
this.sql`CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY)`;
}
async onRequest(request: Request) {
return Response.json({ state: this.state });
}
}Reading Order
| Task | Files to Read |
|---|---|
| Quick start | README only |
| Build chat agent | README → api.md (AIChatAgent) → patterns.md |
| Setup project | README → configuration.md |
| Add React frontend | README → api.md (Client Hooks) → patterns.md |
| Build MCP server | api.md (MCP) → patterns.md |
| Background tasks | api.md (Scheduling, Task Queue) → patterns.md |
| Debug issues | gotchas.md |
Package Entry Points
| Import | Purpose |
|---|---|
agents | Server-side Agent classes, lifecycle |
agents/react | useAgent() hook for WebSocket connections |
agents/ai-react | useAgentChat() hook for AI chat UIs |
In This Reference
- configuration.md - SDK setup, wrangler config, routing
- api.md - Agent classes, lifecycle, client hooks
- patterns.md - Common workflows, best practices
- gotchas.md - Common issues, limits
See Also
- durable-objects - Agent infrastructure
- d1 - External database integration
- workers-ai - AI model integration
- vectorize - Vector search for RAG patterns
Configuration & Setup
Creating a Gateway
Dashboard
AI > AI Gateway > Create Gateway > Configure (auth, caching, rate limiting, logging)
API
curl -X POST https://api.cloudflare.com/client/v4/accounts/{account_id}/ai-gateway/gateways \
-H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
-d '{"id":"my-gateway","cache_ttl":3600,"rate_limiting_interval":60,"rate_limiting_limit":100,"collect_logs":true}'Naming: lowercase alphanumeric + hyphens (e.g., prod-api, dev-chat)
Wrangler Integration
[ai]
binding = "AI"
[[ai.gateway]]
id = "my-gateway"wrangler secret put CF_API_TOKEN
wrangler secret put OPENAI_API_KEY # If not using BYOKAuthentication
Gateway Auth (protects gateway access)
const client = new OpenAI({
baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/openai`,
defaultHeaders: { 'cf-aig-authorization': `Bearer ${cfToken}` }
});Provider Auth Options
1. Unified Billing (keyless) - pay through Cloudflare, no provider key:
const client = new OpenAI({
baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/openai`,
defaultHeaders: { 'cf-aig-authorization': `Bearer ${cfToken}` }
});Supports: OpenAI, Anthropic, Google AI Studio
2. BYOK - store keys in dashboard (Provider Keys > Add), no key in code
3. Request Headers - pass provider key per request:
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/openai`,
defaultHeaders: { 'cf-aig-authorization': `Bearer ${cfToken}` }
});API Token Permissions
- Gateway management: AI Gateway - Read + Edit
- Gateway access: AI Gateway - Read (minimum)
Gateway Management API
# List
curl https://api.cloudflare.com/client/v4/accounts/{account_id}/ai-gateway/gateways \
-H "Authorization: Bearer $CF_API_TOKEN"
# Get
curl .../gateways/{gateway_id}
# Update
curl -X PUT .../gateways/{gateway_id} \
-d '{"cache_ttl":7200,"rate_limiting_limit":200}'
# Delete
curl -X DELETE .../gateways/{gateway_id}Getting IDs
- Account ID: Dashboard > Overview > Copy
- Gateway ID: AI Gateway > Gateway name column
Python Example
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ.get("OPENAI_API_KEY"),
base_url=f"https://gateway.ai.cloudflare.com/v1/{os.environ['CF_ACCOUNT_ID']}/{os.environ['GATEWAY_ID']}/openai",
default_headers={"cf-aig-authorization": f"Bearer {os.environ['CF_API_TOKEN']}"}
)Best Practices
1. Always authenticate gateways in production 2. Use BYOK or unified billing - secrets out of code 3. Environment-specific gateways - separate dev/staging/prod 4. Set rate limits - prevent runaway costs 5. Enable logging - track usage, debug issues
Dynamic Routing
Configure complex routing in dashboard without code changes. Use route names instead of model names.
Usage
const response = await client.chat.completions.create({
model: 'dynamic/smart-chat', // Route name from dashboard
messages: [{ role: 'user', content: 'Hello!' }]
});Node Types
| Node | Purpose | Use Case |
|---|---|---|
| Conditional | Branch on metadata | Paid vs free users, geo routing |
| Percentage | A/B split traffic | Model testing, gradual rollouts |
| Rate Limit | Enforce quotas | Per-user/team limits |
| Budget Limit | Cost quotas | Per-user spending caps |
| Model | Call provider | Final destination |
Metadata
Pass via header (max 5 entries, flat only):
headers: {
'cf-aig-metadata': JSON.stringify({
userId: 'user-123',
tier: 'pro',
region: 'us-east'
})
}Common Patterns
Multi-model fallback:
Start → GPT-4 → On error: Claude → On error: LlamaTiered access:
Conditional: tier == 'enterprise' → GPT-4 (no limit)
Conditional: tier == 'pro' → Rate Limit 1000/hr → GPT-4o
Conditional: tier == 'free' → Rate Limit 10/hr → GPT-4o-miniGradual rollout:
Percentage: 10% → New model, 90% → Old modelCost-based fallback:
Budget Limit: $100/day per teamId
< 80%: GPT-4
>= 80%: GPT-4o-mini
>= 100%: ErrorVersion Management
- Save changes as new version
- Test with
model: 'dynamic/route@v2' - Roll back by deploying previous version
Monitoring
Dashboard → Gateway → Dynamic Routes:
- Request count per path
- Success/error rates
- Latency/cost by path
Limitations
- Max 5 metadata entries
- Values: string/number/boolean/null only
- No nested objects
- Route names: alphanumeric + hyphens
Features & Capabilities
Caching
Dashboard: Settings → Cache Responses → Enable
// Custom TTL (1 hour)
headers: { 'cf-aig-cache-ttl': '3600' }
// Skip cache
headers: { 'cf-aig-skip-cache': 'true' }
// Custom cache key
headers: { 'cf-aig-cache-key': 'greeting-en' }Limits: TTL 60s - 30 days. Does NOT work with streaming.
Rate Limiting
Dashboard: Settings → Rate-limiting → Enable
- Fixed window: Resets at intervals
- Sliding window: Rolling window (more accurate)
- Returns
429when exceeded
Guardrails
Dashboard: Settings → Guardrails → Enable
Filter prompts/responses for inappropriate content. Actions: Flag (log) or Block (reject).
Data Loss Prevention (DLP)
Dashboard: Settings → DLP → Enable
Detect PII (emails, SSNs, credit cards). Actions: Flag, Block, or Redact.
Billing Modes
| Mode | Description | Setup |
|---|---|---|
| Unified Billing | Pay through Cloudflare, no provider keys | Use cf-aig-authorization header only |
| BYOK | Store provider keys in dashboard | Add keys in Provider Keys section |
| Pass-through | Send provider key with each request | Include provider's auth header |
Zero Data Retention
Dashboard: Settings → Privacy → Zero Data Retention
No prompts/responses stored. Request counts and costs still tracked.
Logging
Dashboard: Settings → Logs → Enable (up to 10M logs)
Each entry: prompt, response, provider, model, tokens, cost, duration, cache status, metadata.
// Skip logging for request
headers: { 'cf-aig-collect-log': 'false' }Export: Use Logpush to S3, GCS, Datadog, Splunk, etc.
Custom Cost Tracking
For models not in Cloudflare's pricing database:
Dashboard: Gateway → Settings → Custom Costs
Or via API: set model, input_cost, output_cost.
Supported Providers (22+)
| Provider | Unified API | Notes |
|---|---|---|
| OpenAI | openai/gpt-4o | Full support |
| Anthropic | anthropic/claude-sonnet-4-5 | Full support |
| Google AI | google-ai-studio/gemini-2.0-flash | Full support |
| Workers AI | workersai/@cf/meta/llama-3 | Native |
| Azure OpenAI | azure-openai/* | Deployment names |
| AWS Bedrock | Provider endpoint only | /bedrock/* |
| Groq | groq/* | Fast inference |
| Mistral, Cohere, Perplexity, xAI, DeepSeek, Cerebras | Full support | - |
Best Practices
1. Enable caching for deterministic prompts 2. Set rate limits to prevent abuse 3. Use guardrails for user-facing AI 4. Enable DLP for sensitive data 5. Use unified billing or BYOK for simpler key management 6. Enable logging for debugging 7. Use zero data retention when privacy required
Cloudflare AI Gateway
Expert guidance for implementing Cloudflare AI Gateway - a universal gateway for AI model providers with analytics, caching, rate limiting, and routing capabilities.
When to Use This Reference
- Setting up AI Gateway for any AI provider (OpenAI, Anthropic, Workers AI, etc.)
- Implementing caching, rate limiting, or request retry/fallback
- Configuring dynamic routing with A/B testing or model fallbacks
- Managing provider API keys securely with BYOK
- Adding security features (guardrails, DLP)
- Setting up observability with logging and custom metadata
- Debugging AI Gateway requests or optimizing configurations
Quick Start
What's your setup?
- Using Vercel AI SDK → Pattern 1 (recommended) - see sdk-integration.md
- Using OpenAI SDK → Pattern 2 - see sdk-integration.md
- Cloudflare Worker + Workers AI → Pattern 3 - see sdk-integration.md
- Direct HTTP (any language) → Pattern 4 - see configuration.md
- Framework (LangChain, etc.) → See sdk-integration.md
Pattern 1: Vercel AI SDK (Recommended)
Most modern pattern using official ai-gateway-provider package with automatic fallbacks.
import { createAiGateway } from 'ai-gateway-provider';
import { createOpenAI } from '@ai-sdk/openai';
import { generateText } from 'ai';
const gateway = createAiGateway({
accountId: process.env.CF_ACCOUNT_ID,
gateway: process.env.CF_GATEWAY_ID,
});
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY
});
// Single model
const { text } = await generateText({
model: gateway(openai('gpt-4o')),
prompt: 'Hello'
});
// Automatic fallback array
const { text } = await generateText({
model: gateway([
openai('gpt-4o'), // Try first
anthropic('claude-sonnet-4-5'), // Fallback
]),
prompt: 'Hello'
});Install: npm install ai-gateway-provider ai @ai-sdk/openai @ai-sdk/anthropic
Pattern 2: OpenAI SDK
Drop-in replacement for OpenAI API with multi-provider support.
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/compat`,
defaultHeaders: {
'cf-aig-authorization': `Bearer ${cfToken}` // For authenticated gateways
}
});
// Switch providers by changing model format: {provider}/{model}
const response = await client.chat.completions.create({
model: 'openai/gpt-4o', // or 'anthropic/claude-sonnet-4-5'
messages: [{ role: 'user', content: 'Hello!' }]
});Pattern 3: Workers AI Binding
For Cloudflare Workers using Workers AI.
export default {
async fetch(request, env, ctx) {
const response = await env.AI.run(
'@cf/meta/llama-3-8b-instruct',
{ messages: [{ role: 'user', content: 'Hello!' }] },
{
gateway: {
id: 'my-gateway',
metadata: { userId: '123', team: 'engineering' }
}
}
);
return Response.json(response);
}
};Headers Quick Reference
| Header | Purpose | Example | Notes |
|---|---|---|---|
cf-aig-authorization | Gateway auth | Bearer {token} | Required for authenticated gateways |
cf-aig-metadata | Tracking | {"userId":"x"} | Max 5 entries, flat structure |
cf-aig-cache-ttl | Cache duration | 3600 | Seconds, min 60, max 2592000 (30 days) |
cf-aig-skip-cache | Bypass cache | true | - |
cf-aig-cache-key | Custom cache key | my-key | Must be unique per response |
cf-aig-collect-log | Skip logging | false | Default: true |
cf-aig-cache-status | Cache hit/miss | Response only | HIT or MISS |
In This Reference
| File | Purpose |
|---|---|
| sdk-integration.md | Vercel AI SDK, OpenAI SDK, Workers binding patterns |
| configuration.md | Dashboard setup, wrangler, API tokens |
| features.md | Caching, rate limits, guardrails, DLP, BYOK, unified billing |
| dynamic-routing.md | Fallbacks, A/B testing, conditional routing |
| troubleshooting.md | Debugging, errors, observability, gotchas |
Reading Order
| Task | Files |
|---|---|
| First-time setup | README + configuration.md |
| SDK integration | README + sdk-integration.md |
| Enable caching | README + features.md |
| Setup fallbacks | README + dynamic-routing.md |
| Debug errors | README + troubleshooting.md |
Architecture
AI Gateway acts as a proxy between your application and AI providers:
Your App → AI Gateway → AI Provider (OpenAI, Anthropic, etc.)
↓
Analytics, Caching, Rate Limiting, LoggingKey URL patterns:
- Unified API (OpenAI-compatible):
https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/compat/chat/completions - Provider-specific:
https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/{provider}/{endpoint} - Dynamic routes: Use route name instead of model:
dynamic/{route-name}
Gateway Types
1. Unauthenticated Gateway: Open access (not recommended for production) 2. Authenticated Gateway: Requires cf-aig-authorization header with Cloudflare API token (recommended)
Provider Authentication Options
1. Unified Billing: Use AI Gateway billing to pay for inference (keyless mode - no provider API key needed) 2. BYOK (Store Keys): Store provider API keys in Cloudflare dashboard 3. Request Headers: Include provider API key in each request
Related Skills
- Workers AI - For
env.AI.run()details - Agents SDK - For stateful AI patterns
- Vectorize - For RAG patterns with embeddings
Resources
AI Gateway SDK Integration
Vercel AI SDK (Recommended)
import { createAiGateway } from 'ai-gateway-provider';
import { createOpenAI } from '@ai-sdk/openai';
import { generateText } from 'ai';
const gateway = createAiGateway({
accountId: process.env.CF_ACCOUNT_ID,
gateway: process.env.CF_GATEWAY_ID,
apiKey: process.env.CF_API_TOKEN // Optional for auth gateways
});
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
// Single model
const { text } = await generateText({
model: gateway(openai('gpt-4o')),
prompt: 'Hello'
});
// Automatic fallback array
const { text } = await generateText({
model: gateway([
openai('gpt-4o'),
anthropic('claude-sonnet-4-5'),
openai('gpt-4o-mini')
]),
prompt: 'Complex task'
});Options
model: gateway(openai('gpt-4o'), {
cacheKey: 'my-key',
cacheTtl: 3600,
metadata: { userId: 'u123', team: 'eng' }, // Max 5 entries
retries: { maxAttempts: 3, backoff: 'exponential' }
})OpenAI SDK
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/openai`,
defaultHeaders: { 'cf-aig-authorization': `Bearer ${cfToken}` }
});
// Unified API - switch providers via model name
model: 'openai/gpt-4o' // or 'anthropic/claude-sonnet-4-5'Anthropic SDK
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/anthropic`,
defaultHeaders: { 'cf-aig-authorization': `Bearer ${cfToken}` }
});Workers AI Binding
# wrangler.toml
[ai]
binding = "AI"
[[ai.gateway]]
id = "my-gateway"await env.AI.run('@cf/meta/llama-3-8b-instruct',
{ messages: [...] },
{ gateway: { id: 'my-gateway', metadata: { userId: '123' } } }
);LangChain / LlamaIndex
// Use OpenAI SDK pattern with custom baseURL
new ChatOpenAI({
configuration: {
baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/openai`
}
});HTTP / cURL
curl https://gateway.ai.cloudflare.com/v1/{account}/{gateway}/openai/chat/completions \
-H "Authorization: Bearer $OPENAI_KEY" \
-H "cf-aig-authorization: Bearer $CF_TOKEN" \
-H "cf-aig-metadata: {\"userId\":\"123\"}" \
-d '{"model":"gpt-4o","messages":[...]}'Headers Reference
| Header | Purpose |
|---|---|
cf-aig-authorization | Gateway auth token |
cf-aig-metadata | JSON object (max 5 keys) |
cf-aig-cache-ttl | Cache TTL in seconds |
cf-aig-skip-cache | true to bypass cache |
AI Gateway Troubleshooting
Common Errors
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing cf-aig-authorization header | Add header with CF API token |
| 403 | Invalid provider key / BYOK expired | Check provider key in dashboard |
| 429 | Rate limit exceeded | Increase limit or implement backoff |
401 Fix
const client = new OpenAI({
baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/openai`,
defaultHeaders: { 'cf-aig-authorization': `Bearer ${CF_API_TOKEN}` }
});429 Retry Pattern
async function requestWithRetry(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try { return await fn(); }
catch (e) {
if (e.status === 429 && i < maxRetries - 1) {
await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
continue;
}
throw e;
}
}
}Gotchas
| Issue | Reality |
|---|---|
| Metadata limits | Max 5 entries, flat only (no nesting) |
| Cache key collision | Use unique keys per expected response |
| BYOK + Unified Billing | Mutually exclusive |
| Rate limit scope | Per-gateway, not per-user (use dynamic routing for per-user) |
| Log delay | 30-60 seconds normal |
| Streaming + caching | Incompatible |
| Model name (unified API) | Prefix required: openai/gpt-4o, not gpt-4o |
Cache Not Working
Causes:
- Different request params (temperature, etc.)
- Streaming enabled
- Caching disabled in settings
Check: response.headers.get('cf-aig-cache-status') → HIT or MISS
Logs Not Appearing
1. Check logging enabled: Dashboard → Gateway → Settings 2. Remove cf-aig-collect-log: false header 3. Wait 30-60 seconds 4. Check log limit (10M default)
Debugging
# Test connectivity
curl -v https://gateway.ai.cloudflare.com/v1/{account}/{gateway}/openai/models \
-H "Authorization: Bearer $OPENAI_KEY" \
-H "cf-aig-authorization: Bearer $CF_TOKEN"// Check response headers
console.log('Cache:', response.headers.get('cf-aig-cache-status'));
console.log('Request ID:', response.headers.get('cf-ray'));Analytics
Dashboard → AI Gateway → Select gateway
Metrics: Requests, tokens, latency (p50/p95/p99), cache hit rate, costs
Log filters: status: error, provider: openai, cost > 0.01, duration > 1000
Export: Logpush to S3/GCS/Datadog/Splunk
AI Search API Reference
Workers Binding
const answer = await env.AI.autorag("instance-name").aiSearch(options);
const results = await env.AI.autorag("instance-name").search(options);
const instances = await env.AI.autorag("_").listInstances();aiSearch() Options
interface AiSearchOptions {
query: string; // User query
model: string; // Workers AI model ID
system_prompt?: string; // LLM instructions
rewrite_query?: boolean; // Fix typos (default: false)
max_num_results?: number; // Max chunks (default: 10)
ranking_options?: { score_threshold?: number }; // 0.0-1.0 (default: 0.3)
reranking?: { enabled: boolean; model: string };
stream?: boolean; // Stream response (default: false)
filters?: Filter; // Metadata filters
page?: string; // Pagination token
}Response
interface AiSearchResponse {
search_query: string; // Query used (rewritten if enabled)
response: string; // AI-generated answer
data: SearchResult[]; // Retrieved chunks
has_more: boolean;
next_page?: string;
}
interface SearchResult {
id: string;
score: number;
content: string;
metadata: { filename: string; folder: string; timestamp: number };
}Filters
// Comparison
{ column: "folder", operator: "gte", value: "docs/" }
// Compound
{ operator: "and", filters: [
{ column: "folder", operator: "gte", value: "docs/" },
{ column: "timestamp", operator: "gte", value: 1704067200 }
]}Operators: eq, ne, gt, gte, lt, lte
Built-in metadata: filename, folder, timestamp (Unix seconds)
Streaming
const stream = await env.AI.autorag("docs").aiSearch({ query, model, stream: true });
return new Response(stream, { headers: { "Content-Type": "text/event-stream" } });Error Types
| Error | Cause |
|---|---|
AutoRAGNotFoundError | Instance doesn't exist |
AutoRAGUnauthorizedError | Invalid/missing token |
AutoRAGValidationError | Invalid parameters |
REST API
curl https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/autorag/rags/{NAME}/ai-search \
-H "Authorization: Bearer {TOKEN}" \
-d '{"query": "...", "model": "@cf/meta/llama-3.3-70b-instruct-fp8-fast"}'Requires Service API token with "AI Search - Read" permission.
AI Search Configuration
Worker Setup
// wrangler.jsonc
{
"ai": { "binding": "AI" }
}interface Env {
AI: Ai;
}
const answer = await env.AI.autorag("my-instance").aiSearch({
query: "How do I configure caching?",
model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast"
});Data Sources
R2 Bucket
Dashboard: AI Search → Create Instance → Select R2 bucket
Supported formats: .md, .txt, .html, .pdf, .doc, .docx, .csv, .json
Auto-indexed metadata: filename, folder, timestamp
Website Crawler
Requirements:
- Domain on Cloudflare
sitemap.xmlat root- Bot protection must allow
CloudflareAISearchuser agent
Path Filtering (R2)
docs/**/*.md # All .md in docs/ recursively
**/*.draft.md # Exclude (use in exclude patterns)Indexing
- Automatic: Every 6 hours
- Force Sync: Dashboard button (30s rate limit between syncs)
- Pause: Settings → Pause Indexing (existing index remains searchable)
Service API Token
Dashboard: AI Search → Instance → Use AI Search → API → Create Token
Permissions:
- Read - search operations
- Edit - instance management
Store securely:
wrangler secret put AI_SEARCH_TOKENMulti-Environment
# wrangler.toml
[env.production.vars]
AI_SEARCH_INSTANCE = "prod-docs"
[env.staging.vars]
AI_SEARCH_INSTANCE = "staging-docs"const answer = await env.AI.autorag(env.AI_SEARCH_INSTANCE).aiSearch({ query });Monitoring
const instances = await env.AI.autorag("_").listInstances();
console.log(instances.find(i => i.name === "docs"));Dashboard shows: files indexed, status, last index time, storage usage.
AI Search Gotchas
Type Safety
Timestamp precision: Use seconds (10-digit), not milliseconds.
const nowInSeconds = Math.floor(Date.now() / 1000); // CorrectFolder prefix matching: Use gte for "starts with" on paths.
filters: { column: "folder", operator: "gte", value: "docs/api/" } // Matches nestedFilter Limitations
| Limit | Value |
|---|---|
| Max nesting depth | 2 levels |
| Filters per compound | 10 |
or operator | Same column, eq only |
OR restriction example:
// ✅ Valid: same column, eq only
{ operator: "or", filters: [
{ column: "folder", operator: "eq", value: "docs/" },
{ column: "folder", operator: "eq", value: "guides/" }
]}Indexing Issues
| Problem | Cause | Solution |
|---|---|---|
| File not indexed | Unsupported format or >4MB | Check format (.md/.txt/.html/.pdf/.doc/.csv/.json) |
| Index out of sync | 6-hour index cycle | Wait or use "Force Sync" (30s rate limit) |
| Empty results | Index incomplete | Check dashboard for indexing status |
Auth Errors
| Error | Cause | Fix |
|---|---|---|
AutoRAGUnauthorizedError | Invalid/missing token | Create Service API token with AI Search permissions |
AutoRAGNotFoundError | Wrong instance name | Verify exact name from dashboard |
Performance
Slow responses (>3s):
// Add score threshold + limit results
ranking_options: { score_threshold: 0.5 },
max_num_results: 10Empty results debug: 1. Remove filters, test basic query 2. Lower score_threshold to 0.1 3. Check index is populated
Limits
| Resource | Limit |
|---|---|
| Instances per account | 10 |
| Files per instance | 100,000 |
| Max file size | 4 MB |
| Index frequency | 6 hours |
Anti-Patterns
Use env vars for instance names:
const answer = await env.AI.autorag(env.AI_SEARCH_INSTANCE).aiSearch({...});Handle specific error types:
if (error instanceof AutoRAGNotFoundError) { /* 404 */ }
if (error instanceof AutoRAGUnauthorizedError) { /* 401 */ }AI Search Patterns
search() vs aiSearch()
| Use | Method | Returns |
|---|---|---|
| Custom UI, analytics | search() | Raw chunks only (~100-300ms) |
| Chatbots, Q&A | aiSearch() | AI response + chunks (~500-2000ms) |
rewrite_query
| Setting | Use When |
|---|---|
true | User input (typos, vague queries) |
false | LLM-generated queries (already optimized) |
Multitenancy (Folder-Based)
const answer = await env.AI.autorag("saas-docs").aiSearch({
query: "refund policy",
model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
filters: {
column: "folder",
operator: "gte", // "starts with" pattern
value: `tenants/${tenantId}/`
}
});Streaming
const stream = await env.AI.autorag("docs").aiSearch({
query, model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", stream: true
});
return new Response(stream, { headers: { "Content-Type": "text/event-stream" } });Score Threshold
| Threshold | Use |
|---|---|
| 0.3 (default) | Broad recall, exploratory |
| 0.5 | Balanced, production default |
| 0.7 | High precision, critical accuracy |
System Prompt Template
const systemPrompt = `You are a documentation assistant.
- Answer ONLY based on provided context
- If context doesn't contain answer, say "I don't have information"
- Include code examples from context`;Compound Filters
// OR: Multiple folders
filters: {
operator: "or",
filters: [
{ column: "folder", operator: "gte", value: "docs/api/" },
{ column: "folder", operator: "gte", value: "docs/auth/" }
]
}
// AND: Folder + date
filters: {
operator: "and",
filters: [
{ column: "folder", operator: "gte", value: "docs/" },
{ column: "timestamp", operator: "gte", value: oneWeekAgoSeconds }
]
}Reranking
Enable for high-stakes use cases (adds ~300ms latency):
reranking: { enabled: true, model: "@cf/baai/bge-reranker-base" }Cloudflare AI Search Reference
Expert guidance for implementing Cloudflare AI Search (formerly AutoRAG), Cloudflare's managed semantic search and RAG service.
Overview
AI Search is a managed RAG (Retrieval-Augmented Generation) pipeline that combines:
- Automatic semantic indexing of your content
- Vector similarity search
- Built-in LLM generation
Key value propositions:
- Zero vector management - No manual embedding, indexing, or storage
- Auto-indexing - Content automatically re-indexed every 6 hours
- Built-in generation - Optional AI response generation from retrieved context
- Multi-source - Index from R2 buckets or website crawls
Data source options:
- R2 bucket - Index files from Cloudflare R2 (supports MD, TXT, HTML, PDF, DOC, CSV, JSON)
- Website - Crawl and index website content (requires Cloudflare-hosted domain)
Indexing lifecycle:
- Automatic 6-hour refresh cycle
- Manual "Force Sync" available (30s rate limit)
- Not designed for real-time updates
Quick Start
1. Create AI Search instance in dashboard:
- Go to Cloudflare Dashboard → AI Search → Create
- Choose data source (R2 or website)
- Configure instance name and settings
2. Configure Worker:
// wrangler.jsonc
{
"ai": {
"binding": "AI"
}
}3. Use in Worker:
export default {
async fetch(request, env) {
const answer = await env.AI.autorag("my-search-instance").aiSearch({
query: "How do I configure caching?",
model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast"
});
return Response.json({ answer: answer.response });
}
};When to Use AI Search
AI Search vs Vectorize
| Factor | AI Search | Vectorize |
|---|---|---|
| Management | Fully managed | Manual embedding + indexing |
| Use when | Want zero-ops RAG pipeline | Need custom embeddings/control |
| Indexing | Automatic (6hr cycle) | Manual via API |
| Generation | Built-in optional | Bring your own LLM |
| Data sources | R2 or website | Manual insert |
| Best for | Docs, support, enterprise search | Custom ML pipelines, real-time |
AI Search vs Direct Workers AI
| Factor | AI Search | Workers AI (direct) |
|---|---|---|
| Context | Automatic retrieval | Manual context building |
| Use when | Need RAG (search + generate) | Simple generation tasks |
| Indexing | Built-in | Not applicable |
| Best for | Knowledge bases, docs | Simple chat, transformations |
search() vs aiSearch()
| Method | Returns | Use When |
|---|---|---|
search() | Search results only | Building custom UI, need raw chunks |
aiSearch() | AI response + results | Need ready-to-use answer (chatbot, Q&A) |
Real-time Updates Consideration
AI Search is NOT ideal if:
- Need real-time content updates (<6 hours)
- Content changes multiple times per hour
- Strict freshness requirements
AI Search IS ideal if:
- Content relatively stable (docs, policies, knowledge bases)
- 6-hour refresh acceptable
- Prefer zero-ops over real-time
Platform Limits
| Limit | Value |
|---|---|
| Max instances per account | 10 |
| Max files per instance | 100,000 |
| Max file size | 4 MB |
| Index frequency | Every 6 hours |
| Force Sync rate limit | Once per 30 seconds |
| Filter nesting depth | 2 levels |
| Filters per compound | 10 |
| Score threshold range | 0.0 - 1.0 |
Reading Order
Navigate these references based on your task:
| Task | Read | Est. Time |
|---|---|---|
| Understand AI Search | README only | 5 min |
| Implement basic search | README → api.md | 10 min |
| Configure data source | README → configuration.md | 10 min |
| Production patterns | patterns.md | 15 min |
| Debug issues | gotchas.md | 10 min |
| Full implementation | README → api.md → patterns.md | 30 min |
In This Reference
- [api.md](api.md) - API endpoints, methods, TypeScript interfaces
- [configuration.md](configuration.md) - Setup, data sources, wrangler config
- [patterns.md](patterns.md) - Common patterns, decision guidance, code examples
- [gotchas.md](gotchas.md) - Troubleshooting, code-level gotchas, limits
See Also
Analytics Engine API Reference
Writing Data
writeDataPoint()
Fire-and-forget (returns void, not Promise). Writes happen asynchronously.
interface AnalyticsEngineDataPoint {
blobs?: string[]; // Up to 20 strings (dimensions), 16KB each
doubles?: number[]; // Up to 20 numbers (metrics)
indexes?: string[]; // 1 indexed string for high-cardinality filtering
}
env.ANALYTICS.writeDataPoint({
blobs: ["/api/users", "GET", "200"],
doubles: [145.2, 1], // latency_ms, count
indexes: ["customer_abc123"]
});Behaviors: No await needed, no error thrown (check tail logs), auto-sampled at high volumes, auto-timestamped.
Blob vs Index: Blob for GROUP BY (<100k unique), Index for filter-only (millions unique).
Full Example
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const start = Date.now();
const url = new URL(request.url);
try {
const response = await handleRequest(request);
env.ANALYTICS.writeDataPoint({
blobs: [url.pathname, request.method, response.status.toString()],
doubles: [Date.now() - start, 1],
indexes: [request.headers.get("x-api-key") || "anonymous"]
});
return response;
} catch (error) {
env.ANALYTICS.writeDataPoint({
blobs: [url.pathname, request.method, "500"],
doubles: [Date.now() - start, 1, 0],
});
throw error;
}
}
};SQL API (External Only)
curl -X POST https://api.cloudflare.com/client/v4/accounts/{account_id}/analytics_engine/sql \
-H "Authorization: Bearer $TOKEN" \
-d "SELECT blob1 AS endpoint, COUNT(*) AS requests FROM dataset WHERE timestamp >= NOW() - INTERVAL '1' HOUR GROUP BY blob1"Column References
-- blob1..blob20, double1..double20, index1, timestamp
SELECT blob1 AS endpoint, SUM(double1) AS latency, COUNT(*) AS requests
FROM my_dataset
WHERE index1 = 'customer_123' AND timestamp >= NOW() - INTERVAL '7' DAY
GROUP BY blob1
HAVING COUNT(*) > 100
ORDER BY requests DESC LIMIT 100Aggregations: SUM(), AVG(), COUNT(), MIN(), MAX(), quantile(0.95)()
Time ranges: NOW() - INTERVAL '1' HOUR, BETWEEN '2026-01-01' AND '2026-01-31'
Query Examples
-- Top endpoints
SELECT blob1, COUNT(*) AS requests, AVG(double1) AS avg_latency
FROM api_requests WHERE timestamp >= NOW() - INTERVAL '24' HOUR
GROUP BY blob1 ORDER BY requests DESC LIMIT 20
-- Error rate
SELECT blob1, COUNT(*) AS total,
SUM(CASE WHEN blob3 LIKE '5%' THEN 1 ELSE 0 END) AS errors
FROM api_requests WHERE timestamp >= NOW() - INTERVAL '1' HOUR
GROUP BY blob1 HAVING total > 50
-- P95 latency
SELECT blob1, quantile(0.95)(double1) AS p95
FROM api_requests GROUP BY blob1Response Format
{"data": [{"endpoint": "/api/users", "requests": 1523}], "rows": 2}Limits
| Resource | Limit |
|---|---|
| Blobs/Doubles per point | 20 each |
| Indexes per point | 1 |
| Blob/Index size | 16KB |
| Data retention | 90 days |
| Query timeout | 30s |
Critical: High write volumes (>1M/min) trigger automatic sampling.
Analytics Engine Configuration
Setup
1. Add binding to wrangler.jsonc 2. Deploy Worker 3. Dataset created automatically on first write 4. Query via SQL API
wrangler.jsonc
{
"name": "my-worker",
"analytics_engine_datasets": [
{ "binding": "ANALYTICS", "dataset": "my_events" }
]
}Multiple datasets for separate concerns:
{
"analytics_engine_datasets": [
{ "binding": "API_ANALYTICS", "dataset": "api_requests" },
{ "binding": "USER_EVENTS", "dataset": "user_activity" }
]
}TypeScript
interface Env {
ANALYTICS: AnalyticsEngineDataset;
}
export default {
async fetch(request: Request, env: Env) {
// No await - returns void, fire-and-forget
env.ANALYTICS.writeDataPoint({
blobs: [pathname, method, status], // String dimensions (max 20)
doubles: [latency, 1], // Numeric metrics (max 20)
indexes: [apiKey] // High-cardinality filter (max 1)
});
return response;
}
};Data Point Limits
| Field | Limit | SQL Access |
|---|---|---|
| blobs | 20 strings, 16KB each | blob1...blob20 |
| doubles | 20 numbers | double1...double20 |
| indexes | 1 string, 16KB | index1 |
Write Behavior
| Scenario | Behavior |
|---|---|
| <1M writes/min | All accepted |
| >1M writes/min | Automatic sampling |
| Invalid data | Silent failure (check tail logs) |
Mitigate sampling: Pre-aggregate, use multiple datasets, write only critical metrics.
Query Limits
| Resource | Limit |
|---|---|
| Query timeout | 30 seconds |
| Data retention | 90 days (default) |
| Result size | ~10MB |
Cost
Free tier: 10M writes/month, 1M reads/month
Paid: $0.05 per 1M writes, $1.00 per 1M reads
Environment-Specific
{
"analytics_engine_datasets": [
{ "binding": "ANALYTICS", "dataset": "prod_events" }
],
"env": {
"staging": {
"analytics_engine_datasets": [
{ "binding": "ANALYTICS", "dataset": "staging_events" }
]
}
}
}Monitoring
npx wrangler tail # Check for sampling/write errors-- Check write activity
SELECT DATE_TRUNC('hour', timestamp) AS hour, COUNT(*) AS writes
FROM my_dataset
WHERE timestamp >= NOW() - INTERVAL '24' HOUR
GROUP BY hourAnalytics Engine Gotchas
Critical Issues
Sampling at High Volumes
Problem: Queries return fewer points than written at >1M writes/min.
Solution:
// Pre-aggregate before writing
let buffer = { count: 0, total: 0 };
buffer.count++; buffer.total += value;
// Write once per second instead of per request
if (Date.now() % 1000 === 0) {
env.ANALYTICS.writeDataPoint({ doubles: [buffer.count, buffer.total] });
}Detection: npx wrangler tail → look for "sampling enabled"
writeDataPoint Returns void
// ❌ Pointless await
await env.ANALYTICS.writeDataPoint({...});
// ✅ Fire-and-forget
env.ANALYTICS.writeDataPoint({...});Writes can fail silently. Check tail logs.
Index vs Blob
| Cardinality | Use | Example |
|---|---|---|
| Millions | Index | user_id, api_key |
| Hundreds | Blob | endpoint, status_code, country |
// ✅ Correct
{ blobs: [method, path, status], indexes: [userId] }Can't Query from Workers
Query API requires HTTP auth. Use external service or cache in KV/D1.
No Custom Timestamps
Auto-generated at write time. Store original in blob if needed.
Common Errors
| Error | Fix |
|---|---|
| Binding not found | Check wrangler.jsonc, redeploy |
| No data in query | Wait 30s; check dataset name; check time range |
| Query timeout | Add time filter; use index for filtering |
Limits
| Resource | Limit |
|---|---|
| Blobs per point | 20 |
| Doubles per point | 20 |
| Indexes per point | 1 |
| Blob/Index size | 16KB |
| Write rate (no sampling) | ~1M/min |
| Retention | 90 days |
| Query timeout | 30s |
Best Practices
✅ Pre-aggregate at high volumes ✅ Use index for high-cardinality (millions) ✅ Always include time filter in queries ✅ Design schema before coding
❌ Don't await writeDataPoint ❌ Don't use index for low-cardinality ❌ Don't query without time range ❌ Don't assume all writes succeed
Analytics Engine Patterns
Use Cases
| Use Case | Key Metrics | Index On |
|---|---|---|
| API Metering | requests, bytes, compute_units | api_key |
| Feature Usage | feature, action, duration | user_id |
| Error Tracking | error_type, endpoint, count | customer_id |
| Performance | latency_ms, cache_status | endpoint |
| A/B Testing | variant, conversions | user_id |
API Metering (Billing)
env.ANALYTICS.writeDataPoint({
blobs: [pathname, method, status, tier],
doubles: [1, computeUnits, bytes, latencyMs],
indexes: [apiKey]
});
// Query: Monthly usage by customer
// SELECT index1 AS api_key, SUM(double2) AS compute_units
// FROM usage WHERE timestamp >= DATE_TRUNC('month', NOW()) GROUP BY index1Error Tracking
env.ANALYTICS.writeDataPoint({
blobs: [endpoint, method, errorName, errorMessage.slice(0, 1000)],
doubles: [1, timeToErrorMs],
indexes: [customerId]
});Performance Monitoring
env.ANALYTICS.writeDataPoint({
blobs: [pathname, method, cacheStatus, status],
doubles: [latencyMs, 1],
indexes: [userId]
});
// Query: P95 latency by endpoint
// SELECT blob1, quantile(0.95)(double1) AS p95_ms FROM perf GROUP BY blob1Anti-Patterns
| ❌ Wrong | ✅ Correct |
|---|---|
await writeDataPoint() | writeDataPoint() (fire-and-forget) |
indexes: [method] (low cardinality) | blobs: [method], indexes: [userId] |
blobs: [JSON.stringify(obj)] | Store ID in blob, full object in D1/KV |
| Write every request at 10M/min | Pre-aggregate per second |
| Query from Worker | Query from external service/API |
Best Practices
1. Design schema upfront - Document blob/double/index assignments 2. Always include count metric - doubles: [latency, 1] for AVG calculations 3. Use enums for blobs - Consistent values like Status.SUCCESS 4. Handle sampling - Use ratios (avg_latency = SUM(latency)/SUM(count)) 5. Test queries early - Validate schema before heavy writes
Schema Template
/**
* Dataset: my_metrics
*
* Blobs:
* blob1: endpoint, blob2: method, blob3: status
*
* Doubles:
* double1: latency_ms, double2: count (always 1)
*
* Indexes:
* index1: customer_id (high cardinality)
*/Cloudflare Workers Analytics Engine Reference
Expert guidance for implementing unlimited-cardinality analytics at scale using Cloudflare Workers Analytics Engine.
What is Analytics Engine?
Time-series analytics database designed for high-cardinality data (millions of unique dimensions). Write data points from Workers, query via SQL API. Use for:
- Custom user-facing analytics dashboards
- Usage-based billing & metering
- Per-customer/per-feature monitoring
- High-frequency instrumentation without performance impact
Key Capability: Track metrics with unlimited unique values (e.g., millions of user IDs, API keys) without performance degradation.
Core Concepts
| Concept | Description | Example |
|---|---|---|
| Dataset | Logical table for related metrics | api_requests, user_events |
| Data Point | Single measurement with timestamp | One API request's metrics |
| Blobs | String dimensions (max 20) | endpoint, method, status, user_id |
| Doubles | Numeric values (max 20) | latency_ms, request_count, bytes |
| Indexes | Filtered blobs for efficient queries | customer_id, api_key |
Reading Order
| Task | Start Here | Then Read |
|---|---|---|
| First-time setup | configuration.md → api.md → patterns.md | |
| Writing data | api.md → gotchas.md (sampling) | |
| Querying data | api.md (SQL API) → patterns.md (examples) | |
| Debugging | gotchas.md → api.md (limits) | |
| Optimization | patterns.md (anti-patterns) → gotchas.md |
When to Use Analytics Engine
Need to track metrics? → Yes
↓
Millions of unique dimension values? → Yes
↓
Need real-time queries? → Yes
↓
Use Analytics Engine ✓
Alternative scenarios:
- Low cardinality (<10k unique values) → Workers Analytics (free tier)
- Complex joins/relations → D1 Database
- Logs/debugging → Tail Workers (logpush)
- External tools → Send to external analytics (Datadog, etc.)Quick Start
1. Add binding to wrangler.jsonc:
{
"analytics_engine_datasets": [
{ "binding": "ANALYTICS", "dataset": "my_events" }
]
}2. Write data points (fire-and-forget, no await):
env.ANALYTICS.writeDataPoint({
blobs: ["/api/users", "GET", "200"],
doubles: [145.2, 1], // latency_ms, count
indexes: [customerId]
});3. Query via SQL API (HTTP):
SELECT blob1, SUM(double2) AS total_requests
FROM my_events
WHERE index1 = 'customer_123'
AND timestamp >= NOW() - INTERVAL '7' DAY
GROUP BY blob1
ORDER BY total_requests DESCIn This Reference
- [configuration.md](configuration.md) - Setup, bindings, TypeScript types, limits
- [api.md](api.md) -
writeDataPoint(), SQL API, query syntax - [patterns.md](patterns.md) - Use cases, examples, anti-patterns
- [gotchas.md](gotchas.md) - Sampling, index selection, troubleshooting
See Also
- Cloudflare Analytics Engine Docs
- GraphQL Analytics API Reference - Query built-in Cloudflare analytics (HTTP, Workers, DNS, Firewall, etc.)
- Observability Reference - Workers Logs, Traces, and real-time debugging
API Reference
Base: /zones/{zone_id}/api_gateway
Endpoints
GET /operations # List
GET /operations/{op_id} # Get single
POST /operations/item # Create: {endpoint,host,method}
POST /operations # Bulk: {operations:[{endpoint,host,method}]}
DELETE /operations/{op_id} # Delete
DELETE /operations # Bulk delete: {operation_ids:[...]}Discovery
GET /discovery/operations # List discovered
PATCH /discovery/operations/{op_id} # Update: {state:"saved"|"ignored"}
PATCH /discovery/operations # Bulk: {operation_ids:{id:{state}}}
GET /discovery # OpenAPI exportConfig
GET /configuration # Get session ID config
PUT /configuration # Update: {auth_id_characteristics:[{name,type:"header"|"cookie"}]}Token Validation
GET /token_validation # List
POST /token_validation # Create: {name,location:{header:"..."},jwks:"..."}
POST /jwt_validation_rules # Rule: {name,hostname,token_validation_id,action:"block"}Workers Integration
Access JWT Claims
export default {
async fetch(req, env) {
// Access validated JWT payload
const jwt = req.cf?.jwt?.payload?.[env.JWT_CONFIG_ID]?.[0];
if (jwt) {
const userId = jwt.sub;
const role = jwt.role;
}
}
}Access mTLS Info
export default {
async fetch(req, env) {
const tls = req.cf?.tlsClientAuth;
if (tls?.certVerified === 'SUCCESS') {
const fingerprint = tls.certFingerprintSHA256;
// Authenticated client
}
}
}Dynamic JWKS Update
export default {
async scheduled(event, env) {
const jwks = await (await fetch('https://auth.example.com/.well-known/jwks.json')).json();
await fetch(`https://api.cloudflare.com/client/v4/zones/${env.ZONE_ID}/api_gateway/token_validation/${env.CONFIG_ID}`, {
method: 'PATCH',
headers: {'Authorization': `Bearer ${env.CF_API_TOKEN}`, 'Content-Type': 'application/json'},
body: JSON.stringify({jwks: JSON.stringify(jwks)})
});
}
}Firewall Fields
Core Fields
cf.api_gateway.auth_id_present // Session ID present
cf.api_gateway.request_violates_schema // Schema violation
cf.api_gateway.fallthrough_triggered // No endpoint match
cf.tls_client_auth.cert_verified // mTLS cert valid
cf.tls_client_auth.cert_fingerprint_sha256JWT Validation (2026)
// Modern validation syntax
is_jwt_valid(http.request.jwt.payload["{config_id}"][0])
// Legacy (still supported)
cf.api_gateway.jwt_claims_valid
// Extract claims
lookup_json_string(http.request.jwt.payload["{config_id}"][0], "claim_name")Risk Labels (2026)
// BOLA detection
cf.api_gateway.cf-risk-bola-enumeration // Sequential resource access detected
cf.api_gateway.cf-risk-bola-pollution // Parameter pollution detected
// Authentication posture
cf.api_gateway.cf-risk-missing-auth // Endpoint lacks authentication
cf.api_gateway.cf-risk-mixed-auth // Inconsistent auth patternsBOLA Detection
GET /user_schemas/{schema_id}/bola # Get BOLA config
PATCH /user_schemas/{schema_id}/bola # Update: {enabled:true}Auth Posture
GET /discovery/authentication_posture # List unprotected endpointsGraphQL Protection
GET /settings/graphql_protection # Get limits
PUT /settings/graphql_protection # Set: {max_depth,max_size}See Also
- configuration.md - Setup guides for all features
- patterns.md - Firewall rules and common patterns
- API Gateway API Docs
Configuration
Schema Validation 2.0 Setup
⚠️ Classic Schema Validation deprecated. Use Schema Validation 2.0.
Upload schema (Dashboard):
Security > API Shield > Schema Validation > Add validation
- Upload .yml/.yaml/.json (OpenAPI v3.0)
- Endpoints auto-added to Endpoint Management
- Action: Log | Block | None
- Body inspection: JSON payloadsChange validation action:
Security > API Shield > Settings > Schema Validation
Per-endpoint: Filter → ellipses → Change action
Default action: Set global mitigation actionMigration from Classic:
1. Export existing schema (if available)
2. Delete all Classic schema validation rules
3. Wait 5 min for cache clear
4. Re-upload via Schema Validation 2.0 interface
5. Verify in Security > EventsFallthrough rule (catch-all unknown endpoints):
Security > API Shield > Settings > Fallthrough > Use Template
- Select hostnames
- Create rule with cf.api_gateway.fallthrough_triggered
- Action: Log (discover) or Block (strict)Body inspection: Supports application/json, */*, application/*. Disable origin MIME sniffing to prevent bypasses.
JWT Validation
Setup token config:
Security > API Shield > Settings > JWT Settings > Add configuration
- Name: "Auth0 JWT Config"
- Location: Header/Cookie + name (e.g., "Authorization")
- JWKS: Paste public keys from IdPCreate validation rule:
Security > API Shield > API Rules > Add rule
- Hostname: api.example.com
- Deselect endpoints to ignore
- Token config: Select config
- Enforce presence: Ignore or Mark as non-compliant
- Action: Log/Block/ChallengeRate limit by JWT claim:
lookup_json_string(http.request.jwt.claims["{config_id}"][0], "sub")Special cases:
- Two JWTs, different IdPs: Create 2 configs, select both, "Validate all"
- IdP migration: 2 configs + 2 rules, adjust actions per state
- Bearer prefix: API Shield handles with/without
- Nested claims: Dot notation
user.email
Mutual TLS (mTLS)
Setup:
SSL/TLS > Client Certificates > Create Certificate
- Generate CF-managed CA (all plans)
- Upload custom CA (Enterprise, max 5)Configure mTLS rule:
Security > API Shield > mTLS
- Select hostname(s)
- Choose certificate(s)
- Action: Block/Log/ChallengeTest:
openssl req -x509 -newkey rsa:4096 -keyout client-key.pem -out client-cert.pem -days 365
curl https://api.example.com/endpoint --cert client-cert.pem --key client-key.pemSession Identifiers
Critical for BOLA Detection, Sequence Mitigation, and analytics. Configure header/cookie that uniquely IDs API users.
Examples: JWT sub claim, session token, API key, custom user ID header
Configure:
Security > API Shield > Settings > Session Identifiers
- Type: Header/Cookie
- Name: "X-User-ID" or "Authorization"BOLA Detection
Detects Broken Object Level Authorization attacks (enumeration + parameter pollution).
Enable:
Security > API Shield > Schema Validation > [Select Schema] > BOLA Detection
- Enable detection
- Threshold: Sensitivity level (Low/Medium/High)
- Action: Log or BlockRequirements:
- Schema Validation 2.0 enabled
- Session identifiers configured
- Minimum traffic: 1000+ requests/day per endpoint
Authentication Posture
Identifies unprotected or inconsistently protected endpoints.
View report:
Security > API Shield > Authentication Posture
- Shows endpoints lacking JWT/mTLS
- Highlights mixed authentication patternsRemediate: 1. Review flagged endpoints 2. Add JWT validation rules 3. Configure mTLS for sensitive endpoints 4. Monitor posture score
Volumetric Abuse + GraphQL
Volumetric Abuse Detection: Security > API Shield > Settings > Volumetric Abuse Detection
- Enable per-endpoint monitoring, set thresholds, action: Log | Challenge | Block
GraphQL Protection: Security > API Shield > Settings > GraphQL Protection
- Max query depth: 10, max size: 100KB, block introspection (production)
Terraform
# Session identifier
resource "cloudflare_api_shield" "main" {
zone_id = var.zone_id
auth_id_characteristics {
type = "header"
name = "Authorization"
}
}
# Add endpoint
resource "cloudflare_api_shield_operation" "users_get" {
zone_id = var.zone_id
method = "GET"
host = "api.example.com"
endpoint = "/api/users/{id}"
}
# JWT validation rule
resource "cloudflare_ruleset" "jwt_validation" {
zone_id = var.zone_id
name = "API JWT Validation"
kind = "zone"
phase = "http_request_firewall_custom"
rules {
action = "block"
expression = "(http.host eq \"api.example.com\" and not is_jwt_valid(http.request.jwt.payload[\"{config_id}\"][0]))"
description = "Block invalid JWTs"
}
}See Also
- api.md - API endpoints and Workers integration
- patterns.md - Firewall rules and deployment patterns
- gotchas.md - Troubleshooting and limits
Gotchas & Troubleshooting
Common Errors
"Schema Validation 2.0 not working after migration"
Cause: Classic rules still active, conflicting with new system Solution: 1. Delete ALL Classic schema validation rules 2. Clear Cloudflare cache (wait 5 min) 3. Re-upload schema via new Schema Validation 2.0 interface 4. Verify in Security > Events 5. Check action is set (Log/Block)
"Schema validation blocking valid requests"
Cause: Schema too restrictive, missing fields, or incorrect types Solution: 1. Check Firewall Events for violation details 2. Review schema in Settings 3. Test schema in Swagger Editor 4. Use Log mode to validate before blocking 5. Update schema with correct specifications 6. Ensure Schema Validation 2.0 (not Classic)
"JWT validation failing"
Cause: JWKS mismatch with IdP, expired token, wrong header/cookie name, or clock skew Solution: 1. Verify JWKS matches IdP configuration 2. Check token exp claim is valid 3. Confirm header/cookie name matches config 4. Test token at jwt.io 5. Account for clock skew (±5 min tolerance) 6. Use modern syntax: is_jwt_valid(http.request.jwt.payload["{config_id}"][0])
"BOLA detection false positives"
Cause: Legitimate sequential access patterns, bulk operations, or sensitivity too high Solution: 1. Review BOLA events in Security > Events 2. Lower sensitivity threshold (High → Medium → Low) 3. Exclude legitimate bulk operations from detection 4. Ensure session identifiers uniquely identify users 5. Verify minimum traffic requirements met (1000+ req/day)
"Risk labels not appearing in firewall rules"
Cause: Feature not enabled, insufficient traffic, or missing session identifiers Solution: 1. Verify Schema Validation 2.0 enabled 2. Enable BOLA Detection in schema settings 3. Configure session identifiers (required for BOLA) 4. Wait 24-48h for ML model training 5. Check minimum traffic thresholds met
"Endpoint discovery not finding APIs"
Cause: Insufficient traffic (<500 reqs/10d), non-2xx responses, Worker direct requests, or incorrect session ID config Solution: Ensure 500+ requests in 10 days, 2xx responses from edge (not Workers direct), configure session IDs correctly. ML updates daily.
"Sequence detection false positives"
Cause: Lookback window issues, non-unique session IDs, or model sensitivity Solution: 1. Review lookback settings (10 reqs to managed endpoints, 10min window) 2. Ensure session ID uniqueness per user (not shared tokens) 3. Adjust positive/negative model balance 4. Exclude legitimate workflows from detection
"GraphQL protection blocking valid queries"
Cause: Query depth/size limits too restrictive, complex but legitimate queries Solution: 1. Review blocked query patterns in Security > Events 2. Increase max_depth (default: 10) if needed 3. Increase max_size (default: 100KB) for complex queries 4. Whitelist specific query signatures 5. Use Log mode to tune before blocking
"Token invalid"
Cause: Configuration error, JWKS mismatch, or expired token Solution: Verify config matches IdP, update JWKS, check token expiration
"Schema violation"
Cause: Missing required fields, wrong data types, or spec mismatch Solution: Review schema against actual requests, ensure all required fields present, validate types match spec
"Fallthrough"
Cause: Unknown endpoint or pattern mismatch Solution: Update schema with all endpoints, check path pattern matching
"mTLS failed"
Cause: Certificate untrusted/expired or wrong CA Solution: Verify cert chain, check expiration, confirm correct CA uploaded
Limits (2026)
| Resource/Limit | Value | Notes |
|---|---|---|
| OpenAPI version | v3.0.x only | No external refs, must be valid |
| Schema operations | 10K (Enterprise) | Contact for higher limits |
| JWT validation sources | Headers/cookies only | No query params/body |
| Endpoint discovery | 500+ reqs/10d | Minimum for ML model |
| Path normalization | Automatic | /profile/238 → /profile/{var1} |
| Schema parameters | No content field | No object param validation |
| BOLA detection | 1000+ reqs/day/endpoint | Per-endpoint minimum |
| Session ID uniqueness | Required | BOLA/Sequence need unique IDs |
| GraphQL max depth | 1-50 | Default: 10 |
| GraphQL max size | 1KB-1MB | Default: 100KB |
| JWT claim nesting | 10 levels max | Use dot notation |
| mTLS CA certificates | 5 custom max | CF-managed unlimited |
| Schema upload size | 5MB max | Compressed OpenAPI spec |
| Volumetric abuse baseline | 7 days training | Initial ML period |
| Auth Posture refresh | Daily | Updated nightly |
See Also
- configuration.md - Setup guides to avoid common issues
- patterns.md - Best practices and progressive rollout
- API Shield Docs
Patterns & Use Cases
Protect API with Schema + JWT
# 1. Upload OpenAPI schema
POST /zones/{zone_id}/api_gateway/user_schemas
# 2. Configure JWT validation
POST /zones/{zone_id}/api_gateway/token_validation
{
"name": "Auth0",
"location": {"header": "Authorization"},
"jwks": "{...}"
}
# 3. Create JWT rule
POST /zones/{zone_id}/api_gateway/jwt_validation_rules
# 4. Set schema validation action
PUT /zones/{zone_id}/api_gateway/settings/schema_validation
{"validation_default_mitigation_action": "block"}Progressive Rollout
1. Log mode: Observe false positives
- Schema: Action = Log
- JWT: Action = Log
2. Block subset: Protect critical endpoints
- Change specific endpoint actions to Block
- Monitor firewall events
3. Full enforcement: Block all violations
- Change default action to Block
- Handle fallthrough with custom ruleBOLA Detection
Enumeration Detection
Detects sequential resource access (e.g., /users/1, /users/2, /users/3).
// Block BOLA enumeration attempts
(cf.api_gateway.cf-risk-bola-enumeration and http.host eq "api.example.com")
// Action: Block or ChallengeParameter Pollution
Detects duplicate/excessive parameters in requests.
// Block parameter pollution
(cf.api_gateway.cf-risk-bola-pollution and http.host eq "api.example.com")
// Action: BlockCombined BOLA Protection
// Comprehensive BOLA rule
(cf.api_gateway.cf-risk-bola-enumeration or cf.api_gateway.cf-risk-bola-pollution)
and http.host eq "api.example.com"
// Action: BlockAuthentication Posture
Detect Missing Auth
// Log endpoints lacking authentication
(cf.api_gateway.cf-risk-missing-auth and http.host eq "api.example.com")
// Action: Log (for audit)Detect Mixed Auth
// Alert on inconsistent auth patterns
(cf.api_gateway.cf-risk-mixed-auth and http.host eq "api.example.com")
// Action: Log (review required)Fallthrough Detection (Shadow APIs)
// WAF Custom Rule
(cf.api_gateway.fallthrough_triggered and http.host eq "api.example.com")
// Action: Log (discover unknown) or Block (strict)Rate Limiting by User
// Rate Limiting Rule (modern syntax)
(http.host eq "api.example.com" and
is_jwt_valid(http.request.jwt.payload["{config_id}"][0]))
// Rate: 100 req/60s
// Counting expression: lookup_json_string(http.request.jwt.payload["{config_id}"][0], "sub")Volumetric Abuse Response
// Detect abnormal traffic spikes
(cf.api_gateway.volumetric_abuse_detected and http.host eq "api.example.com")
// Action: Challenge or Rate Limit
// Combined with rate limiting
(cf.api_gateway.volumetric_abuse_detected or
cf.threat_score gt 50) and http.host eq "api.example.com"
// Action: JS ChallengeGraphQL Protection
// Block oversized queries
(http.request.uri.path eq "/graphql" and
cf.api_gateway.graphql_query_size gt 100000)
// Action: Block
// Block deep nested queries
(http.request.uri.path eq "/graphql" and
cf.api_gateway.graphql_query_depth gt 10)
// Action: BlockArchitecture Patterns
Public API: Discovery + Schema Validation 2.0 + JWT + Rate Limiting + Bot Management Partner API: mTLS + Schema Validation + Sequence Mitigation Internal API: Discovery + Schema Learning + Auth Posture
OWASP API Security Top 10 Mapping (2026)
| OWASP Issue | API Shield Solutions |
|---|---|
| API1:2023 Broken Object Level Authorization | BOLA Detection (enumeration + pollution), Sequence mitigation, Schema, JWT, Rate Limiting |
| API2:2023 Broken Authentication | Auth Posture, mTLS, JWT validation, Bot Management |
| API3:2023 Broken Object Property Auth | Schema validation, JWT validation |
| API4:2023 Unrestricted Resource Access | Rate Limiting, Volumetric Abuse Detection, GraphQL Protection, Bot Management |
| API5:2023 Broken Function Level Auth | Schema validation, JWT validation, Auth Posture |
| API6:2023 Unrestricted Business Flows | Sequence mitigation, Bot Management |
| API7:2023 SSRF | Schema validation, WAF managed rules |
| API8:2023 Security Misconfiguration | Schema Validation 2.0, Auth Posture, WAF rules |
| API9:2023 Improper Inventory Management | API Discovery, Schema learning, Auth Posture |
| API10:2023 Unsafe API Consumption | JWT validation, Schema validation, WAF managed |
Monitoring
Security Events: Security > Events → Filter: Action = block, Service = API Shield Firewall Analytics: Analytics > Security → Filter by cf.api_gateway.* fields Logpush fields: APIGatewayAuthIDPresent, APIGatewayRequestViolatesSchema, APIGatewayFallthroughDetected, JWTValidationResult
Availability (2026)
| Feature | Availability | Notes |
|---|---|---|
| mTLS (CF-managed CA) | All plans | Self-service |
| Endpoint Management | All plans | Limited operations |
| Schema Validation 2.0 | All plans | Limited operations |
| API Discovery | Enterprise | 10K+ ops |
| JWT Validation | Enterprise add-on | Full validation |
| BOLA Detection | Enterprise add-on | Requires session IDs |
| Auth Posture | Enterprise add-on | Security audit |
| Volumetric Abuse Detection | Enterprise add-on | Traffic analysis |
| GraphQL Protection | Enterprise add-on | Query limits |
| Sequence Mitigation | Enterprise (beta) | Contact team |
| Full Suite | Enterprise add-on | All features |
Enterprise limits: 10K operations (contact for higher). Preview access available for non-contract evaluation.
See Also
- configuration.md - Setup all features before creating rules
- api.md - Firewall field reference and API endpoints
- gotchas.md - Common issues and limits
Cloudflare API Shield Reference
Expert guidance for API Shield - comprehensive API security suite for discovery, protection, and monitoring.
Reading Order
| Task | Files to Read |
|---|---|
| Initial setup | README → configuration.md |
| Implement JWT validation | configuration.md → api.md |
| Add schema validation | configuration.md → patterns.md |
| Detect API attacks | patterns.md → api.md |
| Debug issues | gotchas.md |
Feature Selection
What protection do you need?
├─ Validate request/response structure → Schema Validation 2.0 (configuration.md)
├─ Verify auth tokens → JWT Validation (configuration.md)
├─ Client certificates → mTLS (configuration.md)
├─ Detect BOLA attacks → BOLA Detection (patterns.md)
├─ Track auth coverage → Auth Posture (patterns.md)
├─ Stop volumetric abuse → Abuse Detection (patterns.md)
└─ Discover shadow APIs → API Discovery (api.md)In This Reference
- [configuration.md](configuration.md) - Setup, session identifiers, rules, token/mTLS configs
- [api.md](api.md) - Endpoint management, discovery, validation APIs, GraphQL operations
- [patterns.md](patterns.md) - Common patterns, progressive rollout, OWASP mappings, workflows
- [gotchas.md](gotchas.md) - Troubleshooting, false positives, performance, best practices
Quick Start
API Shield: Enterprise-grade API security (Discovery, Schema Validation 2.0, JWT, mTLS, BOLA Detection, Auth Posture). Available as Enterprise add-on with preview access.
See Also
API Reference
Client Initialization
TypeScript
import Cloudflare from 'cloudflare';
const client = new Cloudflare({
apiToken: process.env.CLOUDFLARE_API_TOKEN,
});Python
from cloudflare import Cloudflare
client = Cloudflare(api_token=os.environ.get("CLOUDFLARE_API_TOKEN"))
# For async:
from cloudflare import AsyncCloudflare
client = AsyncCloudflare(api_token=os.environ["CLOUDFLARE_API_TOKEN"])Go
import (
"github.com/cloudflare/cloudflare-go/v4"
"github.com/cloudflare/cloudflare-go/v4/option"
)
client := cloudflare.NewClient(
option.WithAPIToken(os.Getenv("CLOUDFLARE_API_TOKEN")),
)Authentication
API Token (Recommended)
Create token: Dashboard → My Profile → API Tokens → Create Token
export CLOUDFLARE_API_TOKEN='your-token-here'
curl "https://api.cloudflare.com/client/v4/zones" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"Token scopes: Always use minimal permissions (zone-specific, time-limited).
API Key (Legacy)
curl "https://api.cloudflare.com/client/v4/zones" \
--header "X-Auth-Email: user@example.com" \
--header "X-Auth-Key: $CLOUDFLARE_API_KEY"Not recommended: Full account access, cannot scope permissions.
Auto-Pagination
All SDKs support automatic pagination for list operations.
// TypeScript: for await...of
for await (const zone of client.zones.list()) {
console.log(zone.id);
}# Python: iterator protocol
for zone in client.zones.list():
print(zone.id)// Go: ListAutoPaging
iter := client.Zones.ListAutoPaging(ctx, cloudflare.ZoneListParams{})
for iter.Next() {
zone := iter.Current()
fmt.Println(zone.ID)
}Error Handling
try {
const zone = await client.zones.get({ zone_id: 'xxx' });
} catch (err) {
if (err instanceof Cloudflare.NotFoundError) {
// 404
} else if (err instanceof Cloudflare.RateLimitError) {
// 429 - SDK auto-retries with backoff
} else if (err instanceof Cloudflare.APIError) {
console.log(err.status, err.message);
}
}Common Error Types:
AuthenticationError(401) - Invalid tokenPermissionDeniedError(403) - Insufficient scopeNotFoundError(404) - Resource not foundRateLimitError(429) - Rate limit exceededInternalServerError(≥500) - Cloudflare error
Zone Management
// List zones
const zones = await client.zones.list({
account: { id: 'account-id' },
status: 'active',
});
// Create zone
const zone = await client.zones.create({
account: { id: 'account-id' },
name: 'example.com',
type: 'full', // or 'partial'
});
// Update zone
await client.zones.edit('zone-id', {
paused: false,
});
// Delete zone
await client.zones.delete('zone-id');// Go: requires cloudflare.F() wrapper
zone, err := client.Zones.New(ctx, cloudflare.ZoneNewParams{
Account: cloudflare.F(cloudflare.ZoneNewParamsAccount{
ID: cloudflare.F("account-id"),
}),
Name: cloudflare.F("example.com"),
Type: cloudflare.F(cloudflare.ZoneNewParamsTypeFull),
})DNS Management
// Create DNS record
await client.dns.records.create({
zone_id: 'zone-id',
type: 'A',
name: 'subdomain.example.com',
content: '192.0.2.1',
ttl: 1, // auto
proxied: true, // Orange cloud
});
// List DNS records (with auto-pagination)
for await (const record of client.dns.records.list({
zone_id: 'zone-id',
type: 'A',
})) {
console.log(record.name, record.content);
}
// Update DNS record
await client.dns.records.update({
zone_id: 'zone-id',
dns_record_id: 'record-id',
type: 'A',
name: 'subdomain.example.com',
content: '203.0.113.1',
proxied: true,
});
// Delete DNS record
await client.dns.records.delete({
zone_id: 'zone-id',
dns_record_id: 'record-id',
});# Python example
client.dns.records.create(
zone_id="zone-id",
type="A",
name="subdomain.example.com",
content="192.0.2.1",
ttl=1,
proxied=True,
)See Also
- configuration.md - SDK configuration, environment variables
- patterns.md - Real-world patterns and workflows
- gotchas.md - Rate limits, troubleshooting
Configuration
Environment Variables
Set Variables
| Platform | Command |
|---|---|
| Linux/macOS | export CLOUDFLARE_API_TOKEN='token' |
| PowerShell | $env:CLOUDFLARE_API_TOKEN = 'token' |
| Windows CMD | set CLOUDFLARE_API_TOKEN=token |
Security: Never commit tokens. Use .env files (gitignored) or secret managers.
.env File Pattern
# .env (add to .gitignore)
CLOUDFLARE_API_TOKEN=your-token-here
CLOUDFLARE_ACCOUNT_ID=your-account-id// TypeScript
import 'dotenv/config';
const client = new Cloudflare({
apiToken: process.env.CLOUDFLARE_API_TOKEN,
});# Python
from dotenv import load_dotenv
load_dotenv()
client = Cloudflare(api_token=os.environ["CLOUDFLARE_API_TOKEN"])SDK Configuration
TypeScript
const client = new Cloudflare({
apiToken: process.env.CLOUDFLARE_API_TOKEN,
timeout: 120000, // 2 min (default 60s), in milliseconds
maxRetries: 5, // default 2
baseURL: 'https://...', // proxy (rare)
});
// Per-request overrides
await client.zones.get(
{ zone_id: 'zone-id' },
{ timeout: 5000, maxRetries: 0 }
);Python
client = Cloudflare(
api_token=os.environ["CLOUDFLARE_API_TOKEN"],
timeout=120, # seconds (default 60)
max_retries=5, # default 2
base_url="https://...", # proxy (rare)
)
# Per-request overrides
client.with_options(timeout=5, max_retries=0).zones.get(zone_id="zone-id")Go
client := cloudflare.NewClient(
option.WithAPIToken(os.Getenv("CLOUDFLARE_API_TOKEN")),
option.WithMaxRetries(5), // default 10 (higher than TS/Python)
option.WithRequestTimeout(2 * time.Minute), // default 60s
option.WithBaseURL("https://..."), // proxy (rare)
)
// Per-request overrides
client.Zones.Get(ctx, "zone-id", option.WithMaxRetries(0))Configuration Options
| Option | TypeScript | Python | Go | Default |
|---|---|---|---|---|
| Timeout | timeout (ms) | timeout (s) | WithRequestTimeout | 60s |
| Retries | maxRetries | max_retries | WithMaxRetries | 2 (Go: 10) |
| Base URL | baseURL | base_url | WithBaseURL | api.cloudflare.com |
Note: Go SDK has higher default retries (10) than TypeScript/Python (2).
Timeout Configuration
When to increase:
- Large zone transfers
- Bulk DNS operations
- Worker script uploads
const client = new Cloudflare({
timeout: 300000, // 5 minutes
});Retry Configuration
When to increase: Rate-limit-heavy workflows, flaky network
When to decrease: Fast-fail requirements, user-facing requests
// Increase retries for batch operations
const client = new Cloudflare({ maxRetries: 10 });
// Disable retries for fast-fail
const fastClient = new Cloudflare({ maxRetries: 0 });Wrangler CLI Integration
# Configure authentication
wrangler login
# Or
export CLOUDFLARE_API_TOKEN='token'
# Common commands that use API
wrangler deploy # Uploads worker via API
wrangler kv:key put # KV operations
wrangler r2 bucket create # R2 operations
wrangler d1 execute # D1 operations
wrangler pages deploy # Pages operations
# Get API configuration
wrangler whoami # Shows authenticated userwrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
account_id = "your-account-id"
# Can also use env vars:
# CLOUDFLARE_ACCOUNT_ID
# CLOUDFLARE_API_TOKENSee Also
- api.md - Client initialization, authentication
- gotchas.md - Rate limits, timeout errors
- Wrangler Reference - CLI tool details
Gotchas & Troubleshooting
Rate Limits & 429 Errors
Actual Limits:
- 1200 requests / 5 minutes per user/token (global)
- 200 requests / second per IP address
- GraphQL: 320 / 5 minutes (cost-based)
SDK Behavior:
- Auto-retry with exponential backoff (default 2 retries, Go: 10)
- Respects
Retry-Afterheader - Throws
RateLimitErrorafter exhausting retries
Solution:
// Increase retries for rate-limit-heavy workflows
const client = new Cloudflare({ maxRetries: 5 });
// Add application-level throttling
import pLimit from 'p-limit';
const limit = pLimit(10); // Max 10 concurrent requestsSDK-Specific Issues
Go: Required Field Wrapper
Problem: Go SDK requires cloudflare.F() wrapper for optional fields.
// ❌ WRONG - Won't compile or send field
client.Zones.New(ctx, cloudflare.ZoneNewParams{
Name: "example.com",
})
// ✅ CORRECT
client.Zones.New(ctx, cloudflare.ZoneNewParams{
Name: cloudflare.F("example.com"),
Account: cloudflare.F(cloudflare.ZoneNewParamsAccount{
ID: cloudflare.F("account-id"),
}),
})Why: Distinguishes between zero value, null, and omitted fields.
Python: Async vs Sync Clients
Problem: Using sync client in async context or vice versa.
# ❌ WRONG - Can't await sync client
from cloudflare import Cloudflare
client = Cloudflare()
await client.zones.list() # TypeError
# ✅ CORRECT - Use AsyncCloudflare
from cloudflare import AsyncCloudflare
client = AsyncCloudflare()
await client.zones.list()Token Permission Errors (403)
Problem: API returns 403 Forbidden despite valid token.
Cause: Token lacks required permissions (scope).
Scopes Required:
| Operation | Required Scope |
|---|---|
| List zones | Zone:Read (zone-level or account-level) |
| Create zone | Zone:Edit (account-level) |
| Edit DNS | DNS:Edit (zone-level) |
| Deploy Worker | Workers Script:Edit (account-level) |
| Read KV | Workers KV Storage:Read |
| Write KV | Workers KV Storage:Edit |
Solution: Re-create token with correct permissions in Dashboard → My Profile → API Tokens.
Pagination Truncation
Problem: Only getting first 20 results (default page size).
Solution: Use auto-pagination iterators.
// ❌ WRONG - Only first page (20 items)
const page = await client.zones.list();
// ✅ CORRECT - All results
const zones = [];
for await (const zone of client.zones.list()) {
zones.push(zone);
}Workers Subrequests
Problem: Rate limit hit faster than expected in Workers.
Cause: Workers subrequests count as separate API calls.
Solution: Use bindings instead of REST API in Workers (see ../bindings/).
// ❌ WRONG - REST API in Workers (counts against rate limit)
const client = new Cloudflare({ apiToken: env.CLOUDFLARE_API_TOKEN });
const zones = await client.zones.list();
// ✅ CORRECT - Use bindings (no rate limit)
// Access via env.MY_BINDINGAuthentication Errors (401)
Problem: "Authentication failed" or "Invalid token"
Causes:
- Token expired
- Token deleted/revoked
- Token not set in environment
- Wrong token format
Solution:
// Verify token is set
if (!process.env.CLOUDFLARE_API_TOKEN) {
throw new Error('CLOUDFLARE_API_TOKEN not set');
}
// Test token
const user = await client.user.tokens.verify();
console.log('Token valid:', user.status);Timeout Errors
Problem: Request times out (default 60s).
Cause: Large operations (bulk DNS, zone transfers).
Solution: Increase timeout or split operations.
// Increase timeout
const client = new Cloudflare({
timeout: 300000, // 5 minutes
});
// Or split operations
const batchSize = 100;
for (let i = 0; i < records.length; i += batchSize) {
const batch = records.slice(i, i + batchSize);
await processBatch(batch);
}Zone Not Found (404)
Problem: Zone ID valid but returns 404.
Causes:
- Zone not in account associated with token
- Zone deleted
- Wrong zone ID format
Solution:
// List all zones to find correct ID
for await (const zone of client.zones.list()) {
console.log(zone.id, zone.name);
}Limits Reference
| Resource/Limit | Value | Notes |
|---|---|---|
| API rate limit | 1200/5min | Per user/token |
| IP rate limit | 200/sec | Per IP |
| GraphQL rate limit | 320/5min | Cost-based |
| Parallel requests (recommended) | < 10 | Avoid overwhelming API |
| Default page size | 20 | Use auto-pagination |
| Max page size | 50 | Some endpoints |
Best Practices
Security:
- Never commit tokens
- Use minimal permissions
- Rotate tokens regularly
- Set token expiration
Performance:
- Batch operations
- Use pagination wisely
- Cache responses
- Handle rate limits
Code Organization:
// Create reusable client instance
export const cfClient = new Cloudflare({
apiToken: process.env.CLOUDFLARE_API_TOKEN,
maxRetries: 5,
});
// Wrap common operations
export async function getZoneDetails(zoneId: string) {
return await cfClient.zones.get({ zone_id: zoneId });
}See Also
- api.md - Error types, authentication
- configuration.md - Timeout/retry configuration
- patterns.md - Error handling patterns
Common Patterns
List All with Auto-Pagination
Problem: API returns paginated results. Default page size is 20.
Solution: Use SDK auto-pagination to iterate all results.
// TypeScript
for await (const zone of client.zones.list()) {
console.log(zone.name);
}# Python
for zone in client.zones.list():
print(zone.name)// Go
iter := client.Zones.ListAutoPaging(ctx, cloudflare.ZoneListParams{})
for iter.Next() {
fmt.Println(iter.Current().Name)
}Error Handling with Retry
Problem: Rate limits (429) and transient errors need retry.
Solution: SDKs auto-retry with exponential backoff. Customize as needed.
// Increase retries for rate-limit-heavy operations
const client = new Cloudflare({ maxRetries: 5 });
try {
const zone = await client.zones.create({ /* ... */ });
} catch (err) {
if (err instanceof Cloudflare.RateLimitError) {
// Already retried 5 times with backoff
const retryAfter = err.headers['retry-after'];
console.log(`Rate limited. Retry after ${retryAfter}s`);
}
}Batch Parallel Operations
Problem: Need to create multiple resources quickly.
Solution: Use Promise.all() for parallel requests (respect rate limits).
// Create multiple DNS records in parallel
const records = ['www', 'api', 'cdn'].map(subdomain =>
client.dns.records.create({
zone_id: 'zone-id',
type: 'A',
name: `${subdomain}.example.com`,
content: '192.0.2.1',
})
);
await Promise.all(records);Controlled concurrency (avoid rate limits):
import pLimit from 'p-limit';
const limit = pLimit(10); // Max 10 concurrent
const subdomains = ['www', 'api', 'cdn', /* many more */];
const records = subdomains.map(subdomain =>
limit(() => client.dns.records.create({
zone_id: 'zone-id',
type: 'A',
name: `${subdomain}.example.com`,
content: '192.0.2.1',
}))
);
await Promise.all(records);Zone CRUD Workflow
// Create
const zone = await client.zones.create({
account: { id: 'account-id' },
name: 'example.com',
type: 'full',
});
// Read
const fetched = await client.zones.get({ zone_id: zone.id });
// Update
await client.zones.edit(zone.id, { paused: false });
// Delete
await client.zones.delete(zone.id);DNS Bulk Update
// Fetch all A records
const records = [];
for await (const record of client.dns.records.list({
zone_id: 'zone-id',
type: 'A',
})) {
records.push(record);
}
// Update all to new IP
await Promise.all(records.map(record =>
client.dns.records.update({
zone_id: 'zone-id',
dns_record_id: record.id,
type: 'A',
name: record.name,
content: '203.0.113.1', // New IP
proxied: record.proxied,
ttl: record.ttl,
})
));Filter and Collect Results
// Find all proxied A records
const proxiedRecords = [];
for await (const record of client.dns.records.list({
zone_id: 'zone-id',
type: 'A',
})) {
if (record.proxied) {
proxiedRecords.push(record);
}
}Error Recovery Pattern
async function createZoneWithRetry(name: string, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await client.zones.create({
account: { id: 'account-id' },
name,
type: 'full',
});
} catch (err) {
if (err instanceof Cloudflare.RateLimitError && attempt < maxAttempts) {
const retryAfter = parseInt(err.headers['retry-after'] || '5');
console.log(`Rate limited, waiting ${retryAfter}s (retry ${attempt}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
} else {
throw err;
}
}
}
}Conditional Update Pattern
// Only update if zone is active
const zone = await client.zones.get({ zone_id: 'zone-id' });
if (zone.status === 'active') {
await client.zones.edit(zone.id, { paused: false });
}Batch with Error Handling
// Process multiple zones, continue on errors
const results = await Promise.allSettled(
zoneIds.map(id => client.zones.get({ zone_id: id }))
);
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`Zone ${i}: ${result.value.name}`);
} else {
console.error(`Zone ${i} failed:`, result.reason.message);
}
});See Also
- api.md - SDK client initialization, basic operations
- gotchas.md - Rate limits, common errors
- configuration.md - SDK configuration options
Cloudflare API Integration
Guide for working with Cloudflare's REST API - authentication, SDK usage, common patterns, and troubleshooting.
Quick Decision Tree
How are you calling the Cloudflare API?
├─ From Workers runtime → Use bindings, not REST API (see ../bindings/)
├─ Server-side (Node/Python/Go) → Official SDK (see api.md)
├─ CLI/scripts → Wrangler or curl (see configuration.md)
├─ Infrastructure-as-code → See ../pulumi/ or ../terraform/
└─ One-off requests → curl examples (see api.md)SDK Selection
| Language | Package | Best For | Default Retries |
|---|---|---|---|
| TypeScript | cloudflare | Node.js, Bun, Next.js, Workers | 2 |
| Python | cloudflare | FastAPI, Django, scripts | 2 |
| Go | cloudflare-go/v4 | CLI tools, microservices | 10 |
All SDKs are Stainless-generated from OpenAPI spec (consistent APIs).
Authentication Methods
| Method | Security | Use Case | Scope |
|---|---|---|---|
| API Token ✓ | Scoped, rotatable | Production | Per-zone or account |
| API Key + Email | Full account access | Legacy only | Everything |
| User Service Key | Limited | Origin CA certs only | Origin CA |
Always use API tokens for new projects.
Rate Limits
| Limit | Value |
|---|---|
| Per user/token | 1200 requests / 5 minutes |
| Per IP | 200 requests / second |
| GraphQL | 320 / 5 minutes (cost-based) |
Reading Order
| Task | Files to Read |
|---|---|
| Initialize SDK client | api.md |
| Configure auth/timeout/retry | configuration.md |
| Find usage patterns | patterns.md |
| Debug errors/rate limits | gotchas.md |
| Product-specific APIs | ../workers/, ../r2/, ../kv/, etc. |
In This Reference
- [api.md](api.md) - SDK client initialization, pagination, error handling, examples
- [configuration.md](configuration.md) - Environment variables, SDK config, Wrangler setup
- [patterns.md](patterns.md) - Real-world patterns, batch operations, workflows
- [gotchas.md](gotchas.md) - Rate limits, SDK-specific issues, troubleshooting
See Also
- Cloudflare API Docs
- Bindings Reference - Workers runtime bindings (preferred over REST API)
- Wrangler Reference - CLI tool for Cloudflare development
- GraphQL Analytics API Reference - Analytics data via GraphQL (separate endpoint from REST API)
API Reference
Note on Smart Shield: Argo Smart Routing is being integrated into Cloudflare's Smart Shield product. API endpoints remain stable; existing integrations continue to work without changes.
Base Endpoint
https://api.cloudflare.com/client/v4Authentication
Use API tokens with Zone:Argo Smart Routing:Edit permissions:
# Headers required
X-Auth-Email: user@example.com
Authorization: Bearer YOUR_API_TOKENGet Argo Smart Routing Status
Endpoint: GET /zones/{zone_id}/argo/smart_routing
Description: Retrieves current Argo Smart Routing enablement status.
cURL Example:
curl -X GET "https://api.cloudflare.com/client/v4/zones/{zone_id}/argo/smart_routing" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json"Response:
{
"result": {
"id": "smart_routing",
"value": "on",
"editable": true,
"modified_on": "2024-01-11T12:00:00Z"
},
"success": true,
"errors": [],
"messages": []
}TypeScript SDK Example:
import Cloudflare from 'cloudflare';
const client = new Cloudflare({
apiToken: process.env.CLOUDFLARE_API_TOKEN
});
const status = await client.argo.smartRouting.get({ zone_id: 'your-zone-id' });
console.log(`Argo status: ${status.value}, editable: ${status.editable}`);Python SDK Example:
from cloudflare import Cloudflare
client = Cloudflare(api_token=os.environ.get('CLOUDFLARE_API_TOKEN'))
status = client.argo.smart_routing.get(zone_id='your-zone-id')
print(f"Argo status: {status.value}, editable: {status.editable}")Update Argo Smart Routing Status
Endpoint: PATCH /zones/{zone_id}/argo/smart_routing
Description: Enable or disable Argo Smart Routing for a zone.
Request Body:
{
"value": "on" // or "off"
}cURL Example:
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/{zone_id}/argo/smart_routing" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": "on"}'TypeScript SDK Example:
const result = await client.argo.smartRouting.edit({
zone_id: 'your-zone-id',
value: 'on',
});
console.log(`Updated: ${result.value} at ${result.modified_on}`);Python SDK Example:
result = client.argo.smart_routing.edit(
zone_id='your-zone-id',
value='on'
)
print(f"Updated: {result.value} at {result.modified_on}")Checking Editability Before Updates
Critical: Always check the editable field before attempting to enable/disable Argo. When editable: false, the zone has restrictions (billing not configured, insufficient permissions, or plan limitations).
Pattern:
async function safelyEnableArgo(client: Cloudflare, zoneId: string): Promise<boolean> {
const status = await client.argo.smartRouting.get({ zone_id: zoneId });
if (!status.editable) {
console.error('Cannot modify Argo: editable=false (check billing/permissions)');
return false;
}
if (status.value === 'on') {
console.log('Argo already enabled');
return true;
}
await client.argo.smartRouting.edit({ zone_id: zoneId, value: 'on' });
console.log('Argo enabled successfully');
return true;
}Python Pattern:
def safely_enable_argo(client: Cloudflare, zone_id: str) -> bool:
status = client.argo.smart_routing.get(zone_id=zone_id)
if not status.editable:
print('Cannot modify Argo: editable=false (check billing/permissions)')
return False
if status.value == 'on':
print('Argo already enabled')
return True
client.argo.smart_routing.edit(zone_id=zone_id, value='on')
print('Argo enabled successfully')
return TrueError Handling
The TypeScript SDK provides typed error classes for robust error handling:
import Cloudflare from 'cloudflare';
import { APIError, APIConnectionError, RateLimitError } from 'cloudflare';
async function enableArgoWithErrorHandling(client: Cloudflare, zoneId: string) {
try {
const result = await client.argo.smartRouting.edit({
zone_id: zoneId,
value: 'on',
});
return result;
} catch (error) {
if (error instanceof RateLimitError) {
console.error('Rate limited. Retry after:', error.response?.headers.get('retry-after'));
// Implement exponential backoff
} else if (error instanceof APIError) {
console.error('API error:', error.status, error.message);
if (error.status === 403) {
console.error('Permission denied - check API token scopes');
} else if (error.status === 400) {
console.error('Bad request - verify zone_id and payload');
}
} else if (error instanceof APIConnectionError) {
console.error('Connection failed:', error.message);
// Retry with exponential backoff
} else {
console.error('Unexpected error:', error);
}
throw error;
}
}Python Error Handling:
from cloudflare import Cloudflare, APIError, RateLimitError
def enable_argo_with_error_handling(client: Cloudflare, zone_id: str):
try:
result = client.argo.smart_routing.edit(zone_id=zone_id, value='on')
return result
except RateLimitError as e:
print(f"Rate limited. Retry after: {e.response.headers.get('retry-after')}")
raise
except APIError as e:
print(f"API error: {e.status} - {e.message}")
if e.status == 403:
print('Permission denied - check API token scopes')
elif e.status == 400:
print('Bad request - verify zone_id and payload')
raise
except Exception as e:
print(f"Unexpected error: {e}")
raiseResponse Schema
All Argo Smart Routing API responses follow this structure:
interface ArgoSmartRoutingResponse {
result: {
id: 'smart_routing';
value: 'on' | 'off';
editable: boolean;
modified_on: string; // ISO 8601 timestamp
};
success: boolean;
errors: Array<{
code: number;
message: string;
}>;
messages: Array<string>;
}Key Response Fields
| Field | Type | Description |
|---|---|---|
value | `"on" \ | "off"` |
editable | boolean | Whether changes are allowed (check before PATCH) |
modified_on | string | ISO timestamp of last modification |
success | boolean | Whether request succeeded |
errors | Array | Error details if success: false |
Configuration Management
Note on Smart Shield Evolution: Argo Smart Routing is being integrated into Smart Shield. Configuration methods below remain valid; Terraform and IaC patterns unchanged.
Infrastructure as Code (Terraform)
# terraform/argo.tf
# Note: Use Cloudflare Terraform provider
resource "cloudflare_argo" "example" {
zone_id = var.zone_id
smart_routing = "on"
tiered_caching = "on"
}
variable "zone_id" {
description = "Cloudflare Zone ID"
type = string
}
output "argo_enabled" {
value = cloudflare_argo.example.smart_routing
description = "Argo Smart Routing status"
}Environment-Based Configuration
// config/argo.ts
interface ArgoEnvironmentConfig {
enabled: boolean;
tieredCache: boolean;
monitoring: {
usageAlerts: boolean;
threshold: number;
};
}
const configs: Record<string, ArgoEnvironmentConfig> = {
production: {
enabled: true,
tieredCache: true,
monitoring: {
usageAlerts: true,
threshold: 1000, // GB
},
},
staging: {
enabled: true,
tieredCache: false,
monitoring: {
usageAlerts: false,
threshold: 100, // GB
},
},
development: {
enabled: false,
tieredCache: false,
monitoring: {
usageAlerts: false,
threshold: 0,
},
},
};
export function getArgoConfig(env: string): ArgoEnvironmentConfig {
return configs[env] || configs.development;
}Pulumi Configuration
// pulumi/argo.ts
import * as cloudflare from '@pulumi/cloudflare';
const zone = new cloudflare.Zone('example-zone', {
zone: 'example.com',
plan: 'enterprise',
});
const argoSettings = new cloudflare.Argo('argo-config', {
zoneId: zone.id,
smartRouting: 'on',
tieredCaching: 'on',
});
export const argoEnabled = argoSettings.smartRouting;
export const zoneId = zone.id;Billing Configuration
Before enabling Argo Smart Routing, ensure billing is configured for the account:
Prerequisites: 1. Valid payment method on file 2. Enterprise or higher plan 3. Zone must have billing enabled
Check Billing Status via Dashboard: 1. Navigate to Account → Billing 2. Verify payment method configured 3. Check zone subscription status
Note: Attempting to enable Argo without billing configured will result in editable: false in API responses.
Environment Variable Setup
Required Environment Variables:
# .env
CLOUDFLARE_API_TOKEN=your_api_token_here
CLOUDFLARE_ZONE_ID=your_zone_id_here
CLOUDFLARE_ACCOUNT_ID=your_account_id_here
# Optional
ARGO_ENABLED=true
ARGO_TIERED_CACHE=trueTypeScript Configuration Loader:
// config/env.ts
import { z } from 'zod';
const envSchema = z.object({
CLOUDFLARE_API_TOKEN: z.string().min(1),
CLOUDFLARE_ZONE_ID: z.string().min(1),
CLOUDFLARE_ACCOUNT_ID: z.string().min(1),
ARGO_ENABLED: z.string().optional().default('false'),
ARGO_TIERED_CACHE: z.string().optional().default('false'),
});
export const env = envSchema.parse(process.env);
export const argoConfig = {
enabled: env.ARGO_ENABLED === 'true',
tieredCache: env.ARGO_TIERED_CACHE === 'true',
};CI/CD Integration
GitHub Actions Example:
# .github/workflows/deploy-argo.yml
name: Deploy Argo Configuration
on:
push:
branches: [main]
paths:
- 'terraform/argo.tf'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Init
run: terraform init
working-directory: ./terraform
- name: Terraform Apply
run: terraform apply -auto-approve
working-directory: ./terraform
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
TF_VAR_zone_id: ${{ secrets.CLOUDFLARE_ZONE_ID }}Enterprise Preview Program
For early access to Argo Smart Routing features and Smart Shield integration:
Eligibility:
- Enterprise plan customers
- Active Cloudflare support contract
- Production traffic >100GB/month
How to Join: 1. Contact Cloudflare account team or support 2. Request Argo/Smart Shield preview access 3. Receive preview zone configuration
Preview Features:
- Enhanced analytics and reporting
- Smart Shield DDoS integration
- Advanced routing policies
- Priority support for routing issues