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

# Rate Limits

> Understand request rate limits by plan tier and how to handle throttled responses.

The Shelfforce API enforces rate limits to ensure fair usage and platform stability. Limits are applied per API key over a rolling 60-second window.

## Limits by plan

| Plan        | Analyses / 60s | Reads / 60s | Writes / 60s |
| ----------- | -------------- | ----------- | ------------ |
| **Free**    | 5              | 60          | 20           |
| **Starter** | 10             | 120         | 40           |
| **Growth**  | 30             | 300         | 80           |
| **Pro**     | 60             | 600         | 150          |
| **Scale**   | 150            | 1,200       | 300          |

**Analyses** — `POST /api/v1/analyses` and `POST /api/v1/analyses/batch` (each image in a batch counts as one analysis request).

**Reads** — All `GET` endpoints (analyses, products, tasks, places, reports, usage).

**Writes** — All `POST` and `PATCH` endpoints except analysis submission (tasks, places, inventory, webhooks).

## Rate limit headers

Every API response includes headers showing your current rate limit status:

| Header                  | Description                                     |
| ----------------------- | ----------------------------------------------- |
| `X-RateLimit-Limit`     | Maximum requests allowed in the current window  |
| `X-RateLimit-Remaining` | Requests remaining in the current window        |
| `X-RateLimit-Reset`     | Unix timestamp (seconds) when the window resets |

Example response headers:

```
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 22
X-RateLimit-Reset: 1740312120
```

## Handling rate limits

When you exceed the rate limit, the API returns a `429 Too Many Requests` response with a `Retry-After` header indicating how many seconds to wait:

```json theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Retry after 12 seconds.",
    "details": {
      "retryAfter": 12,
      "limit": 30,
      "window": "60s"
    }
  }
}
```

## Best practices

<CardGroup cols={2}>
  <Card title="Exponential backoff" icon="history">
    When you receive a `429`, wait for the duration specified in `Retry-After`, then retry. If the retry also fails, double the wait time on each subsequent attempt.
  </Card>

  <Card title="Use batch endpoints" icon="layers">
    Submit up to 20 images in a single batch request instead of making 20 individual calls. This consumes only 1 rate limit slot for writes.
  </Card>

  <Card title="Cache results" icon="database">
    Analysis results are permanent. Cache responses locally to avoid redundant `GET` requests.
  </Card>

  <Card title="Use webhooks" icon="bell">
    Instead of polling for analysis status, register a webhook to receive notifications when analyses complete. This eliminates repeated `GET` requests.
  </Card>
</CardGroup>

### Example: Exponential backoff

<CodeGroup>
  ```typescript Node.js theme={null}
  async function fetchWithBackoff(url: string, options: RequestInit, maxRetries = 5) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const response = await fetch(url, options);

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

      const retryAfter = parseInt(response.headers.get("Retry-After") || "1", 10);
      const backoff = retryAfter * 1000 * Math.pow(2, attempt);
      console.log(`Rate limited. Retrying in ${backoff}ms...`);
      await new Promise((resolve) => setTimeout(resolve, backoff));
    }

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

  ```python Python theme={null}
  import time
  import requests

  def fetch_with_backoff(url, headers, max_retries=5):
      for attempt in range(max_retries):
          response = requests.get(url, headers=headers)

          if response.status_code != 429:
              return response

          retry_after = int(response.headers.get("Retry-After", "1"))
          backoff = retry_after * (2 ** attempt)
          print(f"Rate limited. Retrying in {backoff}s...")
          time.sleep(backoff)

      raise Exception("Max retries exceeded")
  ```
</CodeGroup>

<Tip>
  If you consistently hit rate limits, consider upgrading your plan. Contact [hey@shelfforce.ai](mailto:hey@shelfforce.ai) for custom rate limits on Enterprise plans.
</Tip>
