> Documentation index: https://beezr.io/developers/api/llms.txt

# Limits

## Limit layers

These are the defaults. All workspace keys share the workspace limits.

| Limit | Default | Scope | When exceeded |
|---|---|---|---|
| Requests per minute | 60 | Workspace | 429 `RATE_LIMIT_EXCEEDED`, `Retry-After` seconds |
| Requests per hour | 2,000 | Workspace | 429 `RATE_LIMIT_EXCEEDED`, `Retry-After` |
| Requests at the same time | 2 | Workspace | 429 `TOO_MANY_CONCURRENT_REQUESTS`, `Retry-After: 1` |
| Requests per minute from one IP | 120 | Caller IP, before key check | 429 `RATE_LIMIT_EXCEEDED`, `Retry-After` |
| Read calls per key | 1,000 per hour | Key, for query/get/content | 429 `RATE_LIMIT_EXCEEDED`, `X-RateLimit-Reset` |
| Monthly API calls | Set by plan | Workspace, calendar month | 429 `MONTHLY_QUOTA_EXCEEDED` past 110% of allowance; resets on the 1st |
| Records per bulk call | 100 | Request | 413 `PAYLOAD_TOO_LARGE` |
| File size | 10 MB | File, upload and download | Upload: 400 `CONTENT_TOO_LARGE`; download: 413 `CONTENT_TOO_LARGE` |
| Query page size | 1–100; default 20 | Query | Clamped, no error |

A bulk call counts as one request for rate limits and the monthly count. If your integration needs higher limits, contact us.

## Decide when to retry

All 429s are retryable after waiting, except `MONTHLY_QUOTA_EXCEEDED`: stop until the 1st of the next month. On other 429s, wait `Retry-After` seconds. For the per-key read window, `X-RateLimit-Reset` gives the reset time in epoch milliseconds.

Keep at most 2 calls in flight. Batch writes with bulk endpoints and request only the fields you need. For create and update retries, keep the same [idempotency key](/developers/api/idempotency/) and body.

## Read the headers

Read responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` for the per-key hourly read limit. Write responses don't carry these headers. The per-key values don't describe the workspace minute limit.

## Retry code

These helpers take an existing JSON request body. They bound retries, inspect the monthly error before waiting, and use the read reset header when Retry-After is absent. If neither wait header is present, they stop for the caller to inspect. A create or update caller must provide the same idempotency key on each attempt. Bulk calls don't support idempotency.

```curl
# Bash helper; Python reads headers and JSON. Usage: beezr_post URL BODY [IDEMPOTENCY_KEY]
beezr_post() (
  url=$1; body=$2; key=${3:-}
  tmp=$(mktemp -d)
  trap 'rm -rf "$tmp"' EXIT
  extra=()
  [ -z "$key" ] || extra=(-H "Idempotency-Key: $key")
  for attempt in 1 2 3 4; do
    status=$(curl --silent --show-error -o "$tmp/body" -D "$tmp/headers" -w '%{http_code}' \
      "$url" -H "X-API-Key: $BEEZR_API_KEY" -H "Content-Type: application/json" \
      "${extra[@]}" --data "$body") || return 1
    if [ "$status" != 429 ]; then
      cat "$tmp/body"
      [[ "$status" == 2* ]] && return 0
      return 1
    fi
    wait=$(python - "$tmp/body" "$tmp/headers" <<'PY'
import json, sys, time
with open(sys.argv[1]) as f:
    result = json.load(f)
if result.get("error", {}).get("code") == "MONTHLY_QUOTA_EXCEEDED":
    sys.exit(1)
with open(sys.argv[2]) as f:
    headers = dict(line.strip().split(": ", 1) for line in f if ": " in line)
headers = {k.lower(): v for k, v in headers.items()}
if "retry-after" in headers:
    print(max(0, float(headers["retry-after"])))
elif "x-ratelimit-reset" in headers:
    print(max(0, float(headers["x-ratelimit-reset"]) / 1000 - time.time()))
else:
    sys.exit(1)
PY
    ) || { cat "$tmp/body"; return 1; }
    [ "$attempt" -lt 4 ] || { cat "$tmp/body"; return 1; }
    sleep "$wait"
  done
)
```

```javascript
async function beezrPost(url, body, idempotencyKey) {
  const headers = { "X-API-Key": process.env.BEEZR_API_KEY, "Content-Type": "application/json" };
  if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey;
  for (let attempt = 0; attempt < 4; attempt++) {
    const response = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) });
    const result = await response.json();
    if (response.status !== 429) {
      if (!response.ok || !result.success) throw new Error(JSON.stringify(result));
      return result;
    }
    if (result.error?.code === "MONTHLY_QUOTA_EXCEEDED" || attempt === 3) {
      throw new Error(JSON.stringify(result));
    }
    const seconds = response.headers.get("Retry-After");
    const reset = response.headers.get("X-RateLimit-Reset");
    if (seconds === null && reset === null) throw new Error(JSON.stringify(result));
    const wait = seconds !== null ? Number(seconds) * 1000 : Number(reset) - Date.now();
    if (!Number.isFinite(wait)) throw new Error("Invalid retry header");
    await new Promise(resolve => setTimeout(resolve, Math.max(0, wait)));
  }
}
```

```python
import os
import time
import requests

def beezr_post(url, body, idempotency_key=None):
    headers = {"X-API-Key": os.environ["BEEZR_API_KEY"]}
    if idempotency_key:
        headers["Idempotency-Key"] = idempotency_key
    for attempt in range(4):
        response = requests.post(url, headers=headers, json=body)
        result = response.json()
        if response.status_code != 429:
            response.raise_for_status()
            if not result.get("success"):
                raise RuntimeError(result)
            return result
        if result.get("error", {}).get("code") == "MONTHLY_QUOTA_EXCEEDED" or attempt == 3:
            raise RuntimeError(result)
        seconds = response.headers.get("Retry-After")
        reset = response.headers.get("X-RateLimit-Reset")
        if seconds is None and reset is None:
            raise RuntimeError(result)
        wait = float(seconds) if seconds is not None else float(reset) / 1000 - time.time()
        time.sleep(max(0, wait))
```


## Watch monthly usage

Usage is visible in Designer → Usage (API calls) and Subscription. Emails go to the workspace alert address at 80% and 100% of allowance and when calls are blocked. See [Handle rate limits well](/developers/api/guides-limits/).

