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 ships a four-layer protection model. As an integrator, you only need to handle two response shapes in your client:429 Too Many Requests— back off and retry after the server-provided delay.challenge_required— render a Turnstile widget, collect the token, and resubmit.
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 — Per-app limit
Layer 2 is sized for aggregate traffic across your entire user base, so it comfortably
accommodates normal usage patterns.
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.
Example — SIWE with inline challenge handling
siws and email providers — only the
follow-up step after receiving the nonce or magic link differs.
Response matrix — POST /auth/start
Standalone bot protection
The/bot-protection/* endpoints let you use the same Turnstile widget to gate any action
in your product you want to shield a sensitive action from automated abuse. The API surface is
exactly two endpoints: fetch the site key, verify the token.
GET /bot-protection/site-key
Returns the Turnstile site key registered for your app so you can render the widget in any
part of your UI.
string
required
Your application ID.
string | null
The Turnstile site key to pass into the widget as
data-sitekey (or the equivalent prop in
a React/Vue wrapper). Returns null when no widget is registered for the app.POST /bot-protection/verify
Verifies a Turnstile token against the app’s linked widget. Tokens are single-use — a second
verification of the same token always fails.
string
required
Your AGG app ID. Must match the app the API key was issued for.
string
required
Your app-scoped API key
string
required
The token returned by the Turnstile widget. Maximum 2048 characters.
Example — gating any action
Site key lookup happens in the browser (public, safe). Token verification happens on your server (protected by your API key). Frontend — collect the token and post it to your own backend:Need to create an API key or looking for the full reference on
x-app-api-key
(scopes, rotation, requireApiKey lockout, per-key rate 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.