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
}
}
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.
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