pragma.vision Your technology observatory

The press · Developer & Security Deep-Dives · filed 2026-06-01 · updated 2026-07-10

Rate Limiting & Abuse Prevention for AI API Platforms

Long-form article — sliding-window KV rate limiting, multi-dimensional limits, brute-force protection, agent-specific abuse detection, and nonce replay defense at the edge.

#rate-limiting #api-security #abuse-prevention #cloudflare-workers #ai-agents

The problem

Your API was being attacked by humans with scripts in 2022. In 2026 it is being attacked by other AI agents. AI-powered bot traffic increased by roughly 300% in a single year. 97% of documented API vulnerabilities can be exploited with a single request. The attackers do not work in shifts and do not type — they rotate IPs, mimic legitimate traffic patterns, and operate at machine speed around the clock.

The rate limiter you wrote in 2022 was designed for human-speed abuse. It counted requests per IP, blocked after a threshold, and that worked. It is now facing machine-speed adversaries on multi-IP residential proxy pools, and it is losing. The most dangerous abuse is not the dramatic DDoS that takes your service offline. It is the low-and-slow scraping agent that stays just under your rate limits for weeks, extracting your product catalog or user data one request at a time. APIs accounted for 17% of all published security bulletins in 2025 — over 11,000 vulnerabilities. Insecure Resource Consumption (the category that includes rate-limiting failures) climbed from seventh place in 2024 to fourth in 2025.

This walks through the sliding-window algorithm that closes the fixed-window boundary exploit, the multi-dimensional limits that throttle per-user, per-IP, and per-agent independently, and the agent-specific abuse patterns that traditional rate limiters cannot detect.

Free sample

See the structure and voice before you buy.

Download the sample (PDF)

What most people get wrong

Mistake one: fixed-window rate limiting. The fixed window divides time into discrete intervals — say, one-minute windows starting at each minute boundary. It counts requests within each window and rejects past the limit. The boundary exploit: a client sends 100 requests at 12:00:59 and another 100 at 12:01:01. The fixed window says both bursts are under the “100 per minute” limit. The client just got 200 requests through in two seconds. Any attacker who has read about rate limiting knows this trick. The sliding window eliminates it by looking backward from the current moment rather than counting within fixed boundaries. A request at 12:01:01 is evaluated against the window from 12:00:01 to 12:01:01 — and the 100 requests sent in the previous second are visible to the counter.

Mistake two: one dimension instead of three. Most rate limiters count per IP only. AI-agent attacks defeat per-IP limits trivially by rotating through residential proxy pools. Per-user limits defeat IP rotation but cannot stop a credential-stuffing wave with one stolen credential per IP. Per-agent limits (keyed off API key or signed agent identity) defeat both but cannot stop a single legitimate agent from scraping faster than intended. The three dimensions are independent defenses against three independent attack shapes. Production rate limiters enforce all three simultaneously, with different thresholds for each — typically 100 requests/minute per user, 5 login attempts per 15 minutes per IP, 10 mandate creations per hour per user.

A third pitfall worth flagging — silent failure modes. If your KV write fails, what does your rate limiter return? “Allow” (fail open) means a transient KV outage is an unrate-limited window where an attacker can dump unlimited requests. “Reject” (fail closed) means a KV outage takes your API offline for legitimate users. The right answer depends on the endpoint: login endpoints fail closed; read-only public endpoints fail open with a metric alert. Make the choice explicit; do not let the unhandled exception path decide for you.

This article is the short version — Rate Limiting & Abuse Prevention for AI API Platforms is the full playbook.

Get the ebook — $14

A working approach

The sliding window counter. Two integers and one timestamp per key — twenty bytes total regardless of request volume. The pure sliding-window log tracks every individual request timestamp; that is precise but memory-hostile (at 100 req/min per user with 10,000 active users, log is 8 MB; counter is 200 KB).

interface SlidingWindowState {
  currentCount: number;
  previousCount: number;
  currentWindowStart: number; // epoch ms
  windowSizeMs: number;
}

function estimateRequestCount(
  state: SlidingWindowState,
  now: number
): number {
  const elapsed = now - state.currentWindowStart;

  if (elapsed >= state.windowSizeMs) {
    return 0; // Window has fully rotated
  }

  // Weight: how much of the previous window
  // is still "visible" in the sliding view
  const weight = 1 - elapsed / state.windowSizeMs;

  return Math.floor(
    state.previousCount * weight + state.currentCount
  );
}

The full Cloudflare Workers KV implementation:

interface RateLimitConfig {
  limit: number;       // max requests in window
  windowMs: number;    // window size in milliseconds
  keyPrefix: string;   // namespace prefix
}

interface RateLimitEntry {
  currentCount: number;
  previousCount: number;
  windowStart: number;
}

export class KVRateLimiter {
  constructor(
    private kv: KVNamespace,
    private config: RateLimitConfig
  ) {}

  async check(identifier: string): Promise<{
    allowed: boolean;
    remaining: number;
    resetAt: number;
  }> {
    const key = `${this.config.keyPrefix}:${identifier}`;
    const now = Date.now();

    const raw = await this.kv.get(key, 'json');
    let entry: RateLimitEntry = raw as RateLimitEntry
      ?? {
        currentCount: 0,
        previousCount: 0,
        windowStart: now,
      };

    const elapsed = now - entry.windowStart;
    if (elapsed >= this.config.windowMs * 2) {
      // Both windows expired; reset
      entry = { currentCount: 0, previousCount: 0, windowStart: now };
    } else if (elapsed >= this.config.windowMs) {
      // Rotate windows
      entry = {
        currentCount: 0,
        previousCount: entry.currentCount,
        windowStart: entry.windowStart + this.config.windowMs,
      };
    }

    const estimated = estimateRequestCount(
      { ...entry, windowSizeMs: this.config.windowMs },
      now
    );

    if (estimated >= this.config.limit) {
      return {
        allowed: false,
        remaining: 0,
        resetAt: entry.windowStart + this.config.windowMs,
      };
    }

    entry.currentCount += 1;
    await this.kv.put(key, JSON.stringify(entry), {
      expirationTtl: Math.ceil(this.config.windowMs * 2 / 1000),
    });

    return {
      allowed: true,
      remaining: this.config.limit - estimated - 1,
      resetAt: entry.windowStart + this.config.windowMs,
    };
  }
}

