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

# Ratelimit

> Use the @unkey/ratelimit TypeScript SDK to add fast, globally distributed rate limiting to Node.js, Bun, Deno, and Cloudflare Workers — no Redis required.

`@unkey/ratelimit` is a library for fast global ratelimiting that runs in any **server-side** JavaScript runtime — Node.js, Deno, Bun, Cloudflare Workers, AWS Lambda, and other serverless or edge functions.

<Warning>
  **Server-side only.** This SDK requires your `UNKEY_ROOT_KEY`, which grants
  full access to your Unkey workspace. Never import or call `@unkey/ratelimit`
  from browser code, mobile apps, or any other client that ships to end users —
  doing so leaks the root key. Always invoke it from a trusted server, load the
  key from an environment variable (e.g. `process.env.UNKEY_ROOT_KEY`), and keep
  it out of bundles that reach the client.
</Warning>

<Card icon="github" title="github.com/unkeyed/sdks/tree/main/ratelimit/ts" href="https://github.com/unkeyed/sdks/tree/main/ratelimit/ts" />

## Install

<CodeGroup>
  ```bash npm theme={"theme":"kanagawa-wave"}
  npm install @unkey/ratelimit
  ```

  ```bash pnpm theme={"theme":"kanagawa-wave"}
  pnpm add @unkey/ratelimit
  ```

  ```bash yarn theme={"theme":"kanagawa-wave"}
  yarn add @unkey/ratelimit
  ```

  ```bash bun theme={"theme":"kanagawa-wave"}
  bun install @unkey/ratelimit
  ```
</CodeGroup>

## Configure your ratelimiter

```ts theme={"theme":"kanagawa-wave"}
import { Ratelimit } from "@unkey/ratelimit";

const unkey = new Ratelimit({
  rootKey: process.env.UNKEY_ROOT_KEY,
  namespace: "my-app",
  limit: 10,
  duration: "30s",
});
```

## Use it

```ts theme={"theme":"kanagawa-wave"}
async function handler(request) {
  const identifier = request.getUserId(); // or ip or anything else you want

  const ratelimit = await unkey.limit(identifier);
  if (!ratelimit.success) {
    return new Response("try again later", { status: 429 });
  }

  // handle the request here
}
```

## Making it bullet proof

Everything we do is built for scale and stability.
We built on some of the world's most stable platforms ([Planetscale](https://planetscale.com/) and [Cloudflare](https://www.cloudflare.com)) and run an extensive test suite before and after every deployment.

Even so, we would be fools if we wouldn't explain how you can put in safe guards along the way.

In case of severe network degradations or other unforeseen events, you might want to put an upper bound on how long you are willing to wait for a response from unkey.
By default the SDK will reject a request if it hasn't received a response from unkey within 5 seconds. You can tune this via the `timeout` config in the constructor (see below).

The SDK captures most errors and handles them on its own, but we also encourage you to add a `onError` handler to configure what happens in case something goes wrong.

Both `fallback` property of the `timeout` config and `onError` config are callback functions. They receive the original request identifier as one of their parameters, which you can use to determine whether to reject the request.

```ts theme={"theme":"kanagawa-wave"}
import { Ratelimit } from "@unkey/ratelimit";

// In this example we decide to let requests pass, in case something goes wrong.
// But you can of course also reject them if you want.
const fallback = (identifier: string) => ({
  success: true,
  limit: 0,
  reset: 0,
  remaining: 0,
});

const unkey = new Ratelimit({
  // ... standard stuff
  timeout: {
    ms: 3000, // only wait 3s at most before returning the fallback
    fallback,
  },
  onError: (err, identifier) => {
    console.error(`${identifier} - ${err.message}`);
    return fallback(identifier);
  },
});

const { success } = await unkey.limit(identifier);
```

***

## API

### `new Ratelimit(config: RatelimitConfig)`

Create a new instance for ratelimiting by providing the necessary configuration.

