Rate limits
Aleta meters API traffic per workspace to keep the platform responsive for every integrator. Limits are generous enough that normal backfills, daily syncs, and interactive usage never brush against them — but they do kick in for runaway loops, so every production integration should handle 429 responses gracefully.
Default limits
| Tier | Read requests / min | Write requests / min |
|---|---|---|
| Sandbox | 120 | 30 |
| Production | 1,200 | 300 |
| Enterprise | Custom | Custom |
Burst traffic up to double the per-minute budget is absorbed for short windows — a sync that fires 500 requests in 10 seconds won't trip the limiter as long as the trailing minute averages out.
Need higher limits? Reach out on support@aleta.io with a description of your integration's access pattern. Enterprise customers get custom tiers and region-specific pools.
Headers on every response
Every API response carries your current budget — check them instead of guessing. No polling, no extra requests.
- Name
X-RateLimit-Limit- Type
- integer
- Description
Requests permitted per minute for the current bucket (read or write).
- Name
X-RateLimit-Remaining- Type
- integer
- Description
Requests left before the limiter engages.
- Name
X-RateLimit-Reset- Type
- integer
- Description
Unix timestamp (seconds) when the current bucket refills.
- Name
Retry-After- Type
- integer
- Description
Only present on
429responses. Seconds to wait before retrying.
Handling 429 Too Many Requests
When you exceed the budget, Aleta responds with 429, a Retry-After
header, and no mutation is applied. The canonical response shape applies:
429 Too Many Requests
{
"error": {
"type": "rate_limited",
"code": "too_many_requests",
"message": "You've hit the per-minute write budget. Retry after 24 seconds.",
"request_id": "req_01J…"
}
}
Your integration should honour Retry-After exactly.
Hammering the API during a cooldown window doesn't speed anything up — it extends the window. Repeat offenders have their effective limit temporarily tightened until traffic patterns settle.
Backoff with a jitter
For background workers (batch jobs, resyncs, analytics pipelines), exponential backoff with jitter is the right shape. Raw exponential backoff without jitter causes the "thundering herd" — every worker retries at the same moment and the limiter re-engages immediately.
Backoff with full jitter
async function retryWithBackoff<T>(fn: () => Promise<Response>): Promise<T> {
let base = 1_000;
for (let attempt = 0; attempt < 6; attempt++) {
const res = await fn();
if (res.status !== 429) {
if (res.ok) return (await res.json()) as T;
throw new Error(`API error: ${res.status}`);
}
// Prefer Retry-After when the server suggests a value, otherwise
// fall back to exponential backoff with full jitter.
const retryAfter = Number(res.headers.get("Retry-After")) * 1_000;
const capped = Math.min(base * 2 ** attempt, 30_000);
const delay = retryAfter || Math.random() * capped;
await new Promise((r) => setTimeout(r, delay));
}
throw new Error("Retry budget exhausted");
}
The Aleta SDKs implement this pattern automatically. You only need to roll your own if you're calling the API from raw HTTP.
Cap your backoff at 30 seconds and your retry count at 6 attempts. Anything longer is usually better served by a persistent job queue with scheduled re-runs than by a long-lived retry loop tying up a request handler.
Observability tips
- Expose
X-RateLimit-Remainingas a gauge in your own metrics. When it trends below 20% of the limit during normal operation, you're close to outgrowing your tier and should plan either higher limits or better batching. - Tag request spans with the bucket name so a single expensive endpoint can't eat a shared budget invisibly.
- Use idempotency keys on every mutating request — retries under backoff need them to be safe.
What's next
- Read about idempotency — always paired with retries in production.
- Browse the errors reference for the full catalogue of response codes.