Skip to main content

Rate Limiting

The Arsel API enforces per-organization rate limits to ensure fair usage and platform stability. The limit is shared across every API key in the organization — adding more keys does not raise the ceiling.

Default Limits​

The API allows 1,000 requests per 60-second window per organization (about 16 requests per second on average). It is a fixed window, so the full budget can be spent at any point inside the 60 seconds.

POST /events/send is the one exception — it is budgeted in events, not requests. See Event ingestion below.

Need more? Contact support@arsel.sa.

Event ingestion​

POST /events/send has its own budget of 6,000 events per 60-second window per organization (about 100 events per second on average). It does not consume the 1,000-request budget above.

The unit is events, not requests: a batch of 50 events spends 50. This is deliberate — it means the ceiling describes the same amount of data whether your client batches events or sends them one at a time.

POST /events/send  { "event": "order.completed", ... }   → costs 1
POST /events/send { "events": [ <50 items> ] } → costs 50

Batching is still worth doing — it is far fewer round trips, and one Idempotency-Key covers the whole batch — but it does not buy a discount against this limit.

The budget is shared across every credential in the organization, secret keys (be_…) and publishable client keys (pub_…) alike.

Rate limit vs. monthly quota

This page covers the 60-second-window request and event limits. Your plan also has a monthly send quota per channel. Exhausting the monthly quota returns 429 monthly_quota_exceeded — a separate error that retrying won't resolve. See Common Send Errors.

Rate Limit Headers​

Every API response includes standard rate limit headers:

HeaderDescription
ratelimit-limitMaximum allowed in the current window — 1000 requests, or 6000 events on /events/send
ratelimit-remainingHow much of that budget is left in the current window
ratelimit-resetSeconds remaining until the window resets (a duration, not a Unix timestamp)

On /events/send these count events, matching that endpoint's budget. Reading ratelimit-remaining is the only reliable way to know your position there: a request costs between 1 and 50 depending on how many events it carries, so counting your own calls tells you nothing.

These headers are only on successful responses

ratelimit-* headers are not returned on a 429 response — only retry-after is. Do not build your backoff around reading ratelimit-remaining from a 429; on a 429, wait for retry-after seconds.

When a rate limit is exceeded, this header is included instead:

HeaderDescription
retry-afterSeconds to wait before retrying

Rate Limit Exceeded​

If you exceed the limit, the API returns HTTP 429:

{
"status_code": 429,
"name": "rate_limit_exceeded",
"message": "Too many requests. Please retry after the period indicated in the retry-after header."
}

Match on status_code and name — the exact message string is not a stable contract and may change.

Handling Rate Limits​

Read the headers on successful responses. Check ratelimit-remaining on 2xx responses to pace yourself — especially on /events/send, where request count is not a proxy for budget spent. On a 429, these headers are absent — use retry-after instead.

Wait for retry-after. When you receive a 429, wait the number of seconds in the retry-after header before retrying. Avoid tight retry loops.

Retry safely with an idempotency key. A retried send or event can be processed twice — double-counting rollups and revenue. Send an Idempotency-Key header so a retry within 24 hours returns the original result instead of repeating the side effect. See Idempotency for the endpoints that accept it.

Batch where a batch endpoint exists. Three endpoints take many items in one request:

EndpointItems per requestCost against the limit
POST /events/send50one per event
POST /push/devices/bulk1,000one call
POST /contacts/bulk-delete1,000one call

Everything else is one call per item, so space a bulk push out rather than emptying the 60-second budget at once.