Rate Limits on Cloudflare Workers AI
9 min read · updated August 11, 2026
Workers AI enforces two unrelated limits that both surface as HTTP 429, and the correct response to one is the opposite of the correct response to the other. Telling them apart is the whole of this page.
Two limits, not one
There is no single documented account-wide requests-per-minute figure for Workers AI, and if you go looking for one you will waste an afternoon. What Cloudflare documents is:
- A daily allocation, measured in neurons, per account. 10,000 neurons per day free at the time of writing. This is a consumption cap, it resets daily, and it is the account-level limit people are usually asking about.
- Requests-per-minute ceilings, per task type and per model. These are throughput caps. They reset continuously and have nothing to do with how much you have spent.
The distinction matters because they fail differently. You can be well inside your neuron budget and still be rate limited by sending 400 requests in a minute to a model whose ceiling is 300, and you can be sending one request a minute and still be blocked because yesterday evening’s batch job spent the day’s allocation.
The per-model requests-per-minute ceilings
Cloudflare publishes these as a table by task type with per-model overrides. As of August 2026 the documented figures include 300 requests per minute as the default for text generation, 3,000 for text embeddings and for image classification, 1,500 for summarization, 2,000 for text classification, and 720 for automatic speech recognition, translation and image-to-text.
Per-model overrides can go either way from the task default. Small models are given more room — @cf/qwen/qwen1.5-0.5b-chat is documented at 1,500 requests per minute against the text-generation default of 300 — and large ones less, with @cf/qwen/qwen1.5-14b-chat-awq at 150. Frontier models are tighter again and are documented per account rather than per model family: 20 requests per minute on standard billing, rising to 50 with prepaid credits.
Two things follow. First, a model swap is a capacity change: moving from a 1,500 rpm model to a 150 rpm one is a tenfold cut in throughput that no code review will flag. Second, the embedding ceilings are an order of magnitude above the generation ceilings, which is exactly the right shape for retrieval workloads and means the generation step is almost always the one that saturates first.
The two 429s and how to tell them apart
Cloudflare’s Workers AI errors page documents two error codes that return HTTP 429, and they mean opposite things:
- 3036 — Account limited. The documented message begins “You have used up your daily free allocation of 10,000 neurons”. This is the consumption cap. Retrying does not help; the allocation resets tomorrow or you upgrade.
- 3040 — Out of capacity. The documented message is “No more data centers to forward the request to”. This is Cloudflare not having a GPU free for you at that instant. It is transient, and it is the one worth retrying.
The same page documents two 403s that are frequently mistaken for rate limiting because they also appear under load: 5035, “This model requires a Workers Paid plan”, and 3023, “Service unavailable for account”. Neither is retryable and neither resolves on its own.
Do not classify on the status code alone. Read the numeric code out of the error body and branch on that. A retry loop that treats 3036 as transient will hammer a closed door for the rest of the day, and one that treats 3040 as fatal will fail requests that would have succeeded on the next attempt.
Handling each one correctly
type AiError = { code?: number; message?: string };
async function generate(env: Env, messages: unknown[], attempt = 0) {
try {
return await env.AI.run("@cf/meta/llama-3.1-8b-instruct", { messages });
} catch (err) {
const code = (err as AiError)?.code;
// 3040: no capacity right now. Transient — back off and retry.
if (code === 3040 && attempt < 3) {
const waitMs = 250 * 2 ** attempt + Math.floor(Math.random() * 100);
await scheduler.wait(waitMs);
return generate(env, messages, attempt + 1);
}
// 3036: daily neuron allocation spent. Retrying cannot succeed.
if (code === 3036) throw new Error("neuron allocation exhausted");
throw err;
}
}The jitter term is not optional. Every Worker instance that hit 3040 at the same moment will otherwise retry at the same moment, reproducing the burst that caused the failure. Three attempts with exponential backoff is a reasonable ceiling for an interactive request; a background job can afford more, and should push the work onto a queue instead of holding a request open.
For the per-minute ceilings the better answer is not to reach them. Shape the traffic before it leaves your Worker, either with the platform’s own rate-limiting binding or, where you need a per-user counter that is exactly right rather than approximately right, with one Durable Object per user as a strongly-consistent counter.
Buying headroom
Three levers exist and they are not equivalent. Upgrading to Workers Paid removes the practical effect of the daily neuron cap by making overage billable at the published rate. Prepaid credits raise the frontier-model per-account ceiling from the documented 20 requests per minute to 50. Choosing a smaller model raises the per-minute ceiling and lowers the neuron cost per request at the same time, which is why it is usually the first thing to try.
None of them changes 3040. Capacity is shared infrastructure and no plan makes a GPU appear; the only structural answer to it is a fallback path to a different model or a different provider, which is a routing decision rather than a limits one.
There is a fourth lever that is not really about limits at all, and it is the one to reach for when a batch job is the thing hitting the ceiling. A per-minute cap is a constraint on concurrency, not on total work, so moving the work onto a queue and consuming it at a rate you choose removes the problem entirely rather than mitigating it. The request that would have been rejected is instead accepted, acknowledged and processed a second later, and the ceiling stops being a failure mode and becomes a throughput figure you plan against.
That distinction is worth making explicitly because it decides where the retry logic belongs. Interactive traffic must fail fast and retry in-request, because a user is waiting and there is nowhere to put the work. Background traffic should almost never retry in-request, because there is somewhere to put the work and holding an invocation open to sleep is the most expensive way to wait.