Bot protection is enabled by default on every new app. A Cloudflare Turnstile widget is
provisioned automatically at app-creation time (provided the app has at least one
allowedOrigin), so you can start using it immediately — no setup required. This guide
covers the full protection model, the standalone /bot-protection/* endpoints you can use
to gate any action in your product, and how to render the Turnstile widget in your frontend.Overview
AGG combines global identity quotas, scoped read quotas, sign-in limits, and Turnstile challenges. Handle these response shapes in your client:429 Too Many Requests— back off and retry after the server-provided delay.503 Service Unavailable— scoped rate-limit enforcement is unavailable; honor its retry hint.challenge_required— render a Turnstile widget, collect the token, and resubmit.
Market-data reads also have automatic route-family quotas, including an outcome-unit budget shared by the single and batch outcome snapshot routes.
Layers 1–3 are automatic: they run before your request reaches any handler and return a
standard
429 you can retry. Layer 4 is the Turnstile challenge, which you integrate once
and reuse anywhere in your product.
Rate limits
Layer 1 — Per-IP limit
Layer 2 — Browser identity or validated API key
This is not a shared limit for every user of an app. Browser requests must satisfy both the IP and identity limits. A validated API key uses its own limit instead, with an 18,000-request default or a configured per-key quota, and is exempt from the browser IP limit. New market-data route caps still apply to that key and are not bypassed by a higher per-key quota.
If scoped market-data limits cannot be enforced, those routes return
503 with Retry-After: 1 and Cache-Control: private, no-store; retry after the hint.
Layer 3 — Sign-in limit
A tighter per-IP limit applied only toPOST /auth/start. Authentication endpoints are
high-value targets for credential stuffing, nonce mining, and email enumeration, so they
warrant stricter throttling.
Handling 429 on the client
Always honor the server’s retry hint: Retry-After header for Layers 1 and 2, retryAfter
body field for Layer 3. Never hardcode a fixed delay.
429 responses during normal use typically indicates a tight retry loop in your
client. Debounce the caller and surface a visible “please wait” state after the second
consecutive 429.
Turnstile challenges
Layer 4 activates when all of the following are true:- The sign-in provider is
siwe,siws, oremail(OAuth providers are exempt). - The app has
botProtectionEnabled = true. - The app’s registered user count is at or above
botProtectionThreshold. - A Turnstile widget is linked to the app.
POST /auth/start returns { type: "challenge_required", siteKey } instead
of the usual nonce or magic link. Your client renders the Turnstile widget, collects the
resulting token, and retries /auth/start with turnstileToken included in the body.
When type is challenge_required, render the widget and send the same start again with turnstileToken. Then continue Quickstart: REST. The same retry applies to siws and email. Wallet message signing stays on that quickstart.
Response matrix — POST /auth/start
Standalone bot protection
Use the same widget to gate any action. Two calls: fetch the site key from the browser, then verify the token from your server.x-app-id and x-app-api-key. The server checks that x-app-id matches the app id embedded in the key. An API key alone is not enough. Tokens are single-use.
Example — gating any action
Site key lookup happens in the browser. Token verification happens on your server, with both headers. Frontend — collect the token and post it to your own backend:Need an API key, or the rules for
x-app-api-key (scopes, rotation, requireApiKey, per-key limits)? See
Server API Keys.Rendering the Turnstile widget
Rendering the widget is a three-step process on the client:- Load the Turnstile script — Cloudflare’s loader (
api.js) exposes a globalwindow.turnstileobject with the widget API. - Mount the widget into a DOM element — pass the
siteKeyfetched fromGET /bot-protection/site-key(or thesiteKeyreturned in achallenge_requiredresponse). - Collect the token in a callback — the widget fires a callback with a short-lived token once the user passes the challenge. Submit that token alongside the user’s action.
Step 1 — Load the Turnstile script
Add the loader to your document<head> once, on every page that might render a widget.
The async defer attributes keep it from blocking initial render:
_app.tsx or layout.tsx)
using Next’s <Script> component or a one-shot effect:
Step 2 — Declarative embed (simplest)
The quickest path is the declarative embed. Cloudflare’s loader scans the DOM for any element with classcf-turnstile and auto-renders a widget into it using the attributes
you provide:
Step 3 — Programmatic rendering (React)
For SPAs — where the widget is mounted and unmounted as the user navigates — use the programmatic API. Callwindow.turnstile.render() to mount, reset() when the user
retries, and remove() on unmount so the widget doesn’t leak across route changes.
Token lifetime and reset behavior
Tokens are short-lived and single-use:- 5-minute TTL — Cloudflare invalidates a token 5 minutes after the user solves the challenge.
- Single-use — AGG enforces one-time verification. A token consumed by
/auth/startor/bot-protection/verifycannot be reused. - Always collect a fresh token right before the submit action, not at page load.
- On a failed submit, call
turnstile.reset()so the user can produce a new token without a full page reload.
expired-callback if the user leaves the widget idle too long —
handle it by clearing your local token state so the submit button re-disables until the
user re-solves.
Troubleshooting
Related
Authentication
Full sign-in flow for SIWE, SIWS, email, and OAuth providers.
Token Refresh
Renew access tokens after a successful sign-in.