Turnstile bot check
Cloudflare Turnstile on your WordPress forms (login, registration, lost password, comments, Contact Form 7, WPForms): status, switch on or off, choose forms, new keys.
Turnstile status of a site
GET/api/v1/sites/{site_id}/turnstileScope turnstile:read
What the site's Turnstile tab shows: whether the bot check can be used here (WordPress sites whose DNS is on Cloudflare), whether it is on, the mode, which forms it protects, the extra hostnames it accepts, when it was last read back from the site and when the keys were last replaced. The secret key is never returned.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
Example
curl -s "https://app.pbn.ltd/api/v1/sites/123/turnstile" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/turnstile", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/turnstile", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"available": true,
"status": "active",
"status_label": "On",
"account": "ours",
"sitekey": "0x4AAAAAAAxxxxxxxxxxxxxx",
"mode": "managed",
"forms": [
"login",
"register",
"lostpassword",
"comments",
"cf7",
"wpforms"
],
"extra_hosts": [],
"hosts": [
"example.com"
],
"last_error": "",
"revoked": false,
"verified_at": "2026-09-24 10:00 UTC",
"rotated_at": "",
"modes": [
{
"key": "managed",
"label": "Managed (recommended)"
},
{
"key": "non-interactive",
"label": "Non-interactive"
},
{
"key": "invisible",
"label": "Invisible"
}
],
"all_forms": [
{
"key": "login",
"label": "Login form"
},
{
"key": "register",
"label": "Registration form"
}
],
"paused": false
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized
Switch Turnstile on (or change its settings)
POST/api/v1/sites/{site_id}/turnstileScope turnstile:write
Switches the bot check on for this site, or saves new settings when it is already on. We create the Turnstile widget, install our small WordPress plugin with the keys and read it back from the site. If there is no room yet the status is "waiting" and it finishes by itself. Turnstile that our team switched off ("revoked") cannot be switched back on here - contact support.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
mode | body | string (one of: managed, non-interactive, invisible) | no | How visitors are checked. managed (recommended) shows a checkbox only when needed; non-interactive never asks; invisible shows nothing. Default: managed. |
forms | body | array | no | Which forms to protect: login, register, lostpassword, comments, cf7 (Contact Form 7), wpforms. Default: all of them. |
extra_hosts | body | array | no | Up to 4 more hostnames the check must accept - domains of your OTHER sites with us (e.g. a subdomain site). |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/turnstile" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/turnstile", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/turnstile", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"available": true,
"status": "active",
"status_label": "On",
"account": "ours",
"sitekey": "0x4AAAAAAAxxxxxxxxxxxxxx",
"mode": "managed",
"forms": [
"login",
"register",
"lostpassword",
"comments",
"cf7",
"wpforms"
],
"extra_hosts": [],
"hosts": [
"example.com"
],
"last_error": "",
"revoked": false,
"verified_at": "2026-09-24 10:00 UTC",
"rotated_at": "",
"modes": [
{
"key": "managed",
"label": "Managed (recommended)"
},
{
"key": "non-interactive",
"label": "Non-interactive"
},
{
"key": "invisible",
"label": "Invisible"
}
],
"all_forms": [
{
"key": "login",
"label": "Login form"
},
{
"key": "register",
"label": "Registration form"
}
],
"paused": false
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized, validation_failed
Replace the Turnstile secret key
POST/api/v1/sites/{site_id}/turnstile/rotateScope turnstile:write
Issues a new secret key for the site's widget and installs it on the site straight away. The new key is never shown - it only lives on your site. Use it if you think the old key leaked.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/turnstile/rotate" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/turnstile/rotate", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/turnstile/rotate", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"available": true,
"status": "active",
"status_label": "On",
"account": "ours",
"sitekey": "0x4AAAAAAAxxxxxxxxxxxxxx",
"mode": "managed",
"forms": [
"login",
"register",
"lostpassword",
"comments",
"cf7",
"wpforms"
],
"extra_hosts": [],
"hosts": [
"example.com"
],
"last_error": "",
"revoked": false,
"verified_at": "2026-09-24 10:00 UTC",
"rotated_at": "",
"modes": [
{
"key": "managed",
"label": "Managed (recommended)"
},
{
"key": "non-interactive",
"label": "Non-interactive"
},
{
"key": "invisible",
"label": "Invisible"
}
],
"all_forms": [
{
"key": "login",
"label": "Login form"
},
{
"key": "register",
"label": "Registration form"
}
],
"paused": false
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized
Switch Turnstile off
POST/api/v1/sites/{site_id}/turnstile/disableScope turnstile:write · destructive
Takes the plugin off the site first (read back), then deletes the widget. Your forms are no longer protected by the bot check. Needs "confirm": true. You can switch it on again at any time.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
confirm | body | boolean | yes | Must be true: switching Turnstile off removes the bot check from the site's forms. |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/turnstile/disable" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/turnstile/disable", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/turnstile/disable", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"available": true,
"status": "off",
"status_label": "Off",
"account": "ours",
"sitekey": null,
"mode": "managed",
"forms": [
"login",
"register",
"lostpassword",
"comments",
"cf7",
"wpforms"
],
"extra_hosts": [],
"hosts": [
"example.com"
],
"last_error": "",
"revoked": false,
"verified_at": "2026-09-24 10:00 UTC",
"rotated_at": "",
"modes": [
{
"key": "managed",
"label": "Managed (recommended)"
},
{
"key": "non-interactive",
"label": "Non-interactive"
},
{
"key": "invisible",
"label": "Invisible"
}
],
"all_forms": [
{
"key": "login",
"label": "Login form"
},
{
"key": "register",
"label": "Registration form"
}
],
"paused": false
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized, validation_failed