# 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 /sites/{site_id}/turnstile` · scope `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

```bash
curl -s "https://app.pbn.ltd/api/v1/sites/123/turnstile" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
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())
```

```javascript
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`

```json
{
  "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 /sites/{site_id}/turnstile` · scope `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

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/turnstile" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
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())
```

```javascript
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`

```json
{
  "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 /sites/{site_id}/turnstile/rotate` · scope `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

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/turnstile/rotate" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
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())
```

```javascript
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`

```json
{
  "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 /sites/{site_id}/turnstile/disable` · scope `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

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/turnstile/disable" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
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())
```

```javascript
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`

```json
{
  "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`
