Rate Limits & Best Practices
To ensure platform stability for all users, the eAgenda API enforces rate limits on requests.
Rate limits
| Type | Limit |
|---|---|
| Requests per minute | Varies by plan |
| Requests per hour | Varies by plan |
Specific limits depend on your subscribed plan. Contact support for information about your plan’s limits.
Response headers
The API returns informational headers about rate limit usage:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1717524600
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the period |
X-RateLimit-Remaining | Remaining requests in the current period |
X-RateLimit-Reset | Unix timestamp when the counter resets |
When the limit is reached
If you exceed the limit, the API returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
"detail": "Request limit exceeded. Please try again in 30 seconds."
}
Handling rate limiting
Retry with exponential backoff
import requests
import time
def api_request(url, auth, max_retries=3):
for attempt in range(max_retries):
response = requests.get(url, auth=auth)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 30))
wait = retry_after * (2 ** attempt) # exponential backoff
print(f"Rate limit hit. Waiting {wait}s...")
time.sleep(wait)
continue
return response
raise Exception("Max retries exceeded")
JavaScript
async function apiRequest(url, headers, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, { headers });
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "30");
const wait = retryAfter * Math.pow(2, attempt);
console.log(`Rate limit hit. Waiting ${wait}s...`);
await new Promise(resolve => setTimeout(resolve, wait * 1000));
continue;
}
return response;
}
throw new Error("Max retries exceeded");
}
Best practices for integrations
1. Minimize unnecessary requests
- Use filters — Filter by
status,start_date,calendar_keyinstead of fetching everything and filtering locally - Smart pagination — Use an appropriate
page_sizefor your use case (e.g., 100 results per page) - Avoid excessive polling — Use webhooks instead of querying the API every few seconds
2. Cache when possible
Some data changes infrequently and can be cached:
| Resource | Suggested cache time |
|---|---|
Accounts (/accounts/) | 1 hour |
Calendars (/calendars/) | 15 minutes |
Services (/services/) | 15 minutes |
Tags (/tags/) | 30 minutes |
| Available times | 1-2 minutes (maximum) |
| Appointments | No cache (use webhooks) |
3. Handle errors properly
response = requests.get(url, auth=auth)
match response.status_code:
case 200:
data = response.json()
case 400:
print(f"Invalid data: {response.json()}")
case 401:
print("Invalid credentials")
case 403:
print("No permission")
case 404:
print("Resource not found")
case 429:
print("Rate limit - wait and try again")
case _:
print(f"Unexpected error: {response.status_code}")
4. Use timeouts
Always set timeouts to prevent requests from hanging your application:
response = requests.get(url, auth=auth, timeout=30)
const controller = new AbortController();
setTimeout(() => controller.abort(), 30000);
const response = await fetch(url, {
headers,
signal: controller.signal,
});
5. Logging and monitoring
Log requests to facilitate debugging:
- Request URL and method
- Response status code
- Response time
- Rate limit headers
- Errors and retries