Rate Limits
Understand the rate limits applied to the BeeL. API and how to handle them.
The BeeL. API enforces rate limits to protect service stability. Limits are enforced at the network edge, on a fixed 60-second window. What the window is counted against depends on the tier: Standard counts per credential, while Strict and Global count per originating IP address.
Tiers
| Tier | Limit | Window | Applied to |
|---|---|---|---|
| Global | 2,000 requests | 60 seconds | Every request to the API |
| Standard | 300 requests | 60 seconds | /v1/invoices, /v1/customers and /v1/me and their sub-paths |
| Strict | 5 requests | 60 seconds | POST /v1/invitations |
A single request may count against more than one tier. A POST /api/v1/companies/{company_id}/invoices counts against both the Standard tier (300/min) and the Global tier (1,000/min).
These limits govern API requests. The number of emails you can actually send is a separate, lower quota with its own axes — see Sending email.
Client Identification
The Standard tier is tracked per credential: per API key, or per client for OAuth2 tokens and dashboard sessions. Each key has its own independent Standard quota, so splitting an integration across several keys (for example one per worker) splits that quota accordingly.
The Strict and Global tiers are tracked per originating IP address, whatever credential you present. Several keys calling from the same host share those two budgets, and adding keys does not raise them.
The edge cannot validate a credential — it holds neither your API key nor the session store. If Strict were keyed by credential, sending a different invented Authorization: Bearer on each request would mint a fresh bucket and defeat the limit on /api/v1/auth/login entirely. That is why the two tiers that exist to stop abuse are keyed by the one thing a caller cannot fake.
Excluded Endpoints
| Endpoint | Reason |
|---|---|
/api/webhooks/stripe | Stripe signs webhook requests; rejecting them causes retries |
Response Headers
When a rate limit is hit, the API returns 429 Too Many Requests with these headers:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
RateLimit-Limit: 300
RateLimit-Remaining: 0
RateLimit-Reset: 60{
"error": "Rate limit exceeded. Try again in a minute."
}Request-rate 429s are emitted by the edge filter with the minimal body above; monthly-quota 429s (QUOTA_EXCEEDED) come from the application with the full error envelope. Program against the status and headers, not the body shape.
| Header | Description |
|---|---|
Retry-After | Seconds to wait before retrying. This is the value to program against. |
RateLimit-Limit | Maximum requests allowed in the current window (or quota) |
RateLimit-Remaining | Requests remaining in the current window |
RateLimit-Reset | When the window or quota resets — seconds for request-rate 429s, a date for monthly-quota 429s. Prefer Retry-After for timing retries. |
RateLimit-* headers are only sent on a 429. Successful responses carry none, so you cannot track your remaining budget ahead of time — don't build a client-side gauge. React to the 429 and honour Retry-After.
Handling Rate Limits
Retry with backoff
async function requestWithRetry(fn: () => Promise<any>, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.status === 429) {
const retryAfter = parseInt(error.headers?.['retry-after'] || '60');
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}Best practices
- Use bulk endpoints when operating on multiple resources — one bulk request instead of N individual requests
- Cache responses where possible to avoid unnecessary API calls
- Respect
Retry-Afterheaders instead of retrying immediately - Use idempotency keys so retried requests don't create duplicates
API Status
Check status.beel.es for real-time API uptime, incident reports, and scheduled maintenance.
Need Higher Limits?
If the standard limits don't fit your use case, contact us and we'll work out a plan:
📧 Request a rate limit increase →