Skip to main content

Overview

The High IQ API enforces rate limiting to protect against abuse and ensure fair usage across all clients. Rate limits are applied per-IP address, with different tiers for different operation types. Rate limiting is disabled in development mode and on localhost to allow unrestricted local testing.

Rate Limit Tiers

The API provides six pre-configured rate limit tiers, each designed for a specific class of operations:
TierLimitWindowUse Case
Lenient300 requests1 minuteRead operations (strain data, search)
Moderate60 requests1 minuteStandard API operations
Strict10 requests1 minuteAI/expensive operations (recommendations)
Report5 requests1 hourReport generation
Parse20 requests1 hourReceipt/order parsing
Scanner10 requests10 minutesLabel scanning
Additionally, there is a strain submission rate limiter (5 per hour per IP) for public website strain submissions.

Tier Selection by Endpoint

Read-heavy endpoints that serve cached data:
  • GET /strains - Strain catalog
  • GET /strains/search - Strain search
  • GET /strains/popular - Popular strains
  • GET /strains/slug/:slug - Strain detail
  • GET /strains/slug/:slug/complete - Full strain data
  • GET /strains/by-terpene/:terpene - Terpene filtering
  • GET /strains/by-type/:type - Type filtering

Rate Limit Headers

Every response includes rate limit headers so clients can track their current usage:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 247
X-RateLimit-Reset: 2026-02-16T12:01:00.000Z
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetISO 8601 timestamp when the window resets

429 Too Many Requests

When a rate limit is exceeded, the API returns a 429 status code with a Retry-After header indicating how many seconds to wait:
curl -i "https://tiwih-api.vercel.app/api/v1/strains/recommendations"
HTTP/2 429
Content-Type: application/json
Retry-After: 45
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2026-02-16T12:01:00.000Z
{
  "error": "Rate limit exceeded. Maximum 10 requests per minute.",
  "retryAfter": 45
}

Handling 429 Responses

Implement exponential backoff or respect the Retry-After header:
async function fetchWithRetry(url: string, maxRetries = 3): Promise<Response> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url);

    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After');
      const waitMs = retryAfter
        ? parseInt(retryAfter, 10) * 1000
        : Math.pow(2, attempt) * 1000; // Exponential backoff fallback

      console.warn(`Rate limited. Retrying in ${waitMs}ms...`);
      await new Promise(resolve => setTimeout(resolve, waitMs));
      continue;
    }

    return response;
  }

  throw new Error('Max retries exceeded');
}

Rate Limit Key Generation

Rate limits are keyed based on request identity, falling back through several sources:
Client TypeRate Limit Key
Identified requestUser ID from context (if available)
Standard requestX-Forwarded-For IP address
No identifierFallback to "anonymous" (shared pool)
// Identified: user gets their own rate limit bucket (if user context is set)
user_abc123 -> 247/300 remaining

// Standard: rate limited by IP address
203.0.113.42 -> 180/300 remaining

API Key Rate Limiting

For external integrations using API keys, a configurable per-key rate limiter is available:
const apiKeyLimiter = createApiKeyRateLimiter({
  'key-partner-a': { windowMs: 60_000, maxRequests: 500 },
  'key-partner-b': { windowMs: 60_000, maxRequests: 100 },
});
This allows different rate limits for different API consumers. Unconfigured keys fall back to a default of 100 requests per minute.

Development Mode

Rate limiting is completely disabled when any of these conditions are true:
  • NODE_ENV=development
  • Request host contains localhost
  • Request host contains 127.0.0.1
This allows unrestricted local testing without needing to worry about rate limits:
# No rate limits applied
curl "http://localhost:3001/api/v1/strains/recommendations?q=sleep"
# Run it 100 times - no 429 errors

Best Practices

Cache Responses

Cache API responses client-side to reduce unnecessary requests. Most strain data changes infrequently.

Respect Retry-After

Always check the Retry-After header before retrying. Do not retry immediately.

Use Bulk Endpoints

Prefer batch endpoints (like /reports/stream/batch) over making many individual requests.

Monitor Headers

Track X-RateLimit-Remaining proactively and slow down requests before hitting the limit.