<< All versions

Skill v1.0.1

currentAutomated scan100/100
sickn33/agentic-awesome-skills/upstash-ratelimit

+4 new

──Details
PublishedOctober 27, 2026 at 05:19 AM
Content Hashsha256:cc10090cd7479e4b...
Git SHAe8d346bcb691
Bump Typepatch
Compare with v1.0.0
──Files
Files (1 file, 7.4 KB)
SKILL.md7.4 KBactive
SKILL.md · 195 lines · 7.4 KB

version: "1.0.1" name: upstash-ratelimit description: "Add rate limiting to API routes, middleware, and edge functions with @upstash/ratelimit: sliding window, fixed window, and token bucket backed by Upstash Redis." category: backend risk: critical source: self source_type: self date_added: "2026-08-31" author: CahidArda tags: [upstash, rate-limiting, redis, serverless, edge, middleware, 429] tools: [claude, codex, cursor, gemini]


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

bash
npm install @upstash/ratelimit @upstash/redis

Set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN in the environment.

Step 2: Create the limiter once, outside the handler

typescript
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

typescript
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

typescript
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

typescript
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 prefix per endpoint or plan so limits do not collide.
  • ✅ Return Retry-After and X-RateLimit-* headers with 429 responses.
  • ✅ Prefer slidingWindow for most APIs; use tokenBucket when short bursts

are acceptable; use fixedWindow when the lowest Redis cost matters.

  • ❌ Don't construct a new Ratelimit inside the request handler.
  • ❌ Don't rely on pending completing on its own in edge runtimes.
  • ❌ Don't rate limit by x-forwarded-for without 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.

  • MultiRegionRatelimit trades 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

← v1.0.0All versions