> ## Documentation Index
> Fetch the complete documentation index at: https://redop.useagents.site/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Upstash Ratelimit

> Protect a Redop server with Upstash Ratelimit using Redis-backed sliding-window limits and standard 429 responses.

Use Upstash Ratelimit when you want Redis-backed rate limiting with serverless-friendly HTTP APIs, analytics, caching, and optional multi-region support.

This guide adapts Upstash's TypeScript Ratelimit SDK to a Redop server.

## Install

Add the Upstash packages to your Redop app:

```bash theme={null}
bun add @upstash/ratelimit @upstash/redis
```

## Prerequisites

You need:

* an Upstash Redis database
* `UPSTASH_REDIS_REST_URL`
* `UPSTASH_REDIS_REST_TOKEN`

## Create the limiter

Create the limiter once at module scope so blocked-request caching can work across hot requests.

```ts theme={null}
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10 s"),
  analytics: true,
  prefix: "@redopjs/upstash-ratelimit",
});
```

## Add it as Redop middleware

Use global middleware when you want one policy across many tools, resources, or prompts.

```ts theme={null}
import { McpError, McpErrorCode, Redop } from "@redopjs/redop";

new Redop({
  serverInfo: {
    name: "upstash-demo",
    version: "0.1.0",
  },
})
  .middleware(async ({ request, next }) => {
    const identifier =
      request.headers["x-user-id"] ??
      request.headers["x-forwarded-for"] ??
      request.ip ??
      "anonymous";

    const { pending, remaining, reset, success } =
      await ratelimit.limit(identifier);

    if (pending) {
      void pending.catch(() => {});
    }

    if (!success) {
      throw new McpError(
        McpErrorCode.InvalidRequest,
        `Rate limit exceeded. Retry after ${Math.ceil(
          Math.max(reset - Date.now(), 0) / 1000
        )} seconds.`
      );
    }

    return next();
  })
  .tool("ping", {
    handler: () => ({ ok: true }),
  });
```

## Identify callers carefully

Use a stable identifier for the type of traffic you want to control:

* API key for per-key limits
* tenant ID for per-tenant limits
* authenticated user ID for per-user limits
* IP address only when you do not have a better identity

## Add response metadata

If you want the rate-limit result available to handlers, store it in request context with `derive(...)`.

```ts theme={null}
const app = new Redop<{ rateLimit: { remaining: number; reset: number } }>()
  .derive(async ({ request }) => {
    const identifier =
      request.headers["x-user-id"] ?? request.ip ?? "anonymous";

    const { pending, remaining, reset, success } =
      await ratelimit.limit(identifier);

    if (pending) {
      void pending.catch(() => {});
    }

    if (!success) {
      throw new Error("Too many requests");
    }

    return {
      rateLimit: { remaining, reset },
    };
  });
```

## What Upstash adds

According to the Upstash Ratelimit TypeScript docs, notable features include:

* in-memory blocking cache for hot serverless instances
* timeout handling
* analytics and dashboard support
* traffic protection and deny lists
* multiple limits and dynamic limits
* optional multi-region replication

If you enable analytics or multi-region sync, Upstash also returns a `pending` promise. In serverless runtimes, use the runtime's background-wait primitive when available so those async tasks can finish.

## When to choose Upstash

Upstash is a strong fit when you already use Upstash Redis or want Redis-backed control with features like analytics, deny lists, or multi-region behavior.

## See also

* [Build a plugin or middleware](/docs/guides/build-plugin-or-middleware)
* [Add request context](/docs/guides/add-request-context)
* [Error handling](/docs/documentation/error-handling)
