Rate Limits¶
Mayo ASPM enforces rate limits to ensure fair usage and platform stability. Limits vary by plan tier and endpoint type.
Limits by tier¶
| Tier | Requests per minute | Requests per hour | Concurrent scans |
|---|---|---|---|
| Free | 60 | 1,000 | 1 |
| Pro | 300 | 10,000 | 5 |
| Enterprise | 1,000 | 50,000 | 20 |
Endpoint-specific limits¶
Some endpoints have additional limits beyond the per-tier rate:
| Endpoint | Limit | Applies to |
|---|---|---|
POST /api/scans |
10 per hour (Free), 50 per hour (Pro), 200 per hour (Enterprise) | Scan triggers |
POST /api/tickets/generate |
20 per hour | Ticket generation |
POST /api/auth/login |
10 per minute | Login attempts |
POST /api/policies/evaluate |
120 per minute | Playground evaluations |
Rate limit headers¶
Every API response includes rate limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests allowed in the current window |
X-RateLimit-Remaining |
Requests remaining in the current window |
X-RateLimit-Reset |
Unix timestamp when the window resets |
When you're rate limited¶
If you exceed the limit, the API returns a 429 Too Many Requests response:
HTTP/1.1 429 Too Many Requests
Retry-After: 32
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1713168060
Content-Type: application/json
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Retry after 32 seconds.",
"retry_after": 32
}
}
Handling rate limits¶
Check headers proactively¶
import requests
import time
def api_call(url, headers):
response = requests.get(url, headers=headers)
remaining = int(response.headers.get("X-RateLimit-Remaining", 0))
if remaining < 10:
reset_time = int(response.headers.get("X-RateLimit-Reset", 0))
wait = max(reset_time - time.time(), 0)
print(f"Rate limit nearly exhausted. Resets in {wait:.0f}s")
return response
Retry with exponential backoff¶
import requests
import time
def api_call_with_retry(url, headers, max_retries=3):
for attempt in range(max_retries):
response = requests.get(url, headers=headers)
if response.status_code != 429:
return response
retry_after = int(response.headers.get("Retry-After", 60))
wait = retry_after * (2 ** attempt)
print(f"Rate limited. Retrying in {wait}s (attempt {attempt + 1})")
time.sleep(wait)
raise Exception("Max retries exceeded")
Batch operations¶
Instead of making many individual requests, use bulk endpoints where available:
# Instead of: GET /api/findings/{id} x 100 times
# Use: GET /api/findings?id=f_1,f_2,f_3,...
curl "https://mayoaspm.com/api/findings?id=f_abc,f_def,f_ghi" \
-H "Authorization: Bearer mayo_ak_..."
Rate limits by authentication type¶
| Auth type | Rate limit source |
|---|---|
| API Key | Key's organization tier |
| JWT | User's organization tier |
Info
All API keys in the same organization share the organization's rate limit pool. If one key uses up the limit, all keys are affected.
Monitoring usage¶
View your API usage from Settings > Integrations > API Keys:
- Requests in the last hour / day / month
- Per-key usage breakdown
- Rate limit hit count
Requesting higher limits¶
If your use case requires higher rate limits:
- Pro tier users — upgrade to Enterprise
- Enterprise tier users — contact support at support@mayoaspm.com for custom limits
Best practices¶
- Cache responses — avoid re-fetching data that hasn't changed.
- Use webhooks — instead of polling for scan completion, use the scan webhook callback.
- Batch requests — use filters and pagination instead of fetching one item at a time.
- Respect Retry-After — always wait the indicated time before retrying.
- Monitor your usage — set up alerts when approaching limits.
Next steps¶
- Error codes — understand error responses
- Pricing — compare tier features
- API overview — API fundamentals