Rate limits
A rate limit is a cap on how many calls a program may make in a given time, so one busy script can’t slow almyty down for everyone else. This page lists those caps and how a program should react when it hits one (wait and retry). For example, a script that imports hundreds of records should pause when it gets a “too many requests” answer. If you only use the app, you can skip this page; limits for people chatting with your agents are set on the agent (see Channels).
Default limits
Every client IP address gets a general allowance of 100 requests a minute across the API. The operator sets it with RATE_LIMIT_MAX and RATE_LIMIT_TTL (the window, in seconds). Some routes have a tighter limit of their own:
| Route | Limit |
|---|---|
Sign in (POST /auth/login, POST /auth/token) | 10 per 5 minutes per IP, and 10 per 15 minutes per account |
Register (POST /auth/register) | 10 per hour |
Refresh a session (POST /auth/refresh) | 20 per 5 minutes |
Forgot password (POST /auth/forgot-password) | 5 per hour per IP, and 3 per hour per account |
Start an autonomous run (POST /agents/:id/runs) | 20 per minute |
Invoke or stream an agent (POST /agents/:id/invoke, /stream) | 30 per minute |
Gateway endpoints (/:org/:gateway, the unified root endpoint) | 60 per minute |
| OpenAI- and Anthropic-compatible APIs | 60 per minute per key |
Health checks (/health*) | No limit |
A gateway, and a channel open to visitors, can add limits of its own on top: see below and Channels.
Rate limit headers
Responses carry the standard rate limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | When the window resets |
Retry-After | Seconds until the next request is allowed (only on 429) |
Exceeding limits
When you exceed a limit, the API answers 429 Too Many Requests with a Retry-After header and the usual error body, whose error.code is RATE_LIMITED:
{
"success": false,
"message": "ThrottlerException: Too Many Requests",
"error": {
"code": "RATE_LIMITED",
"message": "ThrottlerException: Too Many Requests",
"statusCode": 429
}
}Handling rate limits
Exponential backoff
The recommended strategy for handling rate limits:
async function requestWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "1");
const delay = retryAfter * 1000 * Math.pow(2, attempt);
console.log(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
return response;
}
throw new Error("Max retries exceeded");
}Python
import time
import requests
def request_with_retry(url, headers, max_retries=3):
for attempt in range(max_retries):
response = requests.get(url, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 1))
delay = retry_after * (2 ** attempt)
print(f"Rate limited. Retrying in {delay}s...")
time.sleep(delay)
continue
return response
raise Exception("Max retries exceeded")Per-gateway and per-tool limits
A gateway can carry its own limits (requests per minute, hour and day), set on the gateway in the app and enforced on its protocol traffic. Individual tools on a gateway can have their own per-minute and per-hour limit as an override, set through Tool scoping:
curl -X PATCH https://api.almyty.com/gateways/{gatewayId}/tools/{gatewayToolId} \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"overrides": { "rateLimit": { "requestsPerMinute": 50 } }
}'Best practices
- Respect
Retry-After: always wait the specified duration before retrying - Implement backoff: use exponential backoff for retry logic
- Cache responses: avoid unnecessary repeated requests
- Batch operations: use bulk endpoints where available (e.g.
POST /gateways/{gatewayId}/tools/bulk) - Monitor headers: track
X-RateLimit-Remainingto slow down before you are refused - Use webhooks: for event-driven workflows, use webhooks instead of polling