upstash-ratelimit – Coding skill for AI agents
Add rate limiting to API routes, middleware, and edge functions with @upstash/ratelimit: sliding window, fixed window, and token bucket backed by Upstash Redis.
Install the upstash-ratelimit skill with one command
Pick the tool you use, copy the command and paste it in your terminal. The skill lands in that tool's skills folder and is ready in your next session.
Claude Code
mkdir -p ~/.claude/skills && curl -fsSL https://skill.dn1nguoi.com/z/upstash-ratelimit-3c5c9.tgz | tar xz -C ~/.claude/skills
Call it in Claude Code: /upstash-ratelimit – then describe the task you want done.
Codex
mkdir -p ~/.codex/skills && curl -fsSL https://skill.dn1nguoi.com/z/upstash-ratelimit-3c5c9.tgz | tar xz -C ~/.codex/skills
Call it in Codex: $upstash-ratelimit – then describe the task you want done.
Hermes Agent
mkdir -p ~/.hermes/skills && curl -fsSL https://skill.dn1nguoi.com/z/upstash-ratelimit-3c5c9.tgz | tar xz -C ~/.hermes/skills
Call it in Hermes Agent: /upstash-ratelimit – then describe the task you want done.
DeepSeek
mkdir -p ~/.deepcode/skills && curl -fsSL https://skill.dn1nguoi.com/z/upstash-ratelimit-3c5c9.tgz | tar xz -C ~/.deepcode/skills
Call it in DeepSeek: /upstash-ratelimit – then describe the task you want done.
SKILL.md content
Upstash Ratelimit
Overview
@upstash/ratelimit implements distributed rate limiting on top of Upstash
Redis. Because state lives in Redis, every instance of a serverless function
or edge worker shares the same counters, which an in-memory limiter cannot
do. It ships three algorithms (fixed window, sliding window, token bucket),
per-identifier keys, optional in-memory blocking of already-limited
identifiers, and optional analytics.
When to Use This Skill
- Use when the user needs to limit requests per IP, user, API key, or tenant
across multiple serverless instances or regions.
- Use when protecting login, signup, form, webhook, or LLM endpoints from
abuse and returning 429 Too Many Requests.
- Use when choosing between fixed window, sliding window, and token bucket.
- Do not use for client-side retry/backoff against a third-party API's limits;
see api-rate-limit-handler.
- Do not use for a single long-running process with no shared state; an
in-memory limiter is simpler there.
How It Works
Step 1: Install and configure
npm install @upstash/ratelimit @upstash/redisSet UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN in the environment.
Step 2: Create the limiter once, outside the handler
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
export const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"), // 10 requests per 10 seconds
prefix: "rl:api",
analytics: true,
});Constructing the limiter at module scope lets the built-in ephemeral cache
short-circuit blocked identifiers without a Redis call.
Step 3: Call limit() with a stable identifier
const { success, limit, remaining, reset, pending } = await ratelimit.limit(userId);success is false when the identifier is over its limit. reset is a Unix
timestamp in milliseconds. pending is a promise for background work
(analytics, multi-region sync); await it or pass it to waitUntil on edge
runtimes so the function is not frozen before it completes.
Examples
Example 1: Next.js middleware returning 429
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
import { NextResponse, type NextRequest } from "next/server";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(20, "1 m"),
});
export async function middleware(request: NextRequest) {
// Conservative shared bucket until an authenticated principal or a platform-verified
// client address adapter is integrated. Never trust a caller-supplied forwarding header.
const identifier = "anonymous-global";
const { success, limit, remaining, reset, pending } = await ratelimit.limit(identifier);
await pending; // Or pass to the deployment platform’s verified waitUntil API.
if (!success) {
return new NextResponse("Too Many Requests", {
status: 429,
headers: {
"X-RateLimit-Limit": String(limit),
"X-RateLimit-Remaining": String(remaining),
"X-RateLimit-Reset": String(reset),
"Retry-After": String(Math.max(0, Math.ceil((reset - Date.now()) / 1000))),
},
});
}
return NextResponse.next();
}
export const config = { matcher: "/api/:path*" };The shared anonymous bucket protects against spoofed identifiers but shares quota among callers. For per-user limits, use a server-verified session identity; for per-IP limits, use the deployment platform's trusted address API after verifying proxy overwrite and direct-origin restrictions.
Example 2: Token bucket with per-plan limits
const limiters = {
free: new Ratelimit({
redis: Redis.fromEnv(),
prefix: "rl:free",
limiter: Ratelimit.tokenBucket(5, "10 s", 10), // refill 5 per 10 s, burst 10
}),
pro: new Ratelimit({
redis: Redis.fromEnv(),
prefix: "rl:pro",
limiter: Ratelimit.tokenBucket(50, "10 s", 100),
}),
};
const { success } = await limiters[plan].limit(apiKey);Best Practices
- ✅ Use a stable, low-cardinality identifier (user id, API key, tenant) where
possible; fall back to IP only for anonymous traffic.
- ✅ Set a distinct
prefixper endpoint or plan so limits do not collide. - ✅ Return
Retry-AfterandX-RateLimit-*headers with 429 responses. - ✅ Prefer
slidingWindowfor most APIs; usetokenBucketwhen short bursts
are acceptable; use fixedWindow when the lowest Redis cost matters.
- ❌ Don't construct a new
Ratelimitinside the request handler. - ❌ Don't rely on
pendingcompleting on its own in edge runtimes. - ❌ Don't rate limit by
x-forwarded-forwithout validating it is set by
your proxy; clients can spoof it otherwise.
Limitations
- Requires an Upstash Redis database; it does not work with other Redis
servers or without network access.
- Each
limit()call is at least one HTTP round trip to Redis, so it adds
latency to every request it guards.
- Sliding window is an approximation that assumes an even spread of requests
in the previous window; it is not an exact log.
MultiRegionRatelimittrades strict accuracy for lower latency and does
not support the token bucket algorithm.
- If Redis is unreachable, the default
timeout(5 s) lets requests through
(reason: "timeout"); this fails open, not closed.
- This skill does not replace environment-specific validation, testing, or
expert review.
Security & Safety Notes
Use a server-verified plan and identifier; do not send raw API keys as Redis identifiers. Hash a stable key identifier where appropriate, review analytics disclosure/retention and choose an explicit timeout/error policy for security-sensitive endpoints rather than assuming fail-open is acceptable.
- Rate limiting is one layer of abuse protection, not authentication. Pair it
with auth and input validation.
- The Redis token grants full database access; keep it server-side.
- Changing limits in production can lock out legitimate users. Confirm the
numbers with the user before deploying stricter limits.
Common Pitfalls
- Problem: Every request is allowed even after the limit.
Solution: Each identifier must be the same string across requests;
check that the identifier is not undefined or a fresh random value.
- Problem: Analytics are empty on Vercel Edge or Cloudflare Workers.
Solution: Pass pending to waitUntil (ctx.waitUntil(pending)) so
the background request is not cancelled when the response is sent.
Related Skills
@upstash-redis- The client this package uses for storage.@api-rate-limit-handler- Client-side backoff and retry when you are the
one being rate limited.
@upstash-qstash- Queue and smooth traffic to downstream services instead
of rejecting it.
Additional Resources
- [Upstash Ratelimit documentation](https://upstash.com/docs/redis/sdks/ratelimit-ts/overview)
- [@upstash/ratelimit on GitHub](https://github.com/upstash/ratelimit-js)
- [Algorithms guide](https://upstash.com/docs/redis/sdks/ratelimit-ts/algorithms)
Related skills
- us-watchlist-alerts – Free US stock watchlists, price alerts and daily briefs; no API key, NYSE-holiday aware…
- use-dom – Use Expo DOM components to run web code in a webview on native and as-is on web. Migrate…
- using-git-worktrees – Git worktrees create isolated workspaces sharing the same repository, allowing work on…
- uv-package-manager – Comprehensive guide to using uv, an extremely fast Python package installer and resolver…
- varlock – Secure-by-default environment variable management for Claude Code sessions.
- varlock-claude-skill – Secure environment variable management with explicit checks to reduce secret exposure in…
- vercel-ai-sdk-expert – Expert in the Vercel AI SDK. Covers Core API (generateText, streamText), UI hooks…
- vercel-cli-with-tokens – Deploy and manage projects on Vercel using token-based authentication. Use when working…