---
title: JIL Pre-Clearance Rate Limiting
version: 2026.05.03
owner: JIL Sovereign Technologies, Inc.
audience: integrators, ops, billing
---

# Pre-Clearance Rate Limiting

The Pre-Clearance API enforces per-customer token-bucket limits on every
authenticated route. This document covers the algorithm, defaults, response
shape, override mechanics, and how to request an upgrade.

## 1. Algorithm

A classic **token bucket**, evaluated in-process per `(customer_slug, route)`:

- Each bucket starts full (`burst` tokens).
- Tokens refill at `rps` per second, capped at `burst`.
- Every accepted request consumes 1 token.
- A request that arrives with 0 tokens is rejected with `429` and a
  `Retry-After` header.

In-memory only: if Pre-Clearance scales horizontally (planned Q3 2026), the
bucket map moves to Redis with `SETEX`-based atomic refill.

## 2. Defaults (Pilot tier)

| Metric | Value |
|---|---|
| Sustained rate (`rps`) | 100 req/s per customer per route |
| Burst (`burst`)        | 200 req                          |
| Granularity            | per `(customer_slug, route_path)` |
| Anonymous (pre-auth)   | no rate limit (auth gate is the chokepoint) |
| Service accounts       | bypass rate limiting              |

Tier B (`500/1000`) and Tier C (`5000/10000`) are available per MSA addendum.

## 3. 429 Response Shape

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 3
X-RateLimit-Limit: 100
X-RateLimit-Burst: 200
X-RateLimit-Remaining: 0
Content-Type: application/json

{
  "error": "rate_limited",
  "detail": "Customer 'fireblocks' exceeded 100 req/s sustained on /v1/preclearance/validate. Retry after 3s.",
  "limit_rps": 100,
  "burst": 200,
  "retry_after_s": 3
}
```

## 4. Successful-Request Headers

Every accepted request returns:

```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 47
```

`X-RateLimit-Remaining` is the floor of the current bucket level after the
request. Treat as advisory  -  don't gate client behavior on it.

## 5. Override Mechanism (ops)

To upgrade a customer mid-cycle without a full redeploy, set:

```bash
PRECLEARANCE_RATELIMIT_OVERRIDES='{"fireblocks":{"rps":500,"burst":1000}}'
```

`docker compose up -d --force-recreate preclearance-attestation` to apply.
Override JSON is loaded once at boot; subsequent changes require a recreate.

## 6. Capacity Planning Guidance

For custodians integrating into a Transaction Authorization Policy (TAP):

- A typical TAP fires `/validate` once per outbound transfer.
- An institutional custodian processes 1-50 transfers/sec under normal load,
  with bursts to 200/s during market opens.
- **Recommendation:** start on Pilot (100 sustained / 200 burst). Monitor
  `429` rate via your own metrics. Upgrade to Tier B if you sustain > 60 req/s
  for any 1-minute window.

## 7. Burst-Behavior FAQ

> **Q: My TAP fires 150 requests in the first 100ms after market open. Will I
> hit 429?**
>
> No. The default burst (200) covers a 100ms surge of 150 requests. The
> bucket refills 100/sec after that.

> **Q: My CI tests run 1,000 requests in 5 seconds against the sandbox. Why
> am I getting 429?**
>
> 1,000 ÷ 5 = 200 req/s sustained, which is 2× the Pilot rate. Either rate-
> limit the test client to 80 req/s or open an ops ticket to bump your sandbox
> override.

> **Q: Does the 429 count against my SLA uptime?**
>
> No. 429 is explicitly excluded from the "available" calculation (SLA §1).

## 8. Internals (engineers only)

- Source: `services/preclearance-attestation/src/rate-limit.ts`
- Wired into routes: `validate`, `verify`, `psa/:id` (read).
- Excluded routes: `health`, `ready`, `metrics`, `public-key`, all `/v1/admin/*`.
- Snapshot: `GET /health` returns `rate_limit: { default_rps, default_burst,
  overrides_active, active_buckets }`.

---

_Last updated: 2026-05-03._
