# Rate limits

> Per-token, per-IP, and per-host rate limits for the Caramel API, plus response headers, backoff strategy, and monthly quotas.

The Caramel API rate-limits at two layers: the gateway in front, and individual tools behind it. Both apply to every request. The most restrictive limit wins. You get `429 Too Many Requests` with a `Retry-After` header before any other failure mode.

## Gateway limits

Three sliding-window caps apply simultaneously. A request is rejected if any cap is full.

| Dimension | Limit | Window | Bucket key |
|---|---|---|---|
| Per OAuth bearer token | 600 requests | 60 seconds | SHA-256 hash of the token |
| Per IP address | 60 requests | 60 seconds | Source IP |
| Per host (global) | 50,000 requests | 60 seconds | Hostname |

The window is sliding, not fixed — a request now and one 30 seconds later both count toward the same bucket.

The **per-bearer limit** is the one you'll hit in practice. 600 rpm = 10 rps sustained. If you need higher caps for a server-side integration handling a burst, email `aymen@reactmotion.com` with usage estimates.

## Per-tool limits

Most tools rely on the gateway's per-bearer cap. Two tools have additional limits:

| Tool | Extra limit | Window | Scope | Reason |
|---|---|---|---|---|
| `caramel.v1.form.submit` | 10 submissions | 60 seconds | Per `form_id` + IP | Anti-abuse — stops form-bombing |
| `generate_campaign`, `refine_campaign` | AI credit quota | 1 month | Per business | Credits decrement per call, not a sliding window |

When a per-tool limit is hit you get `429` with a `Retry-After` that reflects that tool's window, not the gateway's.

## Response headers on a 429

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 7
x-caramel-request-id: req_01h7m9k0aabcdefghj1234567
x-ratelimit-limit:     600
x-ratelimit-remaining: 0
x-ratelimit-reset:     1717684350
Content-Type: application/json

{
  "error":   "rate_limited",
  "message": "Per-token rate cap exceeded.",
  "status":  429
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds until you can retry. Integer; sometimes 0 if the window cleared in flight. |
| `x-ratelimit-limit` | Current bucket capacity. |
| `x-ratelimit-remaining` | Requests left in the current window. |
| `x-ratelimit-reset` | Unix epoch seconds when the bucket fully resets. |

## Backoff strategy

Always honor `Retry-After`. Always add jitter.

```js
async function rateLimitAware(fn) {
  while (true) {
    const res = await fn();
    if (res.status !== 429) return res;

    const wait = (Number(res.headers.get('retry-after')) || 1) * 1000;
    const jittered = wait + Math.random() * 500;
    await new Promise(r => setTimeout(r, jittered));
  }
}
```

```py
import time, random

def rate_limit_aware(fn):
    while True:
        res = fn()
        if res.status_code != 429:
            return res
        wait = int(res.headers.get('retry-after', '1'))
        time.sleep(wait + random.random() * 0.5)
```

Without jitter, every client retries at the same moment and re-triggers the limiter immediately.

## Avoid hitting limits

A few patterns stay well inside the caps:

**Cache `caramel.v1.meta.capabilities`.** The tool catalog changes only when a new tool ships — roughly monthly. Cache it for the session. Every uncached call wastes a bucket slot.

**Cache `list_businesses` for single-business accounts.** Don't call it on every page load.

**Batch status reads.** Fetch all campaigns once, then refresh on user action — not on a timer every 5 seconds.

**Serialize bulk writes.** When importing contacts via `caramel.v1.contact.upsert`, send at 5 rps. The limiter throttles faster traffic; you don't gain throughput.

## Monthly quotas

Some tools have monthly quotas tied to subscription tier. These return `429 submission_cap` or `402 insufficient_credits` — distinct from the gateway's `429 rate_limited`.

| Quota | Returned by | Error code |
|---|---|---|
| Forms submissions / month | `caramel.v1.form.submit` | `submission_cap` |
| AI credits / month | `generate_campaign`, `refine_campaign` | `insufficient_credits` (402) |

Check current usage with `caramel.v1.meta.usage`:

```bash
# MCP call
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "id": 1,
  "params": {
    "name": "caramel.v1.meta.usage",
    "arguments": { "business_id": "YOUR_BUSINESS_ID" }
  }
}
```

Poll this once per day for usage dashboard widgets, not on every API call.

## What is not rate-limited

These calls are free and don't consume any bucket:

- `caramel.v1.meta.capabilities` — tool discovery
- OAuth discovery (`/.well-known/oauth-authorization-server`)

The OAuth token endpoint has its own anti-abuse limits independent of the API limiter.

## Next steps

- [Errors](errors) — the full error catalog, including all 429 variants.
- [Tiers and scopes](tiers-and-scopes) — monthly quotas per tier.
