Version: 2.0
Last Updated: 2026-01-25
Protocol Guide implements a multi-tier rate limiting system that protects the API from abuse while providing appropriate access levels for different user tiers.
Applied to public endpoints without authentication.
| Tier | Requests | Window | Use Case |
|---|---|---|---|
| Standard | 100 | 1 minute | General public endpoints |
| Search | 30 | 1 minute | Search endpoints |
| AI | 10 | 1 minute | Summarize/AI endpoints |
| Auth | 5 | 1 minute | Login/signup (brute force prevention) |
Applied to authenticated users based on subscription tier.
| Subscription Tier | Requests/Minute | Use Case |
|---|---|---|
| Free | 5 | Burst protection |
| Pro | 20 | Standard usage |
| Enterprise/Unlimited | 60 | Heavy usage |
| Subscription Tier | Queries/Day | Reset Time |
|---|---|---|
| Free | 10 | Midnight UTC |
| Pro | 100 | Midnight UTC |
| Enterprise | Unlimited | N/A |
| Endpoint Category | Max Requests | Window | Notes |
|---|---|---|---|
search.* |
10 | 15 min | Prevents scraping |
system.health |
100 | 1 min | Monitoring |
auth.me |
10 | 15 min | Enumeration prevention |
counties.list |
30 | 1 min | Reference data |
| Endpoint Category | Limit Type | Notes |
|---|---|---|
query.submit |
Daily limit | Core AI queries |
user.* |
Minute burst | Profile operations |
subscription.* |
Minute burst | Payment operations |
| Endpoint Category | Max Requests | Window | Notes |
|---|---|---|---|
admin.* |
50 | 1 min | Admin operations |
agencyAdmin.* |
30 | 1 min | Agency management |
All responses include standard rate limit headers:
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit |
Max requests in window | 100 |
X-RateLimit-Remaining |
Requests remaining | 85 |
X-RateLimit-Reset |
Unix timestamp of reset | 1706205600 |
Retry-After |
Seconds until retry (when limited) | 45 |
| Header | Description | Example |
|---|---|---|
X-RateLimit-Daily-Limit |
Daily query limit | 100 or unlimited |
X-RateLimit-Daily-Remaining |
Queries remaining today | 75 or unlimited |
X-RateLimit-Daily-Reset |
Unix timestamp of midnight UTC | 1706227200 |
When rate limited, the API returns:
{
"error": "RATE_LIMITED",
"reason": "minute_limit" | "daily_limit",
"message": "Too many requests. Please wait 45 seconds.",
"retryAfter": 45,
"tier": "free",
"daily": {
"limit": 10,
"used": 10,
"remaining": 0
}
}HTTP Status: 429 Too Many Requests
In production, rate limits are stored in Redis for distributed coordination:
rate_limit:<user_id>:minute → { count, resetTime }
rate_limit:<user_id>:daily → { count, resetTime }
rate_limit:ip:<ip_address> → { count, resetTime }
When Redis is unavailable, an in-memory Map is used:
⚠️ Not suitable for multi-instance deployments- Automatic cleanup of expired entries every 60 seconds
The rate limiter reads user tier from the authenticated context:
// From request context
const tier = ctx.user?.tier || 'free';
const limits = TIER_LIMITS[tier];
// Apply appropriate limits
if (usage.minute >= limits.minuteLimit) {
throw new TRPCError({ code: 'TOO_MANY_REQUESTS' });
}Always check X-RateLimit-Remaining before making requests:
const remaining = parseInt(response.headers['x-ratelimit-remaining']);
if (remaining < 5) {
// Slow down request rate
}When receiving 429 responses:
async function requestWithBackoff(fn: () => Promise<T>): Promise<T> {
for (let attempt = 0; attempt < 5; attempt++) {
try {
return await fn();
} catch (error) {
if (error.code === 'TOO_MANY_REQUESTS') {
const retryAfter = error.data?.retryAfter || Math.pow(2, attempt);
await sleep(retryAfter * 1000);
continue;
}
throw error;
}
}
}Reduce API calls by caching:
- County lists (1 hour)
- Search results (5 minutes)
- Protocol stats (1 hour)
For bulk operations, use batch endpoints when available instead of multiple individual requests.
Rate limit events are logged to Sentry with:
- User ID (if authenticated)
- IP address (hashed for privacy)
- Endpoint path
- Current usage vs. limit
| Metric | Description |
|---|---|
rate_limit.exceeded |
Count of rate limit violations |
rate_limit.near_limit |
Count of requests at >80% limit |
rate_limit.tier_distribution |
Breakdown by user tier |
Users hitting rate limits can upgrade their tier:
| Upgrade From | Upgrade To | Benefit |
|---|---|---|
| Free | Pro | 100 queries/day, 20/min burst |
| Pro | Enterprise | Unlimited queries, 60/min burst |
Upgrade prompts are shown when:
- Daily limit reached
- Consistently hitting minute limits
- Accessing Pro-only features