Rate Limits & Quotas
## Overview
SearchJet enforces rate limits and quotas to ensure fair usage and platform stability. Understanding these limits helps you design resilient applications and avoid service disruptions.
---
## Rate Limits by Plan
| Plan | Requests / Minute | Requests / Day | Index Documents / Day | Search Queries / Day |
|---|---|---|---|---|
| **Free** | 60 | 10,000 | 1,000 | 5,000 |
| **Pro** | 300 | 100,000 | 50,000 | 500,000 |
| **Growth** | 600 | 500,000 | 500,000 | 5,000,000 |
| **Enterprise** | Custom | Custom | Custom | Custom |
Limits are enforced per API key. Each plan tier includes higher throughput and larger daily quotas.
---
## Rate Limit Headers
Every API response includes headers indicating your current rate limit status:
| Header | Description |
|---|---|
| `X-RateLimit-Limit` | Maximum requests allowed per minute |
| `X-RateLimit-Remaining` | Requests remaining in the current window |
| `X-RateLimit-Reset` | Unix timestamp when the rate limit window resets |
### Example Headers
```
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 247
X-RateLimit-Reset: 1720647600
```
Monitor these headers in your application to proactively manage your request rate.
---
## Handling 429 Responses
When you exceed the rate limit, SearchJet returns a `429 Too Many Requests` response.
### Response Body
```json
{
"error": "rate_limit_exceeded",
"message": "You have exceeded the rate limit of 300 requests per minute",
"retry_after": 12,
"limit": 300,
"remaining": 0,
"reset_at": "2025-07-10T14:31:00Z"
}
```
### Retry-After Header
The response includes a `Retry-After` header specifying how many seconds to wait before retrying:
```
Retry-After: 12
```
### Exponential Backoff Strategy
Implement exponential backoff with jitter for automatic retries:
```python
import time
import random
import requests
def search_with_retry(query, max_retries=3):
for attempt in range(max_retries):
response = requests.get(
"https://app.searchjetengine.com/api/v1/search",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"q": query}
)
if response.status_code != 429:
return response.json()
retry_after = int(response.headers.get("Retry-After", 1))
jitter = random.uniform(0, 1)
wait_time = retry_after + (2 ** attempt) + jitter
print(f"Rate limited. Retrying in {wait_time:.1f}s...")
time.sleep(wait_time)
raise Exception("Max retries exceeded")
```
---
## Usage Reporting
Report usage from your application to SearchJet for accurate quota tracking and analytics.
### Report Usage
```
POST /api/v1/sites/{siteId}/usage
```
#### Request Body
```json
{
"searches": 150,
"index_operations": 500,
"timestamp": "2025-07-10T14:00:00Z"
}
```
| Field | Type | Description |
|---|---|---|
| `searches` | integer | Number of search queries performed |
| `index_operations` | integer | Number of index add/update/delete operations |
| `timestamp` | string | ISO 8601 timestamp for the usage period |
#### Response
```json
{
"recorded": true,
"period": "2025-07-10",
"total_searches_today": 1250,
"total_index_ops_today": 8750
}
```
### Get Usage Stats
```
GET /api/v1/sites/{siteId}/usage
```
#### Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| `period` | string | `today` | `today`, `this_week`, `this_month`, or `all` |
#### Response
```json
{
"siteId": "site_abc123",
"plan": "pro",
"period": "today",
"searches": {
"used": 1250,
"limit": 500000,
"remaining": 498750,
"percent_used": 0.25
},
"index_operations": {
"used": 8750,
"limit": 50000,
"remaining": 41250,
"percent_used": 17.5
},
"requests": {
"used": 4520,
"limit": 100000,
"remaining": 95480,
"percent_used": 4.52
},
"remaining_quota": true
}
```
The `remaining_quota` field provides a quick boolean check: `true` if you have quota available, `false` if you are at or near your limit.
---
## What Happens When You Exceed Limits
| Limit Type | Behavior |
|---|---|
| **Rate limit (per minute)** | 429 response with `Retry-After` header |
| **Daily search quota** | 403 response — search queries rejected until next day |
| **Daily index quota** | 403 response — index operations rejected until next day |
| **Daily request quota** | 403 response — all requests rejected until next day |
Quotas reset at midnight UTC. Rate limits use a sliding window.
---
## Best Practices for Staying Within Limits
- **Monitor headers.** Check `X-RateLimit-Remaining` on every response and slow down proactively when remaining drops below 20%.
- **Cache search results.** Cache popular queries for 60-300 seconds to reduce redundant API calls.
- **Batch index operations.** Use the bulk endpoints to send multiple documents in one request.
- **Implement backoff.** Use exponential backoff with jitter on 429 responses.
- **Track daily usage.** Poll the usage endpoint or maintain your own counters to avoid hitting daily quotas.
- **Use server-side search.** Never expose API keys in client-side code, which can lead to uncontrolled usage spikes.
- **Set alerts.** Configure alerts at 75% and 90% of daily quotas to get advance warning.
---
## Upgrading Your Plan
If you consistently approach your quota limits, consider upgrading:
1. Navigate to your SearchJet dashboard
2. Go to **Settings > Billing**
3. Select a plan that meets your usage needs
4. Upgrade takes effect immediately — rate limits and quotas update in real time
Enterprise customers can contact support for custom limits and dedicated infrastructure.
---
## Code Examples
### Check Remaining Quota
```python
import requests
def check_quota(site_id):
response = requests.get(
f"https://app.searchjetengine.com/api/v1/sites/{site_id}/usage",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"period": "today"}
)
data = response.json()
if data["searches"]["percent_used"] > 80:
print(f"Warning: {data['searches']['percent_used']}% of search quota used")
return data["remaining_quota"]
remaining = check_quota("site_abc123")
print(f"Quota remaining: {remaining}")
```
### Handle Rate Limits in JavaScript
```javascript
async function searchWithRateLimitHandling(query) {
const response = await fetch(
`https://app.searchjetengine.com/api/v1/search?q=${encodeURIComponent(query)}`,
{ headers: { Authorization: `Bearer ${process.env.SEARCHJET_API_KEY}` } }
);
const remaining = parseInt(response.headers.get("X-RateLimit-Remaining"));
if (remaining < 50) {
console.warn(`Low rate limit remaining: ${remaining}`);
}
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After")) || 5;
console.log(`Rate limited. Retrying in ${retryAfter}s...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return searchWithRateLimitHandling(query);
}
return response.json();
}
```
### Monitor Quota with cURL
```bash
curl -X GET "https://app.searchjetengine.com/api/v1/sites/site_abc123/usage?period=today" \
-H "Authorization: Bearer YOUR_API_KEY" | jq '.searches'
```
---
## Related Pages
- [Search API](/docs/search-api) — Query your indexed data
- [Error Handling](/docs/error-handling) — Comprehensive error code reference
- [API Key Setup](/docs/api-key-setup) — Create and manage keys