← Back to Platform

API Rate Limits

Understand rate limits per tier, response headers, and how to handle 429 Too Many Requests gracefully.

Rate Limits by Tier

Rate limits are enforced per API key on a rolling 24-hour window starting from your first request of the day. Limits reset at midnight UTC.

Explorer

3 / day
3 requests per 24 hours
Free

Starter

25 / day
25 requests per 24 hours
$99/mo

Professional

100 / day
100 requests per 24 hours
$249/mo

Business

Unlimited requests
$499/mo
Important Notes

The /health endpoint is not rate-limited. Bulk endpoints like /schools/bulk-import count as a single request regardless of the number of schools processed. AI endpoints (/agent/chat, /grants/write) have separate quotas that don't count against your daily API limit.

Rate Limit Headers

Every API response includes comprehensive rate limit information in the response headers to help you monitor your usage proactively.

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed per day for your tier100
X-RateLimit-RemainingRequests remaining in the current 24-hour window73
X-RateLimit-ResetUnix timestamp when the rate limit window resets (midnight UTC)1740528000
X-RateLimit-UsedTotal requests used in the current window27
Retry-AfterSeconds until you can retry (only present on 429 responses)3600
Pro Tip

Monitor the X-RateLimit-Remaining header in your applications to implement proactive throttling before hitting the limit. This prevents your requests from failing with 429 errors.

Handling 429 Too Many Requests

When you exceed your rate limit, the API returns a 429 status code with detailed error information and a Retry-After header indicating when you can retry.

429 Response Example

HTTP Response HTTP/1.1 429 Too Many Requests Content-Type: application/json X-RateLimit-Limit: 25 X-RateLimit-Remaining: 0 X-RateLimit-Used: 25 X-RateLimit-Reset: 1740528000 Retry-After: 3600 { "detail": "Rate limit exceeded. 25/25 requests used today. Resets at midnight UTC.", "type": "rate_limit_exceeded", "title": "Too Many Requests", "status": 429, "upgrade_url": "https://thinkkits.com/pricing", "reset_time": "2026-02-25T00:00:00Z" }
Important

Always respect the Retry-After header. Clients that repeatedly ignore rate limits may be temporarily blocked from accessing the API. Implement exponential backoff for resilient error handling.

Code Examples

Shell / curl

bash # Check remaining quota in response headers curl -si "https://api.thinkkits.com/schools/search?q=Springfield" \ -H "X-API-Key: tk_live_abc123" \ | grep -i "x-ratelimit" # Output: # X-RateLimit-Limit: 100 # X-RateLimit-Remaining: 99 # X-RateLimit-Used: 1 # X-RateLimit-Reset: 1740528000

JavaScript (fetch with retry logic)

