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

# Rate limits

Per-minute and per-day limits for ShopSniffer API endpoints, including the chat API token budget and 429 response shapes.

## Overview

ShopSniffer applies rate limits per user (for authenticated requests) and per IP (for anonymous requests). Most endpoints have no hard per-user limit — we reserve the right to throttle abusive patterns, but a well-behaved integration will never hit anything. The one exception is the chat API, which has explicit per-minute and per-day token budgets.

## Chat API Metered

The chat API is our most expensive endpoint (LLM tokens via OpenRouter) so it has strict per-user limits enforced by a Cloudflare KV-backed counter.

| Tier | Requests/minute | Daily token budget |
| --- | :---: | :---: |
| **Authenticated** (API key or Better Auth session) | **10** | **200,000** |
| **Anonymous** (no auth) | **3** | **20,000** |

- **Token budget** covers input and output tokens combined for the whole day, per user (or per IP for anonymous).
- **Per-minute limits** are a sliding 60-second window, enforced by a KV counter keyed on user ID or IP.
- **Daily budget** resets at 00:00 UTC.

### When you hit a limit

```json title="429 — per-minute"
{
  "error": "Rate limit exceeded",
  "retry_after": 42,
  "limit_type": "per_minute"
}
```

```json title="429 — daily tokens"
{
  "error": "Daily token budget exceeded",
  "limit_type": "daily_tokens",
  "budget": 200000,
  "used": 200143
}
```

Handle both by backing off — the `retry_after` field is the minimum seconds until you can retry for `per_minute`. For `daily_tokens`, wait until 00:00 UTC or authenticate with a higher-budget tier.

## Free export

`GET /api/free-export` has no application-level rate limit — limiting is enforced by the Cloudflare edge on a per-IP basis. Sustained abuse (hundreds of requests per second from a single IP) is automatically blocked for a cooldown window; well-behaved clients should never see a 429.

For reliable high-throughput access, use an API key and `POST /api/jobs` instead.

## Job creation

`POST /api/jobs` has no hard per-user rate limit. The real limiter is:

- **Shopify upstream** — some stores rate-limit the Shopify public product JSON endpoints, which manifests as a workflow retry / slow job rather than a 429 from ShopSniffer.
- **x402 payment verification** — on-chain settlement adds a few seconds of latency per unauthenticated request.
- **Concurrent jobs** — there's a soft concurrency cap per user. If you submit 100 jobs in a burst, they'll queue and process as workers free up.

If you need sustained high throughput (hundreds of jobs per hour), [contact support](https://shopsniffer.com/support) to bump your concurrency cap.

## Monitoring endpoints

All `/api/store-detail/*` endpoints require a Better Auth session and are scoped to stores you personally track. There's no hard limit — they're paginated (default 20-50 per page), so the natural upper bound is how fast you can turn pages.

## Public read endpoints

`GET /api/jobs/:id`, `GET /api/jobs/:id/status`, `GET /api/reports/:id`, `GET /api/pagespeed/:jobId`, `GET /api/stores`, `GET /api/stores/:slug`, `GET /api/apps`, `GET /api/blog`, and well-known files: **no application-level rate limits**. Cloudflare edge protection handles abuse automatically.

## What happens on 429

All 429 responses include:

- `error` — human-readable message
- `limit_type` — `per_minute` | `daily_tokens` | `ip_cooldown`
- `retry_after` — seconds to wait before retrying (when meaningful)

Clients should:

1. Read `retry_after` and back off for at least that many seconds.
2. Add jitter (±10%) to avoid thundering-herd retries.
3. Give up after 3 consecutive 429s on the same endpoint and surface an error to the user.

Do **not** retry immediately in a tight loop — you'll burn rate budget without making progress.

## Tips for staying under limits

- **Use webhooks, not polling.** `POST /api/jobs` with `webhook_url` is free; polling `GET /jobs/:id/status` every second for 3 minutes is 180 wasted requests. See [webhooks](/webhooks).
- **Use the WebSocket for progress.** `GET /api/ws/:jobId` is push-based and has no rate limit. See the [jobs API](/api/jobs#get-ws-jobid).
- **Cache responses.** Job data is immutable once `completed` — cache locally forever.
- **Authenticate for chat.** Anonymous chat limits are 5× lower. If you're calling chat more than once, get an API key.

## Next steps

**[Authentication](/authentication)**

Get an API key to unlock the authenticated rate limits.
  **[Webhooks](/webhooks)**

Avoid polling entirely with async callbacks.
  **[Chat API](/api/chat)**

Full reference for the metered chat endpoint.
  **[API overview](/api)**

Endpoint index.