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 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.
# 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
)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)));
}
}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.