Rate Limits
Every authenticated API request is rate-limited based on the type of key in theAuthorization header. Limits protect against credential abuse and runaway cost without disrupting legitimate integrations.
BotShield is in a free Pilot Access Program. The limits below are pilot defaults, chosen to be generous for real dev + production traffic. Need more headroom — for a launch window, a traffic spike, or sustained higher volume? Contact us and we’ll raise your key’s limit.
Limits by key type
All four bearer token types are limited. Each type has multiple buckets that run in parallel — a request is allowed only if every bucket has capacity. Whichever bucket is closest to its cap is what surfaces asprimary in the response envelope.
Site keys — pk_test_* / pk_live_*
Public keys used by the client-side Web Component (Turnstile-style CAPTCHA replacement). Exposable in browser JavaScript.
Test API keys — bs_test_*
Backend keys for local development, test suites, and the Client SDK Playground. Designed for bursty dev workflow; tight enough on sustained throughput that running a production integration on a test key is painful.
Production API keys — bs_prod_*
Backend keys for server-to-server calls in production — approvals, presence checks, and other authenticated operations.
Anchor grant tokens — bss_*
Short-lived (5-minute) session tokens issued by POST /sdk/create-session. They share the production tier defaults above. Because they expire quickly and are single-issue per session, the daily cap is effectively unreachable in normal use.
The _rateLimit response envelope
Every response includes a _rateLimit envelope so you can observe your current bucket state without guessing. The envelope is a sibling of data (or errors) at the top of the response body.
Envelope inclusion policy
Blocked responses
When any bucket for your key is exhausted, the request is not handled by the operation — instead the API returns an immediate block response.- HTTP status:
403 errors[0].code:RATE_LIMITED- Body: the same
_rateLimitenvelope you get on success, plus anerrorsarray describing which bucket tripped
Retry strategy
- Read
_rateLimit.primary.resetInfrom the blocked response — that’s the seconds until capacity returns. - Don’t retry immediately on block. Exponential backoff is overkill: just wait until
resetInseconds have passed, then try again. - The backend SDKs do not retry
RATE_LIMITEDresponses automatically — you decide whether to wait, fall back, or surface the error to the user.
Common integration patterns
Pre-flight check before a batch: read_rateLimit.primary.remaining from your last successful response. If it’s lower than the batch size you’re about to send, pace the batch.
User-facing error handling: treat RATE_LIMITED as retryable. Show a “this is taking longer than usual” message and retry after resetIn. Don’t expose the raw bucket state to end users.
Dashboard observability: for high-traffic integrations, log _rateLimit.primary.remaining alongside your request metrics. A sustained downward trend is an early signal you’re approaching a limit.
Requesting a higher limit
If you need more headroom — for a launch window, a migration, or sustained higher traffic — email support with:- Your organization ID (visible in the Partner Dashboard)
- The key ID(s) that need the bump
- Which bucket is the bottleneck and your target limit
- Whether the increase should be permanent or have an end date