code: "RATE_LIMITED". It does not get HTTP 429, and there is no Retry-After header, so your client has to read the response body.
Limits by key type
Every operation call made with a credential counts once against that credential. Limits are per key, not per organization, so two API keys have separate counters.
Development limits are sized for building and testing. They are deliberately too low to run production traffic on a development key.
The
per_ip bucket on live site keys counts each visitor’s IP address separately. One visitor who hammers your page reaches it long before your key-wide limits are affected.
Agent keys (bs_agent_…) have no published limit. Build your agent as if one applies: use the long-poll on GET /agentlink/check-status (wait_seconds up to 25) instead of a tight polling loop, and reuse a request_id when you retry a proposal.
If your production traffic needs higher limits, contact BotShield. Limits can be raised for a specific key.
The blocked response
The blocked request did not run. Nothing was created or consumed, so it is safe to send again after the wait.
Watch your usage
Successful responses carry the same_rateLimit object next to data, so you can slow down before you are blocked:
data part is the operation’s normal response.
- For API keys, test site keys, and grant tokens,
_rateLimitis on every response. - For live site keys (
pk_live_…), successful responses leave it out to keep browser traffic small. Send the headerx-botshield-include-ratelimit: trueto include it. A blocked response always includes it.
Back off and retry
Wait for_rateLimit.primary.resetIn seconds, add a little random jitter so parallel workers do not all retry together, and cap the number of attempts.
hourly or daily, resetIn is long. Do not hold a user’s request open for it. Return an error to your caller, alert your team, and look for the cause.
The TypeScript SDK’s built-in retry does not cover rate limits. Its default retry codes are 429 and 5xx, and a rate-limited call throws a
BotShieldDefaultError with statusCode 403. Catch it and back off yourself. See TypeScript SDK.Stay under the limits
- Create sessions on demand. Call
POST /sdk/create-sessionwhen a user is about to verify, not on every page load. Each session also leads to acreate-verification-linkcall. - Poll at a steady pace. When you poll
GET /verification/statusfrom your server, every 2 to 3 seconds is enough. A request lives five minutes. - Use the long-poll for Agents Ask. One
check-statuscall withwait_seconds=25replaces a dozen short polls. - Use webhooks for outcomes you do not need instantly. See Webhooks overview.
- Keep development and production apart. Load tests against a development key hit the development limits quickly.
Next steps
Errors
All error shapes and a typed unwrap helper.
API overview
Credentials and the response envelope.
Keys and environments
Which key belongs where.
Testing
Try a gate in Development.