JavaScript class ThinkKitsAPI { constructor(apiKey) { this.apiKey = apiKey; this.baseURL = 'https://api.thinkkits.com'; } async fetchWithRetry(endpoint, options = {}, maxRetries = 3) { const url = `${this.baseURL}${endpoint}`; const headers = { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json', ...options.headers }; for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, { ...options, headers }); // Log rate limit info const remaining = response.headers.get('X-RateLimit-Remaining'); const limit = response.headers.get('X-RateLimit-Limit'); console.log(`Rate limit: ${remaining}/${limit} remaining`); if (response.status !== 429) { if (!response.ok) { throw new Error(`API error: ${response.status} ${response.statusText}`); } return response; } // Handle rate limiting const retryAfter = parseInt(response.headers.get('Retry-After') || '60'); const resetTime = new Date(response.headers.get('X-RateLimit-Reset') * 1000); console.warn(`Rate limited. Retrying in ${retryAfter}s (resets at ${resetTime.toISOString()})...`); if (attempt < maxRetries - 1) { await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); } } throw new Error('Max retries exceeded for rate-limited request'); } async searchSchools(query, limit = 10) { const params = new URLSearchParams({ q: query, limit }); const response = await this.fetchWithRetry(`/schools/search?${params}`); return response.json(); } } // Usage const api = new ThinkKitsAPI('tk_live_abc123'); try { const schools = await api.searchSchools('Springfield Elementary'); console.log('Found schools:', schools); } catch (error) { console.error('API request failed:', error.message); }

Python (requests with exponential backoff)

Python import time import requests from datetime import datetime class ThinkKitsAPI: def __init__(self, api_key): self.api_key = api_key self.base_url = "https://api.thinkkits.com" self.session = requests.Session() self.session.headers.update({"X-API-Key": api_key}) def request_with_retry(self, method, endpoint, **kwargs): url = f"{self.base_url}{endpoint}" max_retries = kwargs.pop("max_retries", 3) base_delay = kwargs.pop("base_delay", 1) for attempt in range(max_retries): response = self.session.request(method, url, **kwargs) # Log rate limit info remaining = response.headers.get("X-RateLimit-Remaining", "?") limit = response.headers.get("X-RateLimit-Limit", "?") print(f"Rate limit: {remaining}/{limit} remaining") if response.status_code != 429: response.raise_for_status() return response # Handle rate limiting with exponential backoff retry_after = int(response.headers.get("Retry-After", 60)) reset_time = datetime.fromtimestamp( int(response.headers.get("X-RateLimit-Reset", 0)) ) # Use exponential backoff, but respect Retry-After header delay = max(retry_after, base_delay * (2 ** attempt)) print(f"Rate limited. Waiting {delay}s (resets at {reset_time})...") if attempt < max_retries - 1: time.sleep(delay) raise requests.exceptions.RetryError(f"Max retries ({max_retries}) exceeded") def search_schools(self, query, limit=10): params = {"q": query, "limit": limit} response = self.request_with_retry("GET", "/schools/search", params=params) return response.json() def get_usage_stats(self): response = self.request_with_retry("GET", "/marketplace/usage") return response.json() # Usage api = ThinkKitsAPI("tk_live_abc123") try: schools = api.search_schools("Springfield Elementary") print(f"Found {len(schools['results'])} schools") # Check usage stats usage = api.get_usage_stats() print(f"Daily usage: {usage['daily_used']}/{usage['daily_limit']}") except requests.exceptions.RequestException as e: print(f"API request failed: {e}")

Node.js (axios with interceptors)

Node.js const axios = require('axios'); class ThinkKitsAPI { constructor(apiKey) { this.client = axios.create({ baseURL: 'https://api.thinkkits.com', headers: { 'X-API-Key': apiKey }, timeout: 30000 }); // Add response interceptor for rate limit logging this.client.interceptors.response.use( (response) => { const { headers } = response; const remaining = headers['x-ratelimit-remaining']; const limit = headers['x-ratelimit-limit']; if (remaining && limit) { console.log(`Rate limit: ${remaining}/${limit} remaining`); } return response; }, async (error) => { if (error.response?.status === 429) { const retryAfter = parseInt(error.response.headers['retry-after']) || 60; console.warn(`Rate limited. Auto-retrying in ${retryAfter}s...`); await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); // Retry the original request return this.client.request(error.config); } throw error; } ); } async searchSchools(query, limit = 10) { const response = await this.client.get('/schools/search', { params: { q: query, limit } }); return response.data; } } // Usage (async () => { const api = new ThinkKitsAPI('tk_live_abc123'); try { const schools = await api.searchSchools('Springfield Elementary'); console.log('Found schools:', schools.results.length); } catch (error) { console.error('API error:', error.message); } })();

Best Practices

  1. Monitor proactively. Check X-RateLimit-Remaining headers in your application. Implement client-side throttling when approaching limits.
  2. Cache aggressively. School profiles and funding data change infrequently. Cache responses for 24 hours to reduce API calls and improve performance.
  3. Use bulk endpoints. The /schools/bulk-import endpoint lets you process up to 100 schools in one request (Business tier), dramatically reducing your rate limit usage.
  4. Implement exponential backoff. When you receive a 429 error, wait the Retry-After duration. For subsequent 429s, double your wait time to avoid overwhelming the API.
  5. Batch requests efficiently. Group related API calls together and implement request queuing to spread load across your rate limit window.
  6. Use webhooks instead of polling. Subscribe to customer webhooks to receive real-time notifications instead of repeatedly polling for changes.
  7. Upgrade when needed. If you consistently hit limits, upgrading tiers provides significant quota increases: Starter to Professional gives 4x capacity (25→100/day).
Enterprise Support

Business tier customers get priority API support and can request custom rate limits for high-volume integrations. Contact api@thinkkits.com for assistance.

Upgrading Your Tier

Need higher limits? Upgrade your plan through the billing portal, or use the API to check usage and upgrade programmatically.

API Usage # Check current usage and limits curl "https://api.thinkkits.com/marketplace/usage" \ -H "X-API-Key: tk_live_abc123" # Response shows detailed usage breakdown { "tier": "STARTER", "daily_limit": 25, "daily_used": 18, "daily_remaining": 7, "reset_time": "2026-02-25T00:00:00Z", "upgrade_url": "https://thinkkits.com/pricing" }

You can also monitor usage patterns and set up alerts in your application:

JavaScript Monitoring class RateLimitMonitor { constructor(api, thresholds = { warning: 0.8, critical: 0.95 }) { this.api = api; this.thresholds = thresholds; } checkUsage(headers) { const limit = parseInt(headers.get('X-RateLimit-Limit')); const remaining = parseInt(headers.get('X-RateLimit-Remaining')); const used = limit - remaining; const usagePercent = used / limit; if (usagePercent >= this.thresholds.critical) { console.error(`🚨 Critical: ${Math.round(usagePercent * 100)}% of daily limit used`); // Trigger upgrade flow or throttle requests } else if (usagePercent >= this.thresholds.warning) { console.warn(`⚠️ Warning: ${Math.round(usagePercent * 100)}% of daily limit used`); // Suggest caching or optimization } return { used, limit, remaining, usagePercent }; } }

Need help optimizing your API usage? Contact our API team for personalized guidance.

← Full API Documentation · API Changelog · Back to Platform

Was this article helpful?

← Back to Help Center