Guides

Rate Limits

The Calibr API enforces rate limits to ensure fair usage and protect service stability. This guide covers the default limits, response headers, and best practices for handling throttling.

Default Limits

Rate limits are applied per API key. The default limit is:

1,000 requests per minute per API key

This limit applies to all endpoints. Batch scoring requests count as a single request regardless of the number of applicants in the batch (up to 1,000 applicants per batch).

Response Headers

Every API response includes rate limit headers so you can monitor your usage programmatically:

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed in the current window1000
X-RateLimit-RemainingRequests remaining in the current window847
bash
HTTP/1.1 200 OK Content-Type: application/json X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 847 { "score": 687, "pd": 0.034, "risk_grade": "B" }

429 Too Many Requests

When you exceed the rate limit, the API returns a 429 status code with a JSON body indicating when you can retry:

bash
HTTP/1.1 429 Too Many Requests Content-Type: application/json X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 0 { "error": "rate_limited", "message": "Rate limit exceeded. Please retry after the specified delay.", "retry_after_ms": 12400 }

The retry_after_ms field tells you how many milliseconds to wait before sending the next request. This value corresponds to the time remaining until the current rate limit window resets.

Handling Rate Limits in Code

Implement exponential backoff with jitter to handle rate limit responses gracefully:

import time import random import requests def score_with_retry(payload, api_key, max_retries=3): url = "https://api.calibr.dev/api/v1/score" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } for attempt in range(max_retries): response = requests.post(url, json=payload, headers=headers) if response.status_code == 200: return response.json() if response.status_code == 429: retry_after_ms = response.json().get("retry_after_ms", 1000) # Add jitter: 0-25% random additional delay jitter = random.uniform(0, retry_after_ms * 0.25) wait_seconds = (retry_after_ms + jitter) / 1000 print(f"Rate limited. Retrying in {wait_seconds:.1f}s...") time.sleep(wait_seconds) continue response.raise_for_status() raise Exception("Max retries exceeded")

Best Practices

  • Use the batch endpoint for bulk scoring. Instead of sending 500 individual requests, use POST /api/v1/score/batch with up to 1,000 applicants. This counts as a single request against your rate limit.
  • Implement exponential backoff. When you receive a 429, wait for the retry_after_ms duration plus random jitter before retrying. Do not retry immediately in a tight loop.
  • Monitor remaining quota. Check the X-RateLimit-Remaining header proactively. If you are approaching the limit, slow down your request rate before hitting 429.
  • Use separate keys for separate systems. If you have multiple integrations (e.g., a loan origination system and a batch processing pipeline), use separate API keys so their rate limits are independent.
  • Contact support for higher limits. If your use case requires more than 1,000 requests per minute, reach out to the Calibr team to discuss enterprise rate limit tiers.

Batch Scoring Example

The batch endpoint is the most efficient way to score many applicants:

curl -X POST https://api.calibr.dev/api/v1/score/batch \ -H "Authorization: Bearer sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "scorecard_id": "sc_01HXYZ", "applicants": [ { "id": "app_001", "monthly_income": 5500, "employment_length": 36, "debt_to_income": 0.28 }, { "id": "app_002", "monthly_income": 3200, "employment_length": 8, "debt_to_income": 0.45 } ] }'

Response:

json
{ "results": [ { "id": "app_001", "score": 687, "pd": 0.034, "risk_grade": "B" }, { "id": "app_002", "score": 528, "pd": 0.142, "risk_grade": "D" } ], "scorecard_id": "sc_01HXYZ", "model_version": "v3", "scored_at": "2026-03-26T14:32:01Z" }