<!-- source: https://docs.redpine.ai/docs/rate-limits -->
# Rate limits

> Request limits, quota headers, and handling throttling.

## Current limits

| Endpoint                             | Limit                          |
| ------------------------------------ | ------------------------------ |
| `POST /api/v1/search/query`          | 60 requests / minute (default) |
| `POST /api/v1/tools/{prefix}/{tool}` | 60 requests / minute (default) |
| `POST /api/v1/search/preview`        | 50 previews / hour (default)   |
| Other endpoints                      | Varies by endpoint             |

Search and tool calls share one per-organization requests/minute budget. The default is 60; your organization may have a custom limit. Contact support to adjust it.

`POST /api/v1/search/preview` is throttled separately, on a per-organization requests/hour budget rather than the requests/minute one above: it's a free-teaser abuse guard, not a query quota, so it's kept apart from the metered limit search and tool calls share. This budget is shared with the MCP `preview` tool, not a separate allowance. It's keyed on organization, not API key: minting a second key on the same org does not multiply the allowance. The default is 50/hour; your organization may have a custom limit. Contact support to adjust it. `POST /api/v1/search/unlock` is not separately rate limited, but only counts against the requests/minute budget above when it actually unlocks new results. A repeat unlock of results you already paid for costs nothing and doesn't count as a query.

Endpoints outside search and tools (login, invites, and most dashboard/admin routes) are protected by a separate, IP-address-keyed limiter. Its `429` response is a bare `{"error": "Rate limit exceeded: ..."}` string, not the `{code, message, requestId}` shape used everywhere else on this page, and it never sends `X-RateLimit-*` headers.

## Daily and monthly quotas

Beyond the per-minute limit above, your organization also has a daily and a monthly query quota (defaults: 1,000/day and 25,000/month). Hitting either returns the same `429` shape as the per-minute limit, but `Retry-After` reflects the real reset time: up to \~24 hours for the daily quota, up to \~31 days for the monthly one. Call `GET /api/v1/search/quota` to check your current usage and limits for both windows before you hit them.

## Response headers

`POST /api/v1/search/query` and `POST /api/v1/search/{collection}` include rate limit headers reporting your **daily** quota, so you can track usage against it:

```
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 943
X-RateLimit-Reset: 1709000000
```

* `X-RateLimit-Limit`: Your organization's daily query limit (not the per-minute limit above).
* `X-RateLimit-Remaining`: Daily queries remaining.
* `X-RateLimit-Reset`: Unix timestamp for the next UTC midnight, when the daily count resets.

These headers are only sent on the two endpoints above, and only when quota enforcement is available (Redis reachable). Other endpoints, including `GET /api/v1/search/collections`, `GET /api/v1/search/results/{queryId}`, and `GET /api/v1/search/quota` itself, never send them.

## Billing headers

Search responses also carry billing headers so you can track cost per request without a separate call:

```
HTTP/1.1 200 OK
X-Tokens-Used: 842
X-Query-Cost: 0.842000
X-Billing-Mode: live
X-Credits-Remaining: 48.316000
```

* `X-Tokens-Used`: Tokens billed for this request.
* `X-Query-Cost`: What this request was charged, to 6 decimal places. `0.000000` when nothing was charged, which is every request in `exempt`, `trial` or `sandbox` mode.
* `X-Billing-Mode`: How this request was billed: `live` (charged normally), `exempt` (billing-exempt org, no charge), `trial` (covered by trial credit), or `sandbox` (a sandbox key returned synthetic results at no cost).
* `X-Credits-Remaining`: Organization credit balance after this request. Omitted if your organization has balance visibility hidden.

None of these headers are present when billing is kill-switched for your organization.

## Exceeding the limit

When you exceed a limit, the API returns `429 Too Many Requests` with a `Retry-After` header. This example is the per-minute case; daily/monthly quota errors use the same shape with a longer `Retry-After` (see above):

```
HTTP/1.1 429 Too Many Requests
Retry-After: 30

{
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Rate limit exceeded. Try again in 30 seconds.",
    "requestId": "req_a1b2c3d4"
  }
}
```

## Best practices

* **Respect Retry-After**: wait the indicated time before retrying.
* **Use exponential backoff**: on repeated 429s, double your wait time between retries.
* **Cache results**: avoid redundant requests for the same query.
* **Monitor your usage**: check `X-RateLimit-Remaining` to proactively throttle before hitting limits.

## In the SDKs

The clients do the first two for you. A `429` or `503` is retried up to `max_retries` times (default 2), sleeping for `Retry-After` when the server sent it and otherwise backing off exponentially with jitter from 0.5 s, capped at 30 s. Nothing else is retried. When retries run out, `QuotaExceeded` surfaces with `retry_after` set, so a daily or monthly quota hit (hours or days) is yours to schedule rather than something the client sleeps through.

The SDKs return parsed bodies, not raw responses, so the `X-RateLimit-*` and billing headers above are not exposed. Use the quota endpoint for the same numbers:

<CodeBlockTabs defaultValue="cURL" groupId="lang">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="cURL">
      cURL
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="TypeScript">
      TypeScript
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Go">
      Go
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="cURL">
    ```bash  
    curl "https://api.redpine.ai/api/v1/search/quota" \
      -H "Authorization: Bearer sk_live_YOUR_API_KEY"
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python  
    client = Redpine(max_retries=3)   # or 0 to handle 429s yourself

    q = client.quota()
    print(q.daily_remaining, "of", q.daily_limit, "today;", q.monthly_remaining, "of", q.monthly_limit, "this month")
    ```
  </CodeBlockTab>

  <CodeBlockTab value="TypeScript">
    ```typescript  
    const client = new Redpine({ maxRetries: 3 }); // or 0 to handle 429s yourself

    const q = await client.quota();
    console.log(q.dailyRemaining, "of", q.dailyLimit, "today;", q.monthlyRemaining, "of", q.monthlyLimit, "this month");
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Go">
    ```go  
    client, err := redpine.New(redpine.WithMaxRetries(3)) // or 0 to handle 429s yourself
    if err != nil {
        return err
    }

    q, err := client.Quota(ctx)
    if err != nil {
        return err
    }
    fmt.Println(q.DailyRemaining, "of", q.DailyLimit, "today;", q.MonthlyRemaining, "of", q.MonthlyLimit, "this month")
    ```
  </CodeBlockTab>
</CodeBlockTabs>