<ParamField body="RatelimitConfig" type="object" required>
  <Expandable title="RatelimitConfig">
    <ParamField body="limit" type="number" required>
      How many requests may pass in the given duration.
    </ParamField>

    <ParamField body="duration" type="Duration | number" required placeholder="60s">
      How long the window should be.

      Either a type string literal like `60s`, `20m` or plain milliseconds.
    </ParamField>

    <ParamField body="rootKey" type="string" required>
      The unkey root key. You can create one at [app.unkey.com/settings/root-keys](https://app.unkey.com/settings/root-keys)

      Make sure the root key has permissions to use ratelimiting.
    </ParamField>

    <ParamField body="namespace" type="string" required placeholder="tRPC">
      Namespaces allow you to separate different areas of your app and have isolated
      limits. Make sure the root key has permissions to use ratelimiting.
    </ParamField>

    <ParamField body="timeout" type="object | false" default={{ms: 5000,fallback: { success: false, limit: 0, remaining: 0, reset: Date.now()} }}>
      Configure a timeout to prevent network issues from blocking your function for too long.

      Disable it by setting `timeout: false`

      <Warning>
        Timeouts rely on `Date.now()`. In cloudflare workers time doesn't progress
        unless there is some io happening, which means the timeout might not work as
        expected. Other runtimes are working.
      </Warning>

      <Expandable defaultOpen>
        <ParamField body="ms" type="number" default={5000}>
          Time in milliseconds until the response is returned.
        </ParamField>

        <ParamField body="fallback" type="object" default={{ success: false, limit: 0, remaining: 0, reset: Date.now()} }>
          A custom response to return when the timeout is reached.

          The important bit is the `success` value, choose whether you want to let requests pass or not.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="onError" type="() => RatelimitResponse | Promise<RatelimitResponse>">
      Configure what happens for unforeseen errors

      Example letting requests pass:

      ```ts theme={"theme":"kanagawa-wave"}
      onError: () => ({ success: true, limit: 0, remaining: 0, reset: 0 });
      ```

      Example rejecting the request:

      ```ts theme={"theme":"kanagawa-wave"}
      onError: () => ({ success: true, limit: 0, remaining: 0, reset: 0 });
      ```
    </ParamField>
  </Expandable>
</ParamField>

### `.limit(identifier: string, opts: LimitOptions): Promise<RatelimitResponse>`

Check whether a specific identifier is currently allowed to do something or if they have currently exceeded their limit.

<ParamField body="LimitOptions" type="object" required>
  <Expandable title="LimitOptions">
    <ParamField body="cost" type="number" default={1}>
      Expensive requests may use up more resources. You can specify a cost to the request and
      we'll deduct this many tokens in the current window. If there are not enough tokens left,
      the request is denied.

      **Example:**

      1. You have a limit of 10 requests per second you already used 4 of them in the current
         window.

      2. Now a new request comes in with a higher cost:

      ```ts theme={"theme":"kanagawa-wave"}
      const res = await rl.limit("identifier", { cost: 4 });
      ```

      3. The request passes and the current limit is now at `8`

      4. The same request happens again, but would not be rejected, because it would exceed the
         limit in the current window: `8 + 4 > 10`
    </ParamField>
  </Expandable>
</ParamField>

### `RatelimitResponse`

<ParamField body="RatelimitResponse" type="object">
  <Expandable title="RatelimitResponse">
    <ParamField body="success" type="boolean" required>
      Whether the request may pass(true) or exceeded the limit(false).
    </ParamField>

    <ParamField body="limit" type="number" required>
      Maximum number of requests allowed within a window.
    </ParamField>

    <ParamField body="remaining" type="number" required>
      How many requests the user has left within the current window.
    </ParamField>

    <ParamField body="reset" type="number" required>
      Unix timestamp in milliseconds when the limits are reset.
    </ParamField>
  </Expandable>
</ParamField>
