> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.jelliu.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.jelliu.co/_mcp/server.

# Rate limits

Rate limits protect your workspace and the platform. Once a request is authenticated, limits are counted **per workspace**: every API key of the same workspace shares the same budgets. Requests that are not yet tied to a workspace are counted per client IP.

All limits use a **60-second window**. The counter starts with the first request in the window and resets when the window ends.

## General API limit

Most `/api/*` routes are behind a general limit that scales with your plan:

| Plan           | Requests per minute |
| -------------- | ------------------- |
| No active plan | 120                 |
| Starter        | 120                 |
| Growth         | 200                 |
| Business       | 300                 |
| Enterprise     | 600                 |

## Stricter limits on specific routes

Some routes carry a stricter limit. Where it is added on top of the general limit, a request must pass both. Calls, WhatsApp, email and billing routes are governed by their own limit instead of the general one.

| Routes                                                                                                                                                                                       | Limit    | Notes                                                                                         |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------- |
| Everything under `/api/calls`, `/api/whatsapp` and `/api/email`                                                                                                                              | 20 / min | Covers operations that place calls or send messages, and applies to reads on those paths too. |
| Mutations on agents, campaigns, webhooks and other configuration (for example `POST /api/agents`, `POST /api/campaigns`, `PATCH /api/campaigns/{campaignId}/activate`, `POST /api/webhooks`) | 10 / min | These routes **share one budget** per workspace.                                              |
| Everything under `/api/campaigns/{campaignId}/contacts`                                                                                                                                      | 5 / min  | Includes bulk imports and CSV upload confirmation.                                            |
| Analytics endpoints under `/api/analytics`                                                                                                                                                   | 30 / min |                                                                                               |
| Audit log under `/api/audit`                                                                                                                                                                 | 20 / min |                                                                                               |
| Billing under `/api/billing`                                                                                                                                                                 | 15 / min |                                                                                               |

Loading many contacts? Use a single `POST /api/campaigns/{campaignId}/contacts/bulk` request (up to 5,000 contacts) instead of one request per contact.

The limit on `/api/calls`, `/api/whatsapp` and `/api/email` fails closed: if Jelliu cannot count requests reliably, those routes answer `429` until it can, so that outbound spend is never uncapped.

## Response headers

Responses from rate-limited routes include:

| Header                | Meaning                                           |
| --------------------- | ------------------------------------------------- |
| `RateLimit-Limit`     | Requests allowed in the current window.           |
| `RateLimit-Remaining` | Requests left in the current window.              |
| `RateLimit-Reset`     | Seconds until the window resets.                  |
| `RateLimit-Policy`    | The policy, in the form `limit;w=60`.             |
| `Retry-After`         | Sent with `429`: seconds to wait before retrying. |

When several limits apply to one request, the `RateLimit-*` headers describe one of them. Always rely on `Retry-After` when you receive a `429`.

## When you hit a limit

The API answers `429 Too Many Requests` with code `RATE_LIMIT_EXCEEDED`:

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 42
RateLimit-Limit: 120
RateLimit-Remaining: 0
RateLimit-Reset: 42
Content-Type: application/json
```

```json
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests, please slow down"
  }
}
```

The `message` differs between limits (for example `Rate limit exceeded for outbound operations` or `Too many agent operations, please slow down`); the `code` is always `RATE_LIMIT_EXCEEDED`.

### Handling 429 responses

**`Node.js`**

```javascript title="Node.js"
async function jelliuFetch(url, init, attempts = 5) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(url, init);
    if (res.status !== 429 && res.status !== 503) return res;

    const retryAfter = Number(res.headers.get('Retry-After'));
    const waitMs = Number.isFinite(retryAfter) && retryAfter > 0
      ? retryAfter * 1000
      : 2 ** i * 1000;
    await new Promise((resolve) => setTimeout(resolve, waitMs));
  }
  throw new Error('Rate limited: retries exhausted');
}
```

**`Python`**

```python title="Python"
import time
import requests

def jelliu_request(method, url, attempts=5, **kwargs):
    for i in range(attempts):
        res = requests.request(method, url, timeout=30, **kwargs)
        if res.status_code not in (429, 503):
            return res
        retry_after = res.headers.get("Retry-After", "")
        wait = int(retry_after) if retry_after.isdigit() else 2 ** i
        time.sleep(wait)
    raise RuntimeError("Rate limited: retries exhausted")
```

## Other limits

* **MCP server.** The MCP endpoint allows 120 requests per minute per client IP. Each tool the assistant calls is an API request and also counts against your workspace limits above.
* **Web chat widget.** Public widget traffic is limited per visitor IP, per widget (its `rate_limit_rpm` setting, 30 by default) and per workspace (300 per minute across all widgets). See [Web chat widget](/widget).
* **Plan capacity** such as concurrent calls, and budgets you configure on agents, are not rate limits. They return their own codes (`MAX_CONCURRENT_CALLS_REACHED`, `AGENT_BUDGET_EXCEEDED`); see [Errors](/errors).