> ## Documentation Index
> Fetch the complete documentation index at: https://docs.highailabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate Limiting

> Rate limit tiers, response headers, 429 handling, and per-endpoint limits.

## 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:

| Tier         | Limit        | Window     | Use Case                                  |
| ------------ | ------------ | ---------- | ----------------------------------------- |
| **Lenient**  | 300 requests | 1 minute   | Read operations (strain data, search)     |
| **Moderate** | 60 requests  | 1 minute   | Standard API operations                   |
| **Strict**   | 10 requests  | 1 minute   | AI/expensive operations (recommendations) |
| **Notebook** | 5 requests   | 1 hour     | Notebook generation                       |
| **Parse**    | 20 requests  | 1 hour     | Receipt/order parsing                     |
| **Scanner**  | 10 requests  | 10 minutes | Label scanning                            |

Additionally, there is a **strain submission** rate limiter (5 per hour per IP) for public website strain submissions.

### Tier Selection by Endpoint

<Tabs>
  <Tab title="Lenient (300/min)">
    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
  </Tab>

  <Tab title="Strict (10/min)">
    Expensive operations involving AI processing:

    * `GET /strains/recommendations` - AI-powered recommendations
    * `POST /notebooks/stream/:sectionKey` - Notebook section streaming
  </Tab>

  <Tab title="Scanner (10/10min)">
    Label scanning operations with a longer window:

    * `POST /scanner/analyze-label` - Single label scanning
    * `POST /scanner/analyze-batch` - Batch label scanning
  </Tab>

  <Tab title="Notebook (5/hr)">
    Notebook generation endpoints:

    * `POST /notebooks/stream/batch` - Batch notebook streaming
  </Tab>

  <Tab title="Parse (20/hr)">
    Receipt parsing operations:

    * `POST /receipts/parse` - AI receipt extraction
  </Tab>
</Tabs>

## 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
```

| Header                  | Description                                    |
| ----------------------- | ---------------------------------------------- |
| `X-RateLimit-Limit`     | Maximum requests allowed in the current window |
| `X-RateLimit-Remaining` | Requests remaining in the current window       |
| `X-RateLimit-Reset`     | ISO 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:

```bash theme={null}
curl -i "https://api.thisiswhyimhigh.com/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
```

```json theme={null}
{
  "error": "Rate limit exceeded. Maximum 10 requests per minute.",
  "retryAfter": 45
}
```

### Handling 429 Responses

Implement exponential backoff or respect the `Retry-After` header:

```typescript theme={null}
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 Type        | Rate Limit Key                          |
| ------------------ | --------------------------------------- |
| Identified request | User ID from context (if available)     |
| Standard request   | `X-Forwarded-For` IP address            |
| No identifier      | Fallback 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:

```typescript theme={null}
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:

```bash theme={null}
# No rate limits applied
curl "http://localhost:3001/api/v1/strains/recommendations?q=sleep"
# Run it 100 times - no 429 errors
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Cache Responses" icon="database">
    Cache API responses client-side to reduce unnecessary requests. Most strain data changes infrequently.
  </Card>

  <Card title="Respect Retry-After" icon="clock">
    Always check the `Retry-After` header before retrying. Do not retry immediately.
  </Card>

  <Card title="Use Bulk Endpoints" icon="layer-group">
    Prefer batch endpoints (like `/notebooks/stream/batch`) over making many individual requests.
  </Card>

  <Card title="Monitor Headers" icon="chart-line">
    Track `X-RateLimit-Remaining` proactively and slow down requests before hitting the limit.
  </Card>
</CardGroup>
