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{ "error": "Rate limit exceeded", "retry_after": 42, "limit_type": "per_minute" }
json{ "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 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 messagelimit_type—per_minute|daily_tokens|ip_cooldownretry_after— seconds to wait before retrying (when meaningful)
Clients should:
- Read
retry_afterand back off for at least that many seconds. - Add jitter (±10%) to avoid thundering-herd retries.
- 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/jobswithwebhook_urlis free; pollingGET /jobs/:id/statusevery second for 3 minutes is 180 wasted requests. See webhooks. - Use the WebSocket for progress.
GET /api/ws/:jobIdis push-based and has no rate limit. See the jobs API. - 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.