Site security
For sites on Cloudflare: "I'm Under Attack" mode, the site's own firewall rules and blocking AI crawlers.
Security settings of a site
GET/api/v1/sites/{site_id}/securityScope security:read
The same as the site's Security tab: whether "I'm Under Attack" is on (and when it switches itself off), the site's firewall rules and rate-limiting rules with the Cloudflare plan's limits, bot protection (AI crawlers blocked, bot fight mode), and the fields, operators and actions a new rule may use. 409 for a site that is not on Cloudflare.
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/security" \
-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/security", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/security", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"site_id": 123,
"under_attack": {
"available": true,
"on": false,
"external": false,
"auto_off_at": null,
"enabled_at": null,
"level": "medium",
"error": ""
},
"auto_off_choices": [
"1h",
"6h",
"24h",
"never"
],
"bot_protection": {
"ai": true,
"fight": false,
"status": "active",
"label": "Active"
},
"rules": {
"status": "ready",
"plan": "free",
"limits": {
"custom": 5,
"ratelimit": 1
},
"http_request_firewall_custom": [
{
"id": "a1b2",
"name": "Block bad country",
"action": "block",
"enabled": true,
"expression": "(ip.src.country in {\"XX\"})"
}
],
"http_ratelimit": []
},
"fields": {
"country": {
"label": "Country",
"ops": [
"in",
"not_in"
]
}
},
"ratelimit_fields": [
"path",
"method",
"wp_login",
"wp_admin",
"verified_bot"
],
"actions": {
"http_request_firewall_custom": {
"block": "Block"
},
"http_ratelimit": {
"block": "Block"
}
},
"login_path": "/wp-login.php",
"changes_paused": false,
"email_protection": {
"available": true,
"on": true,
"choice": null,
"default": true,
"pending": false,
"error": ""
}
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized
"I'm Under Attack" on or off
PUT/api/v1/sites/{site_id}/security/under-attackScope security:write
Every visitor gets a short browser check before the site loads while it is on - use it while the site is being hammered, then switch it off. While our flood protection has switched it on during an active attack, switching it off is refused (409 conflict, with the reason) until the attack has stopped; the state then carries locked.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
on | body | boolean | yes | true = on, false = off. |
auto_off | body | string (one of: 1h, 6h, 24h, never) | no | When switching on: switch it off again by itself after this long. Default: never. |
Example
curl -s -X PUT "https://app.pbn.ltd/api/v1/sites/123/security/under-attack" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.put("https://app.pbn.ltd/api/v1/sites/123/security/under-attack", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/security/under-attack", {
method: "PUT",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"available": true,
"on": false,
"external": false,
"auto_off_at": null,
"enabled_at": null,
"level": "medium",
"error": ""
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized, unavailable, validation_failed
Change bot protection
PUT/api/v1/sites/{site_id}/security/botsScope security:write
The same as the two bot switches on the Security tab. The change is saved at once and applied at Cloudflare within a few minutes (status reads "applying", then "active").
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
setting | body | string (one of: ai, fight) | yes | "ai" = block the AI crawlers; "fight" = Cloudflare bot fight mode. |
on | body | boolean | yes | true = on, false = off. |
Example
curl -s -X PUT "https://app.pbn.ltd/api/v1/sites/123/security/bots" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.put("https://app.pbn.ltd/api/v1/sites/123/security/bots", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/security/bots", {
method: "PUT",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"ai": true,
"fight": false,
"status": "active",
"label": "Active"
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized
Add a firewall rule
POST/api/v1/sites/{site_id}/security/rulesScope security:write
Adds a firewall rule (or a rate-limiting rule) to the site at Cloudflare, within the plan's limit. Returns every rule of the site.
Parameters
| Name | In | Type | Required | Description | |
|---|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). | |
rule | body | object | yes | The rule, exactly as the tab builds it: {"phase": "http_request_firewall_custom" (default) or "http_ratelimit", "name": "...", "action": one of actions[phase], "enabled": true, and EITHER "builder": {"match": "all" | "any", "conditions": [{"field": one of fields, "op": one of that field's ops, "value": "..." }]} OR "mode": "advanced" with "expression": a Cloudflare rule expression; a rate-limiting rule also takes "requests_per_period" (per 10 seconds per visitor)}. The same checks as the tab apply. |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/security/rules" \
-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/security/rules", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/security/rules", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
201
{
"data": {
"status": "ready",
"plan": "free",
"limits": {
"custom": 5,
"ratelimit": 1
},
"http_request_firewall_custom": [
{
"id": "a1b2",
"name": "Block bad country",
"action": "block",
"enabled": true,
"expression": "(ip.src.country in {\"XX\"})"
}
],
"http_ratelimit": []
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized, unavailable, validation_failed
Change a firewall rule
PUT/api/v1/sites/{site_id}/security/rules/{rule_id}Scope security:write
Replaces one rule (same shape as when adding it). Only rules made on this dashboard can be changed here.
Parameters
| Name | In | Type | Required | Description | |
|---|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). | |
rule_id | path | string | yes | The rule id (see GET .../security). | |
rule | body | object | yes | The rule, exactly as the tab builds it: {"phase": "http_request_firewall_custom" (default) or "http_ratelimit", "name": "...", "action": one of actions[phase], "enabled": true, and EITHER "builder": {"match": "all" | "any", "conditions": [{"field": one of fields, "op": one of that field's ops, "value": "..." }]} OR "mode": "advanced" with "expression": a Cloudflare rule expression; a rate-limiting rule also takes "requests_per_period" (per 10 seconds per visitor)}. The same checks as the tab apply. |
Example
curl -s -X PUT "https://app.pbn.ltd/api/v1/sites/123/security/rules/rule_id" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.put("https://app.pbn.ltd/api/v1/sites/123/security/rules/rule_id", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/security/rules/rule_id", {
method: "PUT",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"status": "ready",
"plan": "free",
"limits": {
"custom": 5,
"ratelimit": 1
},
"http_request_firewall_custom": [
{
"id": "a1b2",
"name": "Block bad country",
"action": "block",
"enabled": true,
"expression": "(ip.src.country in {\"XX\"})"
}
],
"http_ratelimit": []
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized, unavailable, validation_failed
Switch a rule on or off, or move it
POST/api/v1/sites/{site_id}/security/rules/{rule_id}/{action}Scope security:write
Rules run in order: the first that matches decides.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
rule_id | path | string | yes | The rule id (see GET .../security). |
action | path | string (one of: enable, disable, up, down) | yes | enable / disable the rule, or move it up / down the order. |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/security/rules/rule_id/scan" \
-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/security/rules/rule_id/scan", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/security/rules/rule_id/scan", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"status": "ready",
"plan": "free",
"limits": {
"custom": 5,
"ratelimit": 1
},
"http_request_firewall_custom": [
{
"id": "a1b2",
"name": "Block bad country",
"action": "block",
"enabled": true,
"expression": "(ip.src.country in {\"XX\"})"
}
],
"http_ratelimit": []
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized, unavailable, validation_failed
Delete a firewall rule
DELETE/api/v1/sites/{site_id}/security/rules/{rule_id}Scope security:write · destructive
Removes one rule from the site at Cloudflare.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
rule_id | path | string | yes | The rule id (see GET .../security). |
Example
curl -s -X DELETE "https://app.pbn.ltd/api/v1/sites/123/security/rules/rule_id" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.delete("https://app.pbn.ltd/api/v1/sites/123/security/rules/rule_id", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/security/rules/rule_id", {
method: "DELETE",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"status": "ready",
"plan": "free",
"limits": {
"custom": 5,
"ratelimit": 1
},
"http_request_firewall_custom": [
{
"id": "a1b2",
"name": "Block bad country",
"action": "block",
"enabled": true,
"expression": "(ip.src.country in {\"XX\"})"
}
],
"http_ratelimit": []
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized, unavailable
Email address protection on or off
PUT/api/v1/sites/{site_id}/security/email-protectionScope security:write
The CDN hides e-mail addresses on the site's pages from spam bots (on by default): each address becomes a /cdn-cgi/l/email-protection link plus a small script, which SEO crawlers can report as broken links. Switch it off (or on) for this site - the same switch as the Security tab. The choice is kept if the site's DNS zone is re-created or the site moves CDN and back. choice is null while the site uses the default.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
on | body | boolean | yes | true = on, false = off. |
Example
curl -s -X PUT "https://app.pbn.ltd/api/v1/sites/123/security/email-protection" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.put("https://app.pbn.ltd/api/v1/sites/123/security/email-protection", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/security/email-protection", {
method: "PUT",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"available": true,
"on": true,
"choice": null,
"default": true,
"pending": false,
"error": ""
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized, unavailable, validation_failed