Rate limits

Token-bucket, with state in Postgres so buckets are shared across serverless instances.

Per-caller limits

Tools Capacity Refill Effective
run_audit 3 200 s 3 per 10 min
get_audit_status 60 1 s ~60/min
get_report, get_score_history, list_fixes, compare_brands, verify_merchant, list_published_reports 30 2 s ~30/min
generate_fix_artifacts, publish_report 20 3 s ~20/min

Per-target-domain limit

Scope Capacity Refill Effective
Any call carrying a domain 3 600 s 3 per 10 min, across all callers

This is separate from and additional to your own budget. It exists so the API cannot be used to hammer a third party's site — the audited domain gets a say in how often it is crawled, even though it is not the one holding the key.

The caller budget is checked first; the target-domain budget is only consumed if the caller budget passed. Domain keys normalize scheme, trailing slash, and trailing dots, so example.com. and example.com share one bucket.

When rate limited

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded for tool \"run_audit\". Retry after 137s.",
    "details": { "retry_after_seconds": 137 }
  }
}

details.retry_after_seconds is always present. Back off for that long rather than retrying immediately — a retry inside the window consumes nothing and returns the same error.