PBN.LTD API docs
View as Markdown

Hourly backups

The hourly backups add-on of a site: its restore points, a backup now, rolling the site back to an hour, and downloading one.

Hourly backups of a site

GET/api/v1/sites/{site_id}/hourly-backups

Scope backups:read

The same as the hourly part of the site's Backups tab: whether hourly backups are on for this site (and paid until when), when the next one runs, every restore point kept, any roll-back in progress and the downloads being prepared. Switching the add-on on is a purchase made in the Backups tab (manage_url); it is never bought over the API.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).

Example

curl -s "https://app.pbn.ltd/api/v1/sites/123/hourly-backups" \
  -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/hourly-backups", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/hourly-backups", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());

Response

200

{
  "data": {
    "active": true,
    "available": true,
    "price_per_month": "4.00",
    "retention": "every hour for 48 hours, then daily",
    "paid_until": "2026-10-25",
    "next_run": "2026-09-26T18:00:00Z",
    "last_ok": "2026-09-26T17:00:12Z",
    "last_error": "",
    "restoring": false,
    "restore_state": "",
    "count": 1,
    "backups": [
      {
        "id": 9120,
        "taken_at": "2026-09-26T17:00:12Z",
        "kind": "hourly",
        "bytes": 48211093,
        "database": true
      }
    ],
    "downloads": [
      {
        "id": 311,
        "backup_id": 9120,
        "state": "ready",
        "bytes": 48211093,
        "error": ""
      }
    ],
    "manage_url": "https://app.pbn.ltd/site/123#backups"
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

Take an hourly backup now

POST/api/v1/sites/{site_id}/hourly-backups/now

Scope backups:write

Takes a restore point now, as the "Back up now" button (once per site every 10 minutes). It appears in GET /sites/{site_id}/hourly-backups within a minute or two.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).

Example

curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/hourly-backups/now" \
  -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/hourly-backups/now", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/hourly-backups/now", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());

Response

202

{
  "data": {
    "queued": true,
    "message": "Taking a backup now."
  }
}

Errors: conflict, not_found, rate_limited, scope_missing, unauthorized

Roll the site back to a restore point

POST/api/v1/sites/{site_id}/hourly-backups/{backup_id}/restore

Scope backups:write · destructive

Rolls the site back to exactly how it was at that restore point, as the tab's "Restore this backup" dialog. A backup of how the site looks now is taken first. One roll-back per site at a time; you get an e-mail when it is done.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
backup_idpathintegeryesThe restore point id (see GET /sites/{site_id}/hourly-backups).
whatbodystring (one of: both, files, db)noFiles and database (default), files only, or database only. Default: both.
confirmbodybooleanyesMust be true: the site is rolled back to that hour.

Example

curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/hourly-backups/88001/restore" \
  -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/hourly-backups/88001/restore", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/hourly-backups/88001/restore", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());

Response

202

{
  "data": {
    "queued": true,
    "restore_id": 77,
    "message": "Rolling the site back now."
  }
}

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

Prepare a download of a restore point

POST/api/v1/sites/{site_id}/hourly-backups/{backup_id}/download

Scope backups:read

Builds a .tar.gz of that restore point (files and database), as the tab's Download button. Poll GET /sites/{site_id}/hourly-backups until the download reads "ready", then fetch it with GET /sites/{site_id}/hourly-backups/downloads/{download_id}. Kept for 24 hours.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
backup_idpathintegeryesThe restore point id (see GET /sites/{site_id}/hourly-backups).

Example

curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/hourly-backups/88001/download" \
  -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/hourly-backups/88001/download", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/hourly-backups/88001/download", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());

Response

202

{
  "data": {
    "download_id": 311,
    "state": "pending",
    "message": "Preparing the download."
  }
}

Errors: conflict, not_found, rate_limited, scope_missing, unauthorized

Download a prepared restore point

GET/api/v1/sites/{site_id}/hourly-backups/downloads/{download_id}

Scope backups:read

The .tar.gz archive, once the download reads "ready". Not offered to AI assistants (it is a file, not an answer).

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
download_idpathintegeryesThe download id from the prepare call.

Example

curl -s "https://app.pbn.ltd/api/v1/sites/123/hourly-backups/downloads/download_id" \
  -H "Authorization: Bearer $PBN_API_KEY" \
  -o invoice.pdf
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/hourly-backups/downloads/download_id", headers=headers, timeout=120)
r.raise_for_status()
open("invoice.pdf", "wb").write(r.content)
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/hourly-backups/downloads/download_id", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
const pdf = Buffer.from(await res.arrayBuffer());  // Node 18+

Response

200 with the file (application/gzip).

Errors: conflict, not_found, rate_limited, scope_missing, unauthorized