Sites
Every site type: list, search, create, change, delete; status, nameservers, CDN, SEO.
List and search sites
GET/api/v1/sitesScope sites:read
The same search, filters and order as the sites list in the panel. total is the number of matching sites. A key restricted to some sites only sees those.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
search | query | string | no | Part of the domain or name (www. is ignored). |
state | query | string (one of: ok, waiting, activating, working, error, frozen, notinstalled) | no | ok = live, waiting = waiting for DNS, activating = activating CDN, working = being installed/changed, error, frozen = paused, notinstalled. |
type | query | string (one of: Wordpress, Static HTML, PHP hosting, Joomla, Drupal, PrestaShop, OpenCart, Grav, MediaWiki, mismatch) | no | Site type; "mismatch" = the files run a different platform than the site type. |
group | query | string | no | Group id, or "none" for sites in no group. |
cdn | query | string | no | CDN name, e.g. Cloudflare, BunnyCDN, KeyCDN, CDN77.COM, Gcore, CloudFront. |
php_version | query | string | no | PHP version value, e.g. "PHP 8.3". |
indexed | query | string (one of: yes, no, pending) | no | Google indexation state. |
online | query | string (one of: online, offline) | no | The online badge: offline = a confirmed problem. |
sort | query | string (one of: newest, oldest, name, name_desc, domain, domain_desc) | no | Order of the list. Default: newest. |
limit | query | integer | no | Items per page (1-200). Default: 50. |
cursor | query | string | no | The next_cursor value of the previous page. Omit for the first page. |
Example
curl -s "https://app.pbn.ltd/api/v1/sites?search=blog&state=ok&limit=20" \
-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?search=blog&state=ok&limit=20", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites?search=blog&state=ok&limit=20", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": [
{
"id": 123,
"name": "example",
"domain": "example.com",
"type": "Wordpress",
"state": "Ok",
"state_group": "ok",
"url": "https://example.com",
"created_at": "2026-01-05T12:00:00Z",
"group": {
"id": 7,
"name": "Travel"
},
"cdn": "Cloudflare",
"php_version": "PHP 8.3",
"use_https": true,
"use_www": false,
"frozen": false,
"switched_off_by_staff": false,
"suspended": false,
"pending_delete": false,
"main_cloud": false,
"indexed": "yes",
"last_indexed_at": "2026-09-10T03:00:00Z",
"last_not_indexed_at": null,
"domain_expires_at": "2027-03-01T00:00:00Z",
"seo": {
"trust_flow": 12,
"citation_flow": 20,
"backlinks": 340,
"referring_domains": 41,
"trust_flow_change": 1,
"citation_flow_change": 0,
"backlinks_change": 12,
"referring_domains_change": 2,
"measured_at": "2026-09-15T02:00:00Z"
},
"platform": {
"type": "Wordpress",
"label": "WordPress",
"detected": "wordpress",
"mismatch": null,
"checked_at": "2026-09-19T04:00:00Z"
},
"online": {
"status": "online",
"label": "Online",
"reason": null,
"since": null,
"checked_at": "2026-09-19T20:00:00Z",
"text": "Homepage answered normally (last check ...)."
}
}
],
"next_cursor": null,
"has_more": false
}
Errors: rate_limited, scope_missing, unauthorized
Get one site
GET/api/v1/sites/{site_id}Scope sites:read
Everything the site page shows: state, URL, settings, why it is paused (freeze.reasons), nameserver status (current vs required, pointed, autopilot), CDN, SEO, indexation, platform, online status and what can be done with it right now (capabilities).
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" \
-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", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"id": 123,
"name": "example",
"domain": "example.com",
"type": "Wordpress",
"state": "Ok",
"state_group": "ok",
"url": "https://example.com",
"created_at": "2026-01-05T12:00:00Z",
"group": {
"id": 7,
"name": "Travel"
},
"cdn": "Cloudflare",
"php_version": "PHP 8.3",
"use_https": true,
"use_www": false,
"frozen": false,
"switched_off_by_staff": false,
"suspended": false,
"pending_delete": false,
"main_cloud": false,
"indexed": "yes",
"last_indexed_at": "2026-09-10T03:00:00Z",
"last_not_indexed_at": null,
"domain_expires_at": "2027-03-01T00:00:00Z",
"seo": {
"trust_flow": 12,
"citation_flow": 20,
"backlinks": 340,
"referring_domains": 41,
"trust_flow_change": 1,
"citation_flow_change": 0,
"backlinks_change": 12,
"referring_domains_change": 2,
"measured_at": "2026-09-15T02:00:00Z"
},
"platform": {
"type": "Wordpress",
"label": "WordPress",
"detected": "wordpress",
"mismatch": null,
"checked_at": "2026-09-19T04:00:00Z"
},
"online": {
"status": "online",
"label": "Online",
"reason": null,
"since": null,
"checked_at": "2026-09-19T20:00:00Z",
"text": "Homepage answered normally (last check ...)."
},
"title": "My blog",
"subtitle": null,
"login_url": "wp-login.php",
"admin_email": "[email protected]",
"autoupdate_wordpress": true,
"ssl_mode": "full",
"mailbox_requested": false,
"freeze": {
"frozen": false,
"reasons": [],
"temporary_unfreeze": null
},
"nameservers": {
"current": [
"ada.ns.cloudflare.com",
"bob.ns.cloudflare.com"
],
"required": [
"ada.ns.cloudflare.com",
"bob.ns.cloudflare.com"
],
"pointed": true,
"main_cloud": false,
"zone_expired": false,
"dns_provider": "Cloudflare",
"checked_at": "2026-09-19T13:25:00Z",
"autopilot": null
},
"cdn_detail": {
"provider": "Cloudflare",
"https": true,
"www": false,
"own_account": false,
"https_only": true
},
"capabilities": {
"edit": true,
"delete": true,
"backup": true,
"restore": true,
"reinstall": true,
"purge_cache": true,
"admin_login": true,
"files": true,
"error_log": true,
"dns_records": true
},
"backups_count": 4,
"panel_url": "https://app.pbn.ltd/sites/123",
"updated_at": "2026-09-19T10:00:00Z"
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized
Fields and choices for a new site
GET/api/v1/sites/optionsScope sites:write
The fields the Create site form accepts for this account right now, with their choices (site types, CDNs, PHP versions, templates, blueprints, groups...) and defaults. Build a create screen from it; every field name here is accepted by POST /sites (unknown ones go in form_fields).
Example
curl -s "https://app.pbn.ltd/api/v1/sites/options" \
-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/options", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/options", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"fields": [
{
"name": "type",
"form_field": "type",
"label": "Type",
"required": true,
"help": null,
"kind": "typedchoice",
"choices": [
{
"value": "Wordpress",
"label": "WordPress"
}
],
"default": "Wordpress"
}
]
}
}
Errors: rate_limited, scope_missing, unauthorized
Create a site (any type)
POST/api/v1/sitesScope sites:write · returns a job
Creates a site exactly as the Add new site form does - same validation, same slot limit, same checks - for every type: WordPress, Static HTML, PHP hosting, Joomla, Drupal, PrestaShop, OpenCart, Grav and MediaWiki. Installation runs in the background: follow the returned job (GET /jobs/{job_id}) or the site state. Once installed, point the domain at nameservers.required from GET /sites/{site_id}.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
type | body | string (one of: Wordpress, Static HTML, PHP hosting, Joomla, Drupal, PrestaShop, OpenCart, Grav, MediaWiki) | yes | Site type. |
name | body | string | yes | A unique short name (letters, digits, dashes). |
domain | body | string | yes | The domain (or subdomain of one of your sites). |
cdn | body | string | no | CDN (see GET /sites/options for the choices open to you). |
php_version | body | string | no | PHP version value, e.g. "PHP 8.3" (see GET /sites/options). |
use_https | body | boolean | no | Serve over HTTPS. |
use_www | body | boolean | no | Use www. as the primary host. |
title | body | string | no | Site title (WordPress and the ready-installed applications). |
subtitle | body | string | no | Tagline (WordPress). |
admin_email | body | string | no | Administrator e-mail (WordPress and the applications). |
login_url | body | string | no | WordPress login path (default wp-login.php). |
template_id | body | integer | no | WordPress template id (omit for random). |
blueprint_id | body | integer | no | Deploy from one of your blueprints (WordPress). |
group_id | body | integer | no | Put the site in this group. |
create_mailbox | body | boolean | no | Create an e-mail account for the domain. |
autoupdate_wordpress | body | boolean | no | Auto-update WordPress core and plugins. |
form_fields | body | object | no | Any other Create site form field by its form name (see form_field in GET /sites/options), e.g. the WordPress theme/plugin pickers. |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/sites" \
-H "Authorization: Bearer $PBN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "Wordpress", "name": "myblog", "domain": "myblog-example.com", "cdn": "Cloudflare", "use_https": true, "title": "My blog", "subtitle": "Notes from the workshop", "admin_email": "[email protected]", "form_fields": {"feedback_email": "[email protected]"}}'
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites", headers=headers, json={"type": "Wordpress", "name": "myblog", "domain": "myblog-example.com", "cdn": "Cloudflare", "use_https": True, "title": "My blog", "subtitle": "Notes from the workshop", "admin_email": "[email protected]", "form_fields": {"feedback_email": "[email protected]"}}, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
body: JSON.stringify({"type": "Wordpress", "name": "myblog", "domain": "myblog-example.com", "cdn": "Cloudflare", "use_https": true, "title": "My blog", "subtitle": "Notes from the workshop", "admin_email": "[email protected]", "form_fields": {"feedback_email": "[email protected]"}})
});
console.log(res.status, await res.json());
Response
202
{
"data": {
"id": 124,
"domain": "new-example.com",
"state": "New",
"...": "(the site, as GET /sites/{site_id})"
},
"job": {
"id": "job_4f1c0a9e2b7d6c5a3e10",
"kind": "...",
"status": "running",
"url": "https://app.pbn.ltd/api/v1/jobs/job_4f1c0a9e2b7d6c5a3e10"
}
}
Errors: conflict, payment_required, rate_limited, scope_missing, unauthorized, validation_failed
Fields and choices to edit a site
GET/api/v1/sites/{site_id}/optionsScope sites:write
The fields the Edit site form offers for THIS site right now (they depend on type, CDN and state), with choices and current values.
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/options" \
-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/options", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/options", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"fields": [
{
"name": "php_version",
"label": "Php version",
"required": true,
"choices": [
{
"value": "PHP 8.3",
"label": "PHP 8.3"
}
],
"default": "PHP 8.3"
}
]
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized
Change site settings
PATCH/api/v1/sites/{site_id}Scope sites:write
Change any setting the Edit site form offers: name, domain, PHP version, HTTPS, www, group, admin e-mail, login URL, auto-updates, mailbox. Send only what changes. Changes that touch the server (domain, PHP, HTTPS/www) run in the background: a job is returned then.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
name | body | string | no | A unique short name (letters, digits, dashes). |
domain | body | string | no | The domain (or subdomain of one of your sites). |
php_version | body | string | no | PHP version value, e.g. "PHP 8.3" (see GET /sites/options). |
use_https | body | boolean | no | Serve over HTTPS. |
use_www | body | boolean | no | Use www. as the primary host. |
admin_email | body | string | no | Administrator e-mail (WordPress and the applications). |
login_url | body | string | no | WordPress login path (default wp-login.php). |
group_id | body | integer | no | Put the site in this group. |
create_mailbox | body | boolean | no | Create an e-mail account for the domain. |
autoupdate_wordpress | body | boolean | no | Auto-update WordPress core and plugins. |
form_fields | body | object | no | Any other Create site form field by its form name (see form_field in GET /sites/options), e.g. the WordPress theme/plugin pickers. |
ssl_mode | body | string (one of: full, flexible) | no | Cloudflare SSL mode (only offered when the site has its own address records). |
Example
curl -s -X PATCH "https://app.pbn.ltd/api/v1/sites/123" \
-H "Authorization: Bearer $PBN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"php_version": "PHP 8.3", "use_www": true}'
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.patch("https://app.pbn.ltd/api/v1/sites/123", headers=headers, json={"php_version": "PHP 8.3", "use_www": True}, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123", {
method: "PATCH",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
body: JSON.stringify({"php_version": "PHP 8.3", "use_www": true})
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"id": 123,
"name": "example",
"domain": "example.com",
"type": "Wordpress",
"state": "Ok",
"state_group": "ok",
"url": "https://example.com",
"created_at": "2026-01-05T12:00:00Z",
"group": {
"id": 7,
"name": "Travel"
},
"cdn": "Cloudflare",
"php_version": "PHP 8.3",
"use_https": true,
"use_www": false,
"frozen": false,
"switched_off_by_staff": false,
"suspended": false,
"pending_delete": false,
"main_cloud": false,
"indexed": "yes",
"last_indexed_at": "2026-09-10T03:00:00Z",
"last_not_indexed_at": null,
"domain_expires_at": "2027-03-01T00:00:00Z",
"seo": {
"trust_flow": 12,
"citation_flow": 20,
"backlinks": 340,
"referring_domains": 41,
"trust_flow_change": 1,
"citation_flow_change": 0,
"backlinks_change": 12,
"referring_domains_change": 2,
"measured_at": "2026-09-15T02:00:00Z"
},
"platform": {
"type": "Wordpress",
"label": "WordPress",
"detected": "wordpress",
"mismatch": null,
"checked_at": "2026-09-19T04:00:00Z"
},
"online": {
"status": "online",
"label": "Online",
"reason": null,
"since": null,
"checked_at": "2026-09-19T20:00:00Z",
"text": "Homepage answered normally (last check ...)."
}
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized, validation_failed
Delete a site
DELETE/api/v1/sites/{site_id}Scope sites:delete · destructive
Deletes the site for good (files, database, DNS zone, CDN), exactly like Delete site in the panel. Refused while the site is busy or paused for non-payment/malware, while it has subdomain sites, or while a blueprint is being made from it. Runs in the background: a job is returned.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
Example
curl -s -X DELETE "https://app.pbn.ltd/api/v1/sites/123" \
-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", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123", {
method: "DELETE",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
202
{
"data": {
"id": 123,
"deleting": true
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized