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

# Rate Limits

> Understand API rate limits and how to handle them

Rate limits ensure fair usage and platform stability. Limits are applied **per team**, across all API tokens belonging to that team.

## Current Limits

Two windows are enforced simultaneously on every request:

| Window              | Limit                   |
| ------------------- | ----------------------- |
| Per-minute (burst)  | 1,000 requests / minute |
| Per-day (daily cap) | 20,000 requests / day   |

A request is accepted only if **both** windows have budget remaining. If either window is exhausted, the API returns `429 Too Many Requests`.

<Note>
  Need higher limits for production workloads (e.g. ingesting data for many brands/domains)? Contact us at [team@qwairy.co](mailto:team@qwairy.co).
</Note>

## Rate Limit Headers

Every response, including `429`s, includes headers for both windows:

| Header                        | Description                                                                          |
| ----------------------------- | ------------------------------------------------------------------------------------ |
| `X-RateLimit-Limit`           | Per-minute request cap                                                               |
| `X-RateLimit-Remaining`       | Requests remaining in the current minute window                                      |
| `X-RateLimit-Reset`           | ISO 8601 timestamp when the minute window resets                                     |
| `X-RateLimit-Daily-Limit`     | Per-day request cap                                                                  |
| `X-RateLimit-Daily-Remaining` | Requests remaining in the current day window                                         |
| `X-RateLimit-Daily-Reset`     | ISO 8601 timestamp when the day window resets                                        |
| `Retry-After`                 | Seconds to wait before retrying, based on the limit actually hit (only set on `429`) |

### Example Headers

```http theme={null}
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 873
X-RateLimit-Reset: 2026-04-07T09:13:00.000Z
X-RateLimit-Daily-Limit: 20000
X-RateLimit-Daily-Remaining: 4211
X-RateLimit-Daily-Reset: 2026-04-08T00:00:00.000Z
```

<Warning>
  **Always check both windows.** A request can be rejected while `X-RateLimit-Remaining` still shows plenty of per-minute budget if the daily cap is exhausted. Conversely, a healthy daily budget doesn't guarantee the next request will pass the per-minute window.
</Warning>

## Handling Rate Limits

When either window is exceeded, the API returns `429 Too Many Requests` with a body indicating which window was hit:

```json theme={null}
{
  "error": "Too Many Requests",
  "limit": "daily",
  "message": "Daily rate limit (20000/day) exceeded. Please try again in 32451 seconds.",
  "retryAfter": 32451
}
```

The `limit` field is either `"burst"` (per-minute) or `"daily"`. `Retry-After` and the `X-RateLimit-*-Reset` headers are always aligned with the actual blocker, so you can trust `Retry-After` without having to inspect the payload.

### Best Practices

<CardGroup cols={2}>
  <Card title="Trust Retry-After" icon="clock">
    Use the `Retry-After` header as-is. It already reflects whichever window (minute or day) is the real blocker.
  </Card>

  <Card title="Watch both remaining headers" icon="gauge">
    Monitor `X-RateLimit-Remaining` and `X-RateLimit-Daily-Remaining` to throttle proactively.
  </Card>

  <Card title="Cache responses" icon="database">
    Store and reuse data that doesn't change frequently (brands, competitors, topics).
  </Card>

  <Card title="Batch and filter" icon="layer-group">
    Use query filters (`provider`, `period`, `limit`) to fetch only the data you need.
  </Card>
</CardGroup>

### Retry Logic Example

```javascript theme={null}
async function fetchWithRetry(url, options, maxRetries = 10) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status !== 429) return response;

    // Trust the Retry-After header — it points at the real blocker
    // (minute or day window), not just the minute reset.
    const retryAfterSec = Number(response.headers.get('Retry-After') ?? '60');
    const waitMs = Math.max(retryAfterSec * 1000, 1000);

    const body = await response.clone().json().catch(() => ({}));
    console.warn(
      `Rate limited on ${body.limit ?? 'unknown'} window. Retrying in ${waitMs}ms...`
    );

    await new Promise((r) => setTimeout(r, waitMs));
  }

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

<Tip>
  If you see `"limit": "daily"` on retry, waiting within the same process rarely helps: the daily window can be hours away. Persist the `X-RateLimit-Daily-Reset` timestamp and reschedule the job instead of blocking a worker.
</Tip>

## Optimizing API Usage

### Use Filtering Parameters

Instead of fetching all data and filtering client-side, use query parameters:

```bash theme={null}
# Bad: Fetch all answers, filter in code
GET /api/v1/brands/{brandId}/answers

# Good: Filter at the API level
GET /api/v1/brands/{brandId}/answers?provider=chatgpt&hasSelfMention=true&limit=50
```

### Cache Static Data

Some data changes infrequently and can be cached:

* **Brands list**: Changes rarely
* **Competitors list**: Changes when you add/remove competitors
* **Topics/Tags**: Changes when you modify your workspace

### Use Date Ranges

Limit data retrieval to relevant time periods:

```bash theme={null}
# Fetch only last 7 days instead of default 30
GET /api/v1/brands/{brandId}/performance?period=7
```

## Need Higher Limits?

If 20,000 requests per day is too tight for your ingestion pipeline, contact us at [team@qwairy.co](mailto:team@qwairy.co) with your use case and expected volume. We can raise the daily cap on your team.
