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

# Rate-limit handling

> Read the limit, watch the headers, back off against the reset timestamp.

WiseYield rate-limits API requests **per user**, sliding-window, scaled by subscription tier. This guide covers how to read the current state, what happens when you hit the limit, and how to back off correctly.

## The limits

| Tier            | Requests / hour |
| --------------- | --------------- |
| Expired trial   | 3               |
| Seed            | 25              |
| Sprout          | 50              |
| Trial / Harvest | 100             |
| Grove           | 200             |
| Summit          | 1,000           |

The system **fails closed** — if the rate-limit backend is unreachable in production, requests are denied with `503 SERVICE_UNAVAILABLE` rather than allowed through unmetered.

## Headers on every response

Every response (success or 429) carries the current window state:

```http theme={null}
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 2026-05-17T14:00:00.000Z
```

| Header                  | Type                     | Meaning                                      |
| ----------------------- | ------------------------ | -------------------------------------------- |
| `X-RateLimit-Limit`     | integer                  | Total requests allowed in the current window |
| `X-RateLimit-Remaining` | integer                  | Requests remaining before the limit kicks in |
| `X-RateLimit-Reset`     | ISO 8601 timestamp (UTC) | When the sliding window opens again          |

> `X-RateLimit-Reset` is an **ISO 8601 string**, not a Unix timestamp. Parse with `new Date(header)` or `datetime.fromisoformat(header.replace('Z', '+00:00'))`.

## When you hit the limit

```http theme={null}
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2026-05-17T14:00:00.000Z
Retry-After: 1738
Content-Type: application/json

{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMIT_EXCEEDED"
}
```

The `Retry-After` header is included as seconds-until-reset for clients that prefer the standard HTTP signal.

## Backoff strategy

Wait until `X-RateLimit-Reset` before the next attempt. Exponential backoff with jitter is the right pattern for transient `5xx` errors, but for `429` you have the exact reset time — use it.

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function fetchWithBackoff(url, init, maxRetries = 3) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const res = await fetch(url, init);
      if (res.status !== 429) return res;

      const resetIso = res.headers.get('X-RateLimit-Reset');
      const waitMs = resetIso
        ? Math.max(0, new Date(resetIso).getTime() - Date.now())
        : 2 ** attempt * 1000;

      // Add jitter to avoid stampedes when many clients reset at the same time
      const jitter = Math.random() * 500;
      await new Promise(r => setTimeout(r, waitMs + jitter));
    }
    throw new Error('Max retries exceeded');
  }
  ```

  ```python Python theme={null}
  import time
  import random
  import requests
  from datetime import datetime, timezone

  def fetch_with_backoff(url, headers, max_retries=3):
      for attempt in range(max_retries):
          res = requests.get(url, headers=headers)
          if res.status_code != 429:
              return res

          reset_iso = res.headers.get('X-RateLimit-Reset')
          if reset_iso:
              reset = datetime.fromisoformat(reset_iso.replace('Z', '+00:00'))
              wait = max(0, (reset - datetime.now(timezone.utc)).total_seconds())
          else:
              wait = 2 ** attempt
          time.sleep(wait + random.uniform(0, 0.5))
      raise RuntimeError('Max retries exceeded')
  ```
</CodeGroup>

## Pre-flight checks

You don't need to hit a 429 to know you're close to the limit. Inspect `X-RateLimit-Remaining` on every response and throttle proactively:

```javascript theme={null}
const res = await fetch(url, init);
const remaining = parseInt(res.headers.get('X-RateLimit-Remaining') || '0', 10);
if (remaining < 5) {
  const resetIso = res.headers.get('X-RateLimit-Reset');
  const waitMs = Math.max(0, new Date(resetIso).getTime() - Date.now());
  console.warn(`Approaching rate limit, pausing ${waitMs}ms`);
  await new Promise(r => setTimeout(r, waitMs));
}
```

## What the limits are NOT

* **Not per-key.** Two keys owned by the same user share the same window. Splitting traffic across multiple keys does not multiply your budget.
* **Not per-endpoint.** All requests count against the same per-user bucket, regardless of which route they hit.
* **Not per-IP.** They're scoped to the API key's owning user.

## Need a higher limit?

The standard tier limits are sized for typical integration patterns. If your application needs sustained throughput beyond Summit's 1,000 req/hr, contact [support@wiseyield.co](mailto:support@wiseyield.co) — custom limits are available for enterprise integrations.

## See also

* [Subscription tiers](/concepts/tier-ladder) — how rate limits scale with tier
* [Errors & status codes](/concepts/errors) — the canonical 429 response shape
