
Upstash Ratelimit Ts
- 64 installs
- 2k repo stars
- Updated March 9, 2026
- upstash/ratelimit-js
Helps with ai & agent building tasks during AI-assisted development.
About
upstash-ratelimit-ts is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- upstash-ratelimit-ts
- AI & Agent Building
- AI-coding skill
Upstash Ratelimit Ts by the numbers
- 64 all-time installs (skills.sh)
- Ranked #6,008 of 16,556 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/upstash/ratelimit-js --skill upstash-ratelimit-tsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 64 |
|---|---|
| repo stars | ★ 2k |
| Last updated | March 9, 2026 |
| Repository | upstash/ratelimit-js ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Rate Limit TS SDK
Quick Start
- Install the SDK and connect to Redis.
- Create a rate limiter and apply it to incoming operations.
Example:
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const redis = new Redis({ url: "<url>", token: "<token>" });
const limiter = new Ratelimit({ redis, limiter: Ratelimit.slidingWindow(5, "10s") });
const { success } = await limiter.limit("user-id");
if (!success) {
// throttled
}Other Skill Files
- algorithms.md: Describes all available rate‑limiting algorithms and how they behave.
- pricing-cost.md: Explains pricing, Redis cost implications, and operational considerations.
- features.md: Lists SDK features such as prefixes, custom keys, and behavioral options.
- methods-getting-started.md: Full method reference for the SDK's API and getting started guide.
- traffic-protection.md: Guidance on applying rate limiting for traffic shaping, abuse prevention, and protection patterns.
Ratelimiting Algorithms
This documentation explains the three algorithms supported by the ratelimit‑ts SDK: Fixed Window, Sliding Window, and Token Bucket. It focuses on practical usage, pitfalls, and choosing the right algorithm.
Fixed Window
Divides time into fixed periods (for example, 10‑second windows). Requests increment a counter for the current window and are rejected once the limit is exceeded.
Pitfalls
- Burst leakage: many requests at the boundary may bypass intended behavior.
- Stampedes: large client populations may all retry at the start of a window.
- Reset time is based on fixed boundaries, not on the first request.
When to use
- When performance and low computational cost are important.
- When small inaccuracies at boundaries are acceptable.
Example
// 10 requests per 10 seconds
const regional = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.fixedWindow(10, "10 s"),
});
const multi = new MultiRegionRatelimit({
redis: [new Redis({/* auth */}), new Redis({/* auth */})],
limiter: MultiRegionRatelimit.fixedWindow(10, "10 s"),
});Sliding Window
Uses rolling time windows to smooth boundary behavior. Counts requests in the previous window proportionally based on elapsed time.
Pitfalls
- Slightly more expensive to compute and approximate.
- Assumes uniform distribution of past requests.
- In multi‑region mode, generates many Redis commands and can slow down operations.
- Reset time exposed via
limitandgetRemainingis only the start of the next full window.
When to use
- When smoother behavior around window boundaries is important.
- Avoid in multi‑region setups if command count is a concern.
Example
// 10 requests per 10 seconds
const regional = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"),
});
// Multi-region is possible but inefficient
const multi = new MultiRegionRatelimit({
redis: [new Redis({/* auth */}), new Redis({/* auth */})],
limiter: MultiRegionRatelimit.slidingWindow(10, "10 s"),
});Token Bucket
Maintains a bucket of tokens that refill at a defined rate. Each request consumes one token; if none remain, requests are rejected.
Advantages
- Smooths bursts naturally.
- Allows high initial burst capacity (
maxTokens > refillRate).
Pitfalls
- Higher computational cost.
- Not yet supported for multi‑region.
When to use
- When smoothing request traffic and allowing controlled bursts is important.
Example
// Bucket with max 10 tokens, refilling 5 tokens every 10s
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.tokenBucket(5, "10 s", 10),
analytics: true,
});Features
This Skill documents the core features of the Upstash Rate Limiter for TypeScript. It highlights how to apply caching, timeouts, analytics, multiple limit strategies, dynamic limits, and multi-region setups.
Caching
Caching prevents unnecessary Redis calls when identifiers are already blocked.
Key points:
- Use an in-memory
Map<string, number>asephemeralCache. - Default: a new
Map()is created automatically. - Disable by setting
ephemeralCache: false. - Works only when the cache or rate limiter is created outside serverless handlers.
- Responses blocked by cache return
reason: cacheBlock.
Example:
const cache = new Map();
const ratelimit = new Ratelimit({
limiter: Ratelimit.slidingWindow(10, "10 s"),
ephemeralCache: cache,
});Timeout
A timeout allows requests to proceed if Redis is slow or unreachable.
- Default timeout: 5 seconds
- On timeout success,
reasonreflects this
Example:
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"),
timeout: 1000,
});Analytics & Dashboard
Analytics collect counts of success/blocked requests.
- Disabled by default; enable via
analytics: true - Data is viewable in the Upstash Rate Limit Dashboard
- In edge runtimes, ensure analytics requests complete using
pendingfromlimit()
Example:
const { pending } = await ratelimit.limit("id");
context.waitUntil(pending);Using Multiple Limits
Different user tiers can use different limiters.
Example:
const ratelimit = {
free: new Ratelimit({ prefix: "free", limiter: Ratelimit.slidingWindow(10, "10s") }),
paid: new Ratelimit({ prefix: "paid", limiter: Ratelimit.slidingWindow(60, "10s") }),
};
await ratelimit.free.limit(ip);
await ratelimit.paid.limit(userId);Custom Rates
Specify how many tokens to subtract per request using rate.
Example:
await ratelimit.limit("identifier", { rate: batchSize });Multi Region
Multi-region rate limiting provides lower latency and state replication via CRDTs.
- Uses multiple Redis instances
- Trades strict accuracy for global performance
Example:
const ratelimit = new MultiRegionRatelimit({
redis: [redisUS, redisEU],
limiter: MultiRegionRatelimit.slidingWindow(10, "10 s"),
});
const { pending } = await ratelimit.limit("id");
context.waitUntil(pending);Dynamic Limits
Update rate limits at runtime without recreating the limiter.
- Works only for single-region limiters (fixedWindow, slidingWindow, tokenBucket)
Example:
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10s"),
dynamicLimits: true,
});
await ratelimit.setDynamicLimit({ limit: 5 });
const current = await ratelimit.getDynamicLimit();
await ratelimit.setDynamicLimit({ limit: false });Common Pitfalls
- Forgetting to place the cache outside serverless handlers disables effective caching.
- Not calling
context.waitUntil(pending)in edge runtimes may cause lost analytics/sync requests. - Multi-region setups cannot guarantee strict limit enforcement.
- Dynamic limits do not work with multi-region limiters.
Upstash Ratelimit Methods (TypeScript)
This document provides a focused, practical reference for all Ratelimit methods. Each section includes direct examples, usage patterns, and common pitfalls.
limit
Primary method for checking and consuming tokens.
const { success, remaining, reset, reason, pending } = await ratelimit.limit(
identifier,
{
rate: 2, // optional: consume N tokens
ip: req.ip, // optional: used for deny‑list checks
userAgent: ua, // optional
country: geo?.country,
}
);
if (!success) return "blocked";
// In Cloudflare/Vercel Edge, flush async work
context.waitUntil(pending);Notes:
ratelets a request consume more than 1 token.reasoncan be:timeout,cacheBlock,denyList, or undefined.- When analytics or MultiRegion is enabled, always handle `pending` in serverless environments.
blockUntilReady
Waits for a request to become allowed instead of rejecting immediately.
const { success } = await ratelimit.blockUntilReady("id", 30_000);
if (!success) return "still blocked after timeout";resetUsedTokens
Clears the state for an identifier.
await ratelimit.resetUsedTokens("user123");Useful when granting temporary resets or admin overrides.
getRemaining
Read-only view of remaining quota.
const { remaining, reset } = await ratelimit.getRemaining("user123");Common use cases:
- Dashboard queries
- Showing users their remaining quota
setDynamicLimit
Overrides the global limit at runtime.
await ratelimit.setDynamicLimit({ limit: 5 }); // set
await ratelimit.setDynamicLimit({ limit: false }); // removeNotes:
- Requires
dynamicLimits: truein constructor. - Applies to all future rate checks.
getDynamicLimit
Fetch the currently active dynamic limit.
const { dynamicLimit } = await ratelimit.getDynamicLimit();Returns null when no override is active.
Pricing & Cost Considerations for Ratelimit Operations
This document explains how Redis command costs vary across Ratelimit algorithms, cache states, and optional features. Use it to reason about latency, throughput, and pricing impacts when designing systems with Upstash Ratelimit.
---
Overview
Redis command usage depends on:
- Algorithm type (Fixed Window, Sliding Window, Token Bucket)
- Algorithm state for a given identifier (first request, intermediate, rate‑limited)
- Cache hit/miss in the runtime environment
- Optional features (deny lists, analytics, dynamic limits, multi‑region replication)
A Global Upstash Redis setup multiplies write commands by (1 + readRegionCount) and adds 1 extra command when analytics is enabled.
---
Algorithm States
Each identifier (e.g., an IP or user ID) has an associated state:
- First: No existing key; creates state and sets expiry
- Intermediate: Key exists; normal operation
- Rate‑Limited: Request blocked; may avoid Redis if cache contains an unexpired block timestamp
Cache hits allow skipping Redis entirely for rate‑limited requests.
---
Cache Behavior
- Hit: Identifier found in in‑memory cache → request may be denied without Redis calls.
- Miss: Cache empty or value does not indicate a block → algorithm consults Redis.
Only rate‑limited results populate the cache.
---
Command Costs by Operation
limit()
Costs depend on algorithm, cache state, and identifier state.
Fixed Window:
- First: 3 commands (EVAL, INCR, PEXPIRE)
- Intermediate: 2 commands (EVAL, INCR)
- Rate‑limited miss: 2 commands (EVAL, INCR)
- Rate‑limited hit: 0 commands
Sliding Window:
- First: 5 commands (EVAL, GET, GET, INCR, PEXPIRE)
- Intermediate: 4 commands (EVAL, GET, GET, INCR)
- Rate‑limited miss: 3 commands (EVAL, GET, GET)
- Rate‑limited hit: 0 commands
Token Bucket:
- First/Intermediate: 4 commands (EVAL, HMGET, HSET, PEXPIRE)
- Rate‑limited miss: 2 commands (EVAL, HMGET)
- Rate‑limited hit: 0 commands
getRemaining()
Always deterministic, no cache effects:
- Fixed Window: 2 commands (EVAL, GET)
- Sliding Window: 3 commands (EVAL, GET, GET)
- Token Bucket: 2 commands (EVAL, HMGET)
resetUsedTokens()
Starts with SCAN and deletes all matching keys:
- Fixed Window: 3 commands (EVAL, SCAN, DEL)
- Sliding Window: 4 commands (EVAL, SCAN, DEL, DEL)
- Token Bucket: 3 commands (EVAL, SCAN, DEL)
blockUntilReady()
Same cost model as limit().
---
Optional Features Impact
Deny List
Adds 2 commands per `limit()` call:
- One
SMISMEMBERcheck - One TTL fetch for deny list validity
Auto‑IP deny list refresh uses 9 commands once per day (first limit call after 02:00 UTC).
A deny‑listed identifier is cached for a minute, skipping Redis for subsequent checks.
Analytics
Adds 1 Redis command per `limit()` via ZINCRBY.
Dynamic Limits
Adds 1 command to each limit() and getRemaining() call.
setDynamicLimit()= 1 commandgetDynamicLimit()= 1 command
Multi‑Region
Effective cost becomes:
(1 + readRegionCount) * writeCommandCount + readCommandCount
+1 if analytics is enabled---
Example Usage
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const redis = new Redis({ url: process.env.UPSTASH_URL!, token: process.env.UPSTASH_TOKEN! });
const limiter = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, "60 s"),
analytics: true, // adds +1 ZINCRBY per call
enableProtection: true, // adds +2 per call
dynamicLimits: true // adds +1 per call
});
async function handle(ip) {
const { success, reset } = await limiter.limit(ip);
if (!success) {
return { status: 429, retryAfter: reset - Date.now() };
}
const remaining = await limiter.getRemaining(ip);
return { status: 200, remaining };
}This example shows all cost‑impacting features enabled. Sliding Window + deny list + analytics + dynamic limits can increase the base 4–5 command cost per limit().
---
Common Pitfalls
- Ignoring cold starts: Serverless environments often start with empty caches → initial calls incur higher Redis usage.
- Using Sliding Window when cost‑sensitive: It requires multiple GET operations, making it the most expensive algorithm.
- Unexpected multi‑region amplification: Write commands scale with region count; cost can rise significantly.
- Assuming deny list is free: Even when no identifier is blocked, two extra commands execute per request.
---
This file provides an operational reference for estimating Redis usage and optimizing Ratelimit performance and cost.
Traffic Protection
This skill documents how to use deny lists and automatic IP protection in the Upstash Ratelimit TypeScript SDK. It explains configuration, behavior, caching, update patterns, and common pitfalls.
---
Deny Lists
Deny lists block requests based on IP, user agent, country, or identifier. Enable protection by setting enableProtection: true when creating your Ratelimit client.
Example usage:
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"),
enableProtection: true,
analytics: true,
});
const result = await ratelimit.limit("userId", {
ip: "203.0.113.5",
userAgent: "malicious-bot",
country: "CN",
});
await result.pending; // analytics sync
if (!result.success && result.reason === "denyList") {
console.log("Blocked value:", result.deniedValue);
}Behavior & Pitfalls
- Exact match only; pattern matching is not supported.
- Denied values are cached for 1 minute to reduce Redis load. Removal from the deny list may take up to a minute to propagate.
- Adding a value propagates instantly.
- Dashboard manages all deny list entries; analytics can show aggregated blocks.
---
Auto IP Deny List
Automatically blocks IPs aggregated from >30 open‑source abuse lists (via GitHub's ipsum repository). Updates occur daily at 2 AM UTC.
Enable protection:
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"),
enableProtection: true,
});
const { success, pending } = await ratelimit.limit("userId", { ip: "203.0.113.77" });
await pending; // ensures async sync completionUpdate Flow
- First call to
limitafter 2 AM UTC triggers asynchronous list refresh. - Request results are returned immediately; updates complete in the background.
- Use the
pendingpromise when accuracy depends on the sync.
Dashboard Integration
- All auto‑blocked IPs appear in the "Denied" section.
- Feature can be disabled from the Upstash Console without disabling standard deny lists.
---
Common Mistakes
- Forgetting to pass
ip,userAgent, orcountrytolimit→ protection does not apply. - Expecting pattern or CIDR matches; only exact strings are checked.
- Confusing auto IP deny list with manual deny list entries; both operate independently.