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

# Rate limits

> Learn the rate limits ShieldLabs enforces and how to stay within them.

These limits protect the ingest infrastructure. They are **not** part of the Risk Score and they never change how a visitor is scored. The [Risk Score](/features/risk-scoring) is `0-100` (Clean / Low / Medium / High) and is driven entirely by the signals collected from a visitor. This page is about keeping the gateway healthy under load.

<Note>
  In normal use you rarely hit these limits. The [snippet](/setup/snippet) manages its own call cadence (one identify per visit, throttled by a session window), so a single visitor on a single page does not generate a burst of requests. Limits exist to absorb abusive traffic, not legitimate integration patterns.
</Note>

## The limits at a glance

The snippet posts collected signals to `rest.shieldlabs.ai`. That ingest gateway enforces three protections:

| Protection        | Scope                    | Threshold                    | Response when exceeded                        |
| ----------------- | ------------------------ | ---------------------------- | --------------------------------------------- |
| Per-IP rate limit | Source IP, per minute    | 20 requests / minute         | `429 Too Many Requests`, then a 1-hour IP ban |
| Concurrency cap   | Whole gateway, in-flight | 512 simultaneous connections | `503 Service Unavailable`                     |
| Request body size | Per request              | 512 KB                       | Request rejected                              |

All three are pure infrastructure guards on the ingest gateway. None of them feed the score, and none of them appear in the webhook or History API payloads as a signal.

The **Management API** on `api.shieldlabs.ai` applies the same per-IP rate limit (20 req/min, 1-hour ban) and concurrency cap (512 in-flight) independently. A `429` or `503` there returns `{"error":"too many requests"}` or `{"error":"server is busy"}`. The [History API](/api/server-api) on `account.shieldlabs.ai` is not subject to these gateway limits.

## Per-IP rate limit and the 1-hour ban

A single source IP may make up to **20 requests per minute** to the REST ingest endpoint. Cross that and the gateway returns:

```json theme={null}
HTTP/1.1 429 Too Many Requests
Content-Type: application/json

{ "error": "too many requests" }
```

After the threshold is crossed, that IP is **banned for 1 hour**. During the ban window, further requests from the same IP continue to be rejected with `429`. The ban clears automatically; there is no manual unban step and nothing to configure.

<Warning>
  Because the limit is **per source IP**, be careful in environments where many real users share one egress IP (a corporate NAT, a mobile carrier gateway, a shipping/CI proxy). In those setups a normal crowd of users can look like one busy IP. If you proxy snippet traffic through your own backend, you collapse every user onto your server's IP and will hit this limit fast. Let the snippet post directly from the browser instead.
</Warning>

### Guard against the "999" ban marker

When an IP is banned, the **browser receives the `429`**, but your backend can still see a snapshot for that request: ShieldLabs writes a sentinel value of `999` to mark the banned request, and that snapshot can reach you on a [webhook](/setup/webhooks) delivery or a [History API](/api/server-api) row. The `999` is not capped to 100 on the ban path, so it arrives as-is. You may therefore receive a payload where `data.risk_score` (webhook) or `Score` (History API) is `999`.

<Warning>
  **Guard for `999` before you read the band.** A rate-limit ban is a gateway event, not a high-risk visitor, but its `999` marker can land in a webhook or History row uncapped. Drop those rows at the top of your handler:

  ```js theme={null}
  // The Risk Score is 0-100. Anything above 100 is the rate-limit ban marker, not a score.
  const score = req.body?.data?.risk_score ?? req.body?.Score;
  if (score > 100) return;
  ```

  Branch your decision logic only on a score in the `0-100` range. Read a `999` as "this IP was rate-limited at the gateway," and look at the `429` status, not the number.
</Warning>

## Concurrency cap (503)

Independent of the per-IP limit, the gateway caps the number of **simultaneous in-flight requests** across all traffic. When that cap (512 connections) is saturated, new requests get:

```json theme={null}
HTTP/1.1 503 Service Unavailable
Content-Type: application/json

{ "error": "server is busy" }
```

A `503` here means "try again shortly," not "you did something wrong." It is transient back-pressure. The snippet does not need special handling for this; if you call the [Server API](/api/server-api) server-side and hit a `503`, retry with a short backoff.

## Request body size (512 KB)

Each request body to the REST gateway is capped at **512 KB**. The signal payload the snippet sends is well under this in normal operation, so you will not approach the cap unless something is wrong upstream (for example, a payload being duplicated or wrapped before it reaches the gateway). Oversized bodies are rejected before scoring.

## Billing exhaustion is separate (402)

Running out of request balance is **not** a rate limit. It is a billing condition with its own status code:

```json theme={null}
HTTP/1.1 402 Payment Required
```

A `402` means the domain has no remaining request balance, so the call cannot be processed. This is unrelated to how fast you are sending traffic. Top up or upgrade the plan to clear it. The [Billing](/billing) page covers how requests are counted (one identify = one request), and the [Errors](/errors) page lists the full status-code reference.

## How to stay within the limits

<Steps>
  <Step title="Let the snippet post from the browser">
    The [snippet](/setup/snippet) is designed to call `rest.shieldlabs.ai` directly from each visitor's browser, so requests are naturally spread across many client IPs. Do not relay snippet traffic through a single server, which would funnel everyone onto one IP and trip the 20/minute limit.
  </Step>

  <Step title="Rely on the built-in session throttle">
    The snippet runs one identify per visit within a session window rather than on every interaction. You generally do not need to add your own debouncing. Use `forceCheckAnonymous` / `forceCheckAuthenticatedUser` only at meaningful moments (right after login, before a sensitive action), not in a loop.
  </Step>

  <Step title="Read results, do not poll the gateway">
    Get scores from the [webhook](/setup/webhooks) (delivered automatically) or, when you need a guaranteed read, from the [History API](/api/server-api). Do not re-fire identify calls to "refresh" a score.
  </Step>

  <Step title="Back off on 503">
    For any server-side call, treat `503` as transient and retry after a short, jittered delay. Treat `429` as a hard stop for that IP until the ban window passes.
  </Step>
</Steps>

## Quick reference

| Status        | Cause                                    | What it means for you                                                                            |
| ------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `429`         | More than 20 requests/minute from one IP | That IP is banned for 1 hour. Spread traffic across client IPs; do not proxy through one server. |
| `503`         | Gateway concurrency cap reached          | Transient back-pressure. Retry with a short backoff.                                             |
| `402`         | Domain request balance exhausted         | A [billing](/billing) condition, not a rate limit. Top up or upgrade.                            |
| Rejected body | Request body over 512 KB                 | Payload too large for the ingest endpoint.                                                       |

The [Errors](/errors) page documents every status code ShieldLabs can return and how to handle it.
