PBN.LTD API docs
View as Markdown

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}/security

Scope 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

NameInTypeRequiredDescription
site_idpathintegeryesThe 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-attack

Scope 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

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
onbodybooleanyestrue = on, false = off.
auto_offbodystring (one of: 1h, 6h, 24h, never)noWhen 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/bots

Scope 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

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
settingbodystring (one of: ai, fight)yes"ai" = block the AI crawlers; "fight" = Cloudflare bot fight mode.
onbodybooleanyestrue = 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/rules

Scope 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

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
rulebodyobjectyesThe 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

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
rule_idpathstringyesThe rule id (see GET .../security).
rulebodyobjectyesThe 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

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
rule_idpathstringyesThe rule id (see GET .../security).
actionpathstring (one of: enable, disable, up, down)yesenable / 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

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
rule_idpathstringyesThe 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-protection

Scope 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

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
onbodybooleanyestrue = 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