Rate Limits & Quotas
Chronary enforces two types of limits to ensure fair usage and platform stability: per-key rate limits and monthly quotas.
Per-key rate limit
Section titled “Per-key rate limit”The per-key rate limit depends on your plan:
| Plan | Rate limit | |------|-----------| | Free | 10 requests/second | | Pro | 50 requests/second | | Custom | Negotiated |
Requests that exceed this limit receive a 429 response with a Retry-After header indicating how many seconds to wait.
{ "error": { "type": "rate_limit_error", "message": "Rate limit exceeded. Retry after 1 second.", "param": null, "request_id": "req_abc123" }}The Retry-After response header contains the number of seconds to wait before retrying.
Monthly quotas by plan
Section titled “Monthly quotas by plan”Monthly quotas reset automatically at the start of each billing cycle. Exceeding a quota returns a 429 response with "type": "quota_exceeded".
| Metric | Free | Pro | Custom |
|---|---|---|---|
| Agents | 3 | 50 | Custom |
| Calendars | 10 | 250 | Custom |
| iCal Subscriptions | 5 | 100 | Custom |
| Webhook Endpoints | 3 | 25 | Custom |
| Per-Agent API Keys | Not available | 50 | Custom |
| Events created | 2,500/mo | 125,000/mo | Custom |
| API Calls | 50,000/mo | 1,000,000/mo | Custom |
| Webhook Deliveries | 5,000/mo | 250,000/mo | Custom |
| Availability Queries | 10,000/mo | 1,000,000/mo | Custom |
| Scheduling Proposals | Not available | Unlimited | Custom |
What counts against which budget
Section titled “What counts against which budget”v1 endpoints share most existing budgets rather than introducing new per-endpoint limits:
| Operation | Budget consumed |
|-----------|-----------------|
| POST /v1/calendars/:cal_id/events with status: "hold" | Events created (shared with regular events) |
| PUT /v1/events/:id/confirm — promoting a hold | API Calls only (no extra event charge) |
| PUT /v1/events/:id/release — releasing a hold | API Calls only |
| GET /v1/calendars/:id/context | API Calls |
| PUT / GET / DELETE /v1/calendars/:id/availability-rules | API Calls |
| POST /v1/scheduling/proposals | Scheduling Proposals + API Calls |
| POST /v1/scheduling/proposals/:id/respond | API Calls |
| POST /v1/scheduling/proposals/:id/resolve | Events created (resolution creates a confirmed event) + API Calls |
Every request also consumes one API Call regardless of endpoint.
Cross-agent availability limits
Section titled “Cross-agent availability limits”When querying availability across multiple agents (e.g., finding a common free slot), additional limits apply:
| Parameter | Free | Pro | Custom | |-----------|------|-----|--------| | Max agents per query | 5 | 20 | Custom | | Max date range | 30 days | 90 days | Custom |
Checking your usage
Section titled “Checking your usage”Call GET /v1/usage to see your current consumption against your plan limits:
curl https://api.chronary.ai/v1/usage \ -H "Authorization: Bearer chr_sk_your_key_here"Example response:
{ "plan": "free", "period_start": "2026-04-01T00:00:00.000Z", "period_end": "2026-04-30T23:59:59.000Z", "agents": { "used": 3, "limit": 3 }, "calendars": { "used": 7, "limit": 10 }, "events": { "used": 1842, "limit": 2500 }, "api_calls": { "used": 12450, "limit": 50000 }, "webhooks": { "used": 956, "limit": 5000 }, "availability_queries": { "used": 304, "limit": 10000 }, "ical_subscriptions": { "used": 2, "limit": 5 }, "proposals": { "used": 0, "limit": 0 }}Retry strategy
Section titled “Retry strategy”When you receive a 429 response, implement exponential backoff with jitter to avoid thundering-herd problems.
async function fetchWithRetry(url, options, maxRetries = 5) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, options);
if (response.status !== 429) { return response; }
// Use Retry-After header if present, otherwise calculate backoff const retryAfter = response.headers.get('Retry-After'); const baseDelay = retryAfter ? parseFloat(retryAfter) * 1000 : Math.min(1000 * Math.pow(2, attempt), 30000);
// Add jitter: random delay between 0% and 100% of base delay const jitter = Math.random() * baseDelay; const delay = baseDelay + jitter;
console.log(`Rate limited. Retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${maxRetries})`); await new Promise((resolve) => setTimeout(resolve, delay)); }
throw new Error('Max retries exceeded');}from chronary import Chronary, RateLimitError
# The SDK handles retries automatically with exponential backoff.# Configure max_retries to control retry behavior:client = Chronary(max_retries=5)
# All methods automatically retry on 429 and 5xx responses.# The Retry-After header is respected when present.agents = client.agents.list()
# You can also catch rate limit errors explicitly:try: agent = client.agents.get("agt_a1b2c3d4")except RateLimitError as e: print(f"Rate limited after all retries: {e.message}")Webhook delivery retries
Section titled “Webhook delivery retries”When a webhook delivery fails (non-2xx response or timeout), Chronary retries with exponential backoff. The number of retry attempts depends on your plan:
| Plan | Retry attempts | Schedule (after each failure) | |------|----------------|-------------------------------| | Free | 4 | +1m → +5m → +30m | | Pro | 8 | +1m → +5m → +30m → +1h → +2h → +6h → +12h | | Custom | Negotiated | — |
Retries are scheduled at the plan in effect when the delivery was first enqueued; mid-flight plan downgrades preserve the longer schedule.
Monthly reset
Section titled “Monthly reset”Quotas reset automatically at the start of each billing period — no manual action is required. You can check your current period and usage at any time with GET /v1/usage.