Rate Limits & Quotas
Understand and work within PlatformXe rate limits and plan quotas.
PlatformXe enforces per-route rate limits and per-plan monthly quotas. This guide covers how to work within these limits effectively.
Rate limit headers
When you approach or hit a rate limit, the response includes:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
Retry-After | Seconds to wait before retrying (on 429 responses) |
Rate-limited response
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 45 seconds."
}
}
Per-route limits
| Route class | Limit |
|---|---|
Permission checks (check, resolve, check-batch) | 5,000/hr |
| Permission management (CRUD) | 500/hr |
| Audit queries and export | 100/hr |
| All other routes | 1,000/hr per API key |
Strategies for staying within limits
Use batch endpoints
Instead of making 50 individual permission checks, use check-batch for up to 100 checks in a single request:
// Instead of this:
for (const action of actions) {
await px.permissions.check({ adminId, path, action });
}
// Do this:
await px.permissions.checkBatch({
adminId,
checks: actions.map(action => ({ path, action })),
});
Cache resolved permissions
Use resolve to fetch a user's full capability set, then check locally:
const resolved = await px.permissions.resolve(userId);
const capabilities = new Set(resolved.data.capabilities);
// Check locally without API calls
function hasCapability(path: string, action: string): boolean {
return capabilities.has(`${path}:${action}`);
}
Resolved permissions are cached server-side for 60 seconds. Client-side caching on top of this can eliminate the majority of permission check API calls.
Implement backoff on 429s
async function withBackoff<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err: any) {
if (err.code === 'RATE_LIMITED' && attempt < maxRetries) {
const retryAfter = err.retryAfter || Math.pow(2, attempt) * 1000;
await new Promise(r => setTimeout(r, retryAfter));
continue;
}
throw err;
}
}
throw new Error('Max retries exceeded');
}
Monthly quotas by plan
| Service | Free | Basic | Pro | Enterprise |
|---|---|---|---|---|
| Emails | 500 | 10,000 | 50,000 | Custom |
| SMS | 50 | 1,000 | 5,000 | Custom |
| Permission checks | 10,000 | 100,000 | 500,000 | Custom |
| Storage | 100 MB | 1 GB | 10 GB | Custom |
When a quota is exhausted, requests return QUOTA_EXCEEDED. Subscribe to billing.usage.threshold events to get warnings at 80% and 95%.
Rate limits are per API key, not per user. If multiple services share an API key, their requests count toward the same limit. Use separate API keys for different services when possible.