The search API (/api/search.json) includes built-in rate limiting to prevent abuse:
- 20 requests per minute per IP address
- 500 character maximum query length
- 20 results maximum per query
All API responses include standard rate limit headers:
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 15
X-RateLimit-Reset: 1700000000
When rate limited, the API returns:
{
"error": "Too many requests",
"message": "Rate limit exceeded. Please try again later.",
"retryAfter": 42
}With headers:
Retry-After: 42
X-RateLimit-Remaining: 0
- In-memory rate limiting
- Works for: Netlify, Vercel serverless functions
- Limitation: Each function instance has its own counter
For multi-server deployments, consider:
-
Redis-based rate limiting
pnpm add ioredis
-
Edge rate limiting (Platform-specific)
- Cloudflare Workers: Use Durable Objects
- Vercel: Use Edge Config or KV
- Netlify: Use Blobs
-
WAF/CDN rate limiting
- Cloudflare: Configure rate limiting rules
- AWS CloudFront: Lambda@Edge
- Fastly: VCL rate limiting
The API limits:
- Query length (500 chars) - prevents expensive embedding generation
- Results count (max 20) - prevents excessive database queries
- Request rate (20/min) - prevents API/database abuse
Local/Xenova Provider (Free)
- Risk: CPU abuse
- Mitigation: Rate limiting sufficient
Gemini Provider (Free tier: 1,500 req/day)
- Risk: API quota exhaustion
- Mitigation: Consider stricter rate limits (5-10 req/min)
OpenAI Provider (Paid)
- Risk: Cost abuse
- Mitigation: Monitor usage, alert on anomalies
- Recommendation: Use OpenAI's own rate limiting
Free tier limits:
- 500 databases
- 9 GB total storage
- Unlimited rows read
- Unlimited rows written
Cost protection: Rate limiting prevents write abuse from malicious indexing attempts.
-
CORS restrictions
headers: { 'Access-Control-Allow-Origin': 'https://yourdomain.com' }
-
Referer checking (weak but simple)
const referer = request.headers.get('referer'); if (!referer?.includes('yourdomain.com')) { return new Response('Forbidden', { status: 403 }); }
-
API Keys (for private docs)
const apiKey = request.headers.get('x-api-key'); if (apiKey !== process.env.SEARCH_API_KEY) { return new Response('Unauthorized', { status: 401 }); }
-
Query caching
// Cache common queries to reduce database load const cacheKey = `search:${query}`; const cached = await cache.get(cacheKey); if (cached) return cached;
Recommended metrics to track:
- Requests per IP
- 429 (rate limited) responses
- Query patterns
- Response times
- Database query counts
- Embedding API usage
Consider setting up alerts for:
- Spike in 429 responses
- Unusually long queries
- High request volume from single IP