Rate Limits
Understanding API rate limits and throttling
Overview
The SchoolScout API enforces rate limits to ensure fair usage and platform stability. Limits are applied per organization on a sliding 60-second window.
Limits by Plan
| Plan | Requests/min | Max results/request | Concurrent scouting jobs |
|---|---|---|---|
| Free | 300 | 50 | 1 |
| Starter | 600 | 100 | 2 |
| Growth | 1,200 | 200 | 5 |
| Scale | 3,000 | 500 | 10 |
| Pro | 5,000 | 1,000 | 25 |
| Enterprise | 5,000 | 1,000 | 25 |
API key overrides: Individual API keys can have custom rate limits configured in Settings > API Keys.
Response Headers
Every 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 (seconds) when the window resets |
When rate limited, a Retry-After header is also included.
Rate Limited Responses
When you exceed the rate limit, the API returns HTTP 429:
{
"error": "Rate Limited",
"message": "Too many requests. Please slow down.",
"code": "RATE_LIMITED"
}Handling Rate Limits
- Check headers proactively -- monitor
X-RateLimit-Remainingto throttle before hitting the limit - Use
Retry-After-- wait the specified number of seconds before retrying - Implement exponential backoff -- if you receive a 429, wait progressively longer between retries
Example retry logic:
import time
import requests
def api_request(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", 2 ** attempt))
time.sleep(retry_after)
continue
return response
raise Exception("Rate limit exceeded after retries")Concurrency Limits
Scouting (analysis) jobs have a separate concurrency limit per plan. If you exceed the limit, the scouting request returns HTTP 429 with a message indicating the concurrent job cap.
Best Practices
- Batch where possible -- use bulk unlock (
POST /unlockwithentitiesarray) instead of individual calls - Cache responses -- entities don't change frequently; cache district/school data for reasonable periods
- Use filters -- narrow searches with
state,status, and other filters to reduce result sizes - Paginate efficiently -- request only the
limityou need; don't over-fetch