KV has a constraint that matters: you can only write to a single key once per second. For minute-or-hour windows (the common case), this is fine. For sub-second precision, KV is wrong — use a Durable Object instead, which gives you strong consistency at the single-region cost. The book covers both paths and the trade-off matrix.

Multi-dimensional enforcement. Run three independent rate limiters in parallel — per-user, per-IP, per-agent. The first to reject wins:

async function checkAllLimits(
  request: Request,
  ctx: { userId?: string; ip: string; agentId?: string },
  env: Env
): Promise<RateLimitResult> {
  const checks = await Promise.all([
    ctx.userId && env.USER_LIMITER.check(ctx.userId),
    env.IP_LIMITER.check(ctx.ip),
    ctx.agentId && env.AGENT_LIMITER.check(ctx.agentId),
  ].filter(Boolean));

  const blocked = checks.find(c => !c.allowed);
  if (blocked) return blocked;

  // Return the lowest remaining for response headers
  return checks.reduce((min, c) =>
    c.remaining < min.remaining ? c : min
  );
}

The response carries standard rate-limit headers (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset) per RFC 9239, plus a 429 with a Retry-After when blocked. AI agents that respect the headers back off automatically. Agents that ignore them get classified as abuse and routed to the progressive-penalty path.

This article is the short version — Rate Limiting & Abuse Prevention for AI API Platforms is the full playbook.

Get the ebook — $14

Where this scales

The article above is the spine. The full book covers what survives the first real abuse incident:

  • Brute force and credential stuffing prevention — progressive penalty architecture that escalates from rate-limit to short lockout to long lockout to permanent ban based on attempt patterns; credential-stuffing detection that flags low-success-rate floods across many usernames from one source; account lockout with self-service recovery flows that don’t lock out legitimate users; IP reputation and block-list integration.
  • Agent-specific abuse patterns — intelligent catalog scraping where the agent stays just under your limits, replay attacks via captured tokens at high IP diversity, prompt injection through API endpoints that accept natural language, API-key enumeration, and resource exhaustion through expensive queries (the WHERE LIKE '%search%' that scans an unindexed column). Each pattern has its own detection heuristic and response.
  • Nonce-based replay protection — single-use nonce generation, server-side nonce tracking with time-bounded validity windows, clock-synchronization considerations, and integration with mandate authorization for financial endpoints.
  • The complete production middleware — TypeScript for Cloudflare Workers integrating sliding-window limits, multi-dimensional enforcement, brute-force escalation, agent-pattern detection, and nonce verification into a single pipeline. Wrangler configuration included.
  • Monitoring and operational playbooks — structured telemetry, alert conditions, the incident playbooks (Credential Stuffing Attack, DDoS via API Flood, Nonce Replay Surge), capacity planning, post-incident analysis template, and the continuous calibration process that keeps your limits tuned as traffic patterns drift.

Included with the book

  • rate-limiting-templates.ts — sliding-window KV rate limiter, multi-dimensional middleware (per-user, per-IP, per-agent), and abuse-pattern detection helpers. TypeScript, MIT-licensed, drop into your Worker. Within Cloudflare’s free-tier limits (100K requests/day, 100K KV reads/day) — no infrastructure spend required to ship production-grade rate limiting.

Get the full picture

The full playbook

Rate Limiting & Abuse Prevention for AI API Platforms — everything this article compresses, worked through end to end.

Get the ebook — $14

Readers of this also chose

Questions readers ask

Why Cloudflare KV instead of Redis or Memcached?

KV runs at the edge across 300+ locations with sub-millisecond reads from cache. Redis and Memcached are excellent — but you have to host them, and edge-to-origin latency for every rate-limit check adds 50ms+ that the user feels. KV's eventual consistency is acceptable for rate-limiting horizons measured in minutes or hours. For sub-second precision (which most use cases do not need), Cloudflare Durable Objects give you strong consistency.

What about fixed windows or token buckets — are they ever right?

Token bucket is the right answer for burst-tolerant APIs (e.g., legitimate scientific clients that need 1000 quick requests then nothing for a minute). Sliding window is the right answer for steady-traffic APIs where the boundary exploit matters. Fixed window is almost never right in 2026 — attackers exploit the boundary on contact. The book covers all three with the trade-off matrix and the decision criteria.

How do I rate-limit per-agent if the agent rotates API keys?

Bind the limit to a stable identity higher than the API key — a signed agent DID, a hashed OAuth client_id, or a customer account. API keys are credentials; agents are identities. Limit the identity, not the credential. The chapter on adaptive limits for trusted agents walks through the pattern.

What happens when KV writes fail — does the limiter fail open or closed?

You choose, per endpoint. Login fails closed (better to fail safely than open the credential-stuffing window). Read-only public endpoints fail open with a metric alert (better to serve traffic than block legitimate users on a transient KV outage). Make the choice explicit per endpoint; the chapter on monitoring covers the alerting that flags fail-open events for review.

What's the refund policy?

Lemon Squeezy's standard refund window applies. The refund link is in the receipt email.

Your opinion

Tell us anything.

What works, what doesn't, what's missing — especially about our watches, lenses, and the register itself. Anonymous is fine; leave an email if you'd like a reply.