# Site tools

Cache purge, one-click admin login, reinstall, health checks, Site Cleaner, usage.

## Change the PHP version

`PUT /sites/{site_id}/php-version` · scope `sites:write`

Shortcut for PATCH /sites/{site_id} with php_version. The web server is reconfigured in the background (a job is returned).

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `php_version` | body | string | yes | e.g. "PHP 8.3" (see GET /sites/{site_id}/options) |

### Example

```bash
curl -s -X PUT "https://app.pbn.ltd/api/v1/sites/123/php-version" \
  -H "Authorization: Bearer $PBN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"php_version": "PHP 8.3"}'
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.put("https://app.pbn.ltd/api/v1/sites/123/php-version", headers=headers, json={"php_version": "PHP 8.3"}, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/php-version", {
  method: "PUT",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
  body: JSON.stringify({"php_version": "PHP 8.3"})
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "php_version": "PHP 8.3",
    "state": "Creating webserver config"
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Disk, database and file usage

`GET /sites/{site_id}/usage` · scope `sites:read`

The Usage & add-ons tab: disk, database and file-count use against the limits (plan + add-ons), what is over, CDN bandwidth for metered CDNs, and the add-ons on the site. Numbers are measured every few hours; POST /sites/{site_id}/usage/refresh measures now.

### 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/usage" \
  -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/usage", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/usage", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "site_id": 123,
    "state": "Ok",
    "frozen": false,
    "over": [],
    "disk": {
      "used_mib": 310,
      "limit_mib": 1024,
      "included_mib": 1024,
      "addon_mib": 0,
      "percent": 30,
      "over": false,
      "measured_at": "2026-09-19T18:00:00+00:00"
    },
    "db": {
      "used_mib": 40,
      "limit_mib": 500
    },
    "inodes": {
      "used": 8200,
      "limit": 50000
    },
    "bandwidth": null,
    "addons": [],
    "busy": false
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Measure usage now

`POST /sites/{site_id}/usage/refresh` · scope `sites:write`

Starts a fresh measurement of disk, database and files (the usage tab's "Check now"). Returns the current figures with busy=true; poll GET /sites/{site_id}/usage until busy is false (seconds). Same throttles as the panel: one measurement per site every few minutes.

### 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/usage/refresh" \
  -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/usage/refresh", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/usage/refresh", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "busy": true,
    "disk": {
      "used_mib": 310
    }
  }
}
```

Errors: `conflict`, `not_found`, `rate_limited`, `scope_missing`, `unauthorized`, `unavailable`

## Purge the CDN cache

`POST /sites/{site_id}/purge-cache` · scope `sites:write`

Clears the CDN cache of both hostnames (as Purge CDN cache in the panel). The site must be live.

### 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/purge-cache" \
  -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/purge-cache", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/purge-cache", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "purged": true
  }
}
```

Errors: `conflict`, `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Reinstall from scratch

`POST /sites/{site_id}/reinstall` · scope `sites:write` · **destructive**

Installs the site again from scratch (as Reinstall in the panel) - the current files and database are replaced. Refused while the site is busy, switched off or being deleted.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `confirm` | body | boolean | yes | Must be true: this action overwrites the site. |

### Example

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/reinstall" \
  -H "Authorization: Bearer $PBN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"confirm": true}'
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/reinstall", headers=headers, json={"confirm": True}, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/reinstall", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
  body: JSON.stringify({"confirm": true})
});
console.log(res.status, await res.json());
```

### Response

`202`

```json
{
  "data": {
    "reinstalling": true
  }
}
```

Errors: `conflict`, `not_found`, `rate_limited`, `scope_missing`, `unauthorized`, `validation_failed`

## One-click admin login link

`POST /sites/{site_id}/admin-login` · scope `sites:login`

A single-use login link to the site's admin (WordPress wp-admin, Joomla, Drupal, PrestaShop, OpenCart, Grav, MediaWiki), valid for 60 seconds - the panel's one-click login. Open it in a browser; do not store it. Not for Static HTML / PHP hosting sites.

### 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/admin-login" \
  -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/admin-login", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/admin-login", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "url": "https://example.com/?ltoken=0f3c...",
    "expires_in_seconds": 60,
    "single_use": true
  }
}
```

Errors: `conflict`, `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Health status

`GET /sites/{site_id}/health` · scope `sites:read`

The online badge (online / offline / slow / paused... with the reason) and, for application sites, the last integrity check (V1 config lines, admin account, login health, core files).

### 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/health" \
  -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/health", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/health", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "site_id": 123,
    "online": {
      "status": "online",
      "label": "Online"
    },
    "integrity": {
      "checked": "19 Sep 2026 20:00 UTC",
      "status": "ok",
      "findings": [],
      "problems": 0
    }
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Re-check if the site is up

`POST /sites/{site_id}/health/recheck` · scope `sites:write`

Probes the home page again now (same probe and recording as the platform's monitor) and asks the server why if it fails. Asynchronous: follow the job; its result has the outcome. At most 10 an hour.

### 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/health/recheck" \
  -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/health/recheck", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/health/recheck", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`202`

```json
{
  "data": {
    "started": true
  }
}
```

Errors: `conflict`, `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Integrity check now

`POST /sites/{site_id}/health/integrity-check` · scope `sites:write`

Checks the application now (WordPress, Joomla, Drupal, PrestaShop, OpenCart, Grav, MediaWiki): V1's config lines, the site URL, the admin account, one-click login health and core file checksums. Changes nothing. Takes a few seconds.

### 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/health/integrity-check" \
  -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/health/integrity-check", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/health/integrity-check", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "checked": "20 Sep 2026 08:00 UTC",
    "status": "ok",
    "findings": [],
    "problems": 0,
    "message": "No problems found."
  }
}
```

Errors: `conflict`, `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Repair the site

`POST /sites/{site_id}/health/repair` · scope `sites:write`

"Repair site": checks, repairs everything repairable (backups first, on the server), checks again. You get an e-mail listing what was repaired.

### 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/health/repair" \
  -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/health/repair", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/health/repair", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "status": "ok",
    "fixed": [
      "WordPress core files restored"
    ],
    "message": "Repaired."
  }
}
```

Errors: `conflict`, `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Switch a paused site on for cleanup

`POST /sites/{site_id}/temporary-unfreeze` · scope `sites:write`

A site paused ONLY for a usage limit (disk / database / files) comes back for 30 minutes so you can clean it up (3 times per site per day). At the end it is paused again only if still over the limit. freeze.temporary_unfreeze in GET /sites/{site_id} says if it is available and why not.

### 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/temporary-unfreeze" \
  -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/temporary-unfreeze", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/temporary-unfreeze", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "active": true,
    "ends_at": "2026-09-20T09:30:00+00:00",
    "message": "example.com is coming back online for 30 minutes..."
  }
}
```

Errors: `conflict`, `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Site Cleaner status

`GET /sites/{site_id}/cleaner` · scope `sites:read`

Site Cleaner for this site: whether the add-on is active, the last scan (what can be removed and how much it saves), the running job, recent jobs (with undo_available) and the schedule.

### 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/cleaner" \
  -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/cleaner", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/cleaner", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "site_id": 123,
    "cms": "wordpress",
    "usable": true,
    "why_not": null,
    "addon_active": true,
    "schedule": "off",
    "last_scan": {
      "items": [
        {
          "id": "plugin:hello-dolly",
          "cat": "plugins",
          "name": "Hello Dolly",
          "mb": 0.1,
          "default": true
        }
      ],
      "at": "2026-09-19T10:00:00Z"
    },
    "running": null,
    "recent_jobs": [],
    "buy_url": "https://app.pbn.ltd/site-cleaner/"
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Scan, clean or undo

`POST /sites/{site_id}/cleaner/{action}` · scope `sites:write`

Runs Site Cleaner. `scan` lists unused plugins/themes, junk files and database clutter (free). `clean` removes the selected items after taking a backup and checks the site afterwards - it needs the Site Cleaner add-on (402 addon_required otherwise). `undo` puts a clean back from its backup.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `action` | path | string (one of: scan, clean, undo) | yes | scan (free) / clean (needs the Site Cleaner add-on) / undo a clean. |
| `items` | body | array | no | clean: ids from last_scan.items to remove (omit = the default selection). |
| `job_id` | body | integer | no | undo: the id of the clean job to undo. |

### Example

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/cleaner/scan" \
  -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/cleaner/scan", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/cleaner/scan", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`202`

```json
{
  "data": {
    "cleaner_job": {
      "id": 991,
      "kind": "scan",
      "state": "queued"
    }
  }
}
```

Errors: `conflict`, `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`, `validation_failed`

## Automatic cleaning

`PUT /sites/{site_id}/cleaner/schedule` · scope `sites:write`

Sets automatic cleaning for the site (needs the add-on to actually clean).

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `schedule` | body | string (one of: off, daily, weekly, monthly) | yes | How often Site Cleaner runs by itself. |

### Example

```bash
curl -s -X PUT "https://app.pbn.ltd/api/v1/sites/123/cleaner/schedule" \
  -H "Authorization: Bearer $PBN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"schedule": "weekly"}'
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.put("https://app.pbn.ltd/api/v1/sites/123/cleaner/schedule", headers=headers, json={"schedule": "weekly"}, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/cleaner/schedule", {
  method: "PUT",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
  body: JSON.stringify({"schedule": "weekly"})
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "schedule": "weekly",
    "text": "weekly"
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`
