PBN.LTD API docs
View as Markdown

Staging

Staging sites: a private copy of a live WordPress or Static HTML site to try changes on, then copy to the live site (or refresh from it).

Staging: add-on, slots and price

GET/api/v1/staging

Scope staging:read

Whether staging is on for your account, until when it is paid, how many staging sites you have and may have at once, the size limits, and the monthly price (before VAT, from the live price row) with the page where you buy or renew it. Buying happens in the browser: the API never starts a payment.

Example

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

Response

200

{
  "data": {
    "available": true,
    "active": true,
    "included_with_staff_account": false,
    "paid_until": "2026-12-01",
    "used": 2,
    "limit": 10,
    "room": 8,
    "limits": {
      "max_files_gb": 10.0,
      "max_db_gb": 2.0
    },
    "price_usd_per_month": "9.00",
    "vat_percent": 0.0,
    "months_offered": [
      1,
      3,
      6,
      12
    ],
    "payment_methods": [
      "paypal",
      "crypto"
    ],
    "can_buy": true,
    "checkout_url": "https://app.pbn.ltd/staging/",
    "supported_site_types": [
      "Wordpress",
      "Static HTML"
    ]
  }
}

Errors: rate_limited, scope_missing, unauthorized

List staging sites

GET/api/v1/staging/sites

Scope staging:read

Every staging site in your account that exists now (being created, ready, copying, paused, needs attention or being removed), newest first, with its last copy job. Poll it while a job runs: the state goes back to "ready" when a copy is done.

Example

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

Response

200

{
  "data": [
    {
      "id": 71,
      "site_id": 12345,
      "domain": "example.com",
      "type": "Wordpress",
      "label": "redesign",
      "state": "ready",
      "state_label": "Ready",
      "url": "https://stg71-3f9a1c2b4d5e.srv7.previewhost.net",
      "open_url": "https://app.pbn.ltd/staging/71/open",
      "admin_login_url": "https://app.pbn.ltd/staging/71/wp-login",
      "sftp_on": false,
      "files_mb": 412,
      "db_mb": 38,
      "last_copy_at": "2026-09-24T10:00:00Z",
      "error": "",
      "created_at": "2026-09-24T09:40:00Z",
      "last_job": {
        "kind": "to_staging",
        "what": "full",
        "state": "done",
        "error": "",
        "created_at": "2026-09-24T09:58:00Z",
        "finished_at": "2026-09-24T10:00:00Z"
      }
    }
  ]
}

Errors: rate_limited, scope_missing, unauthorized

One staging site

GET/api/v1/staging/sites/{staging_id}

Scope staging:read

One staging site with its last ten jobs (create, copy to live, copy from live, remove).

Parameters

NameInTypeRequiredDescription
staging_idpathintegeryesThe staging site id (see staging.list).

Example

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

Response

200

{
  "data": {
    "id": 71,
    "site_id": 12345,
    "domain": "example.com",
    "type": "Wordpress",
    "label": "redesign",
    "state": "ready",
    "state_label": "Ready",
    "url": "https://stg71-3f9a1c2b4d5e.srv7.previewhost.net",
    "open_url": "https://app.pbn.ltd/staging/71/open",
    "admin_login_url": "https://app.pbn.ltd/staging/71/wp-login",
    "sftp_on": false,
    "files_mb": 412,
    "db_mb": 38,
    "last_copy_at": "2026-09-24T10:00:00Z",
    "error": "",
    "created_at": "2026-09-24T09:40:00Z",
    "last_job": {
      "kind": "to_staging",
      "what": "full",
      "state": "done",
      "error": "",
      "created_at": "2026-09-24T09:58:00Z",
      "finished_at": "2026-09-24T10:00:00Z"
    },
    "jobs": [
      {
        "kind": "to_staging",
        "what": "full",
        "state": "done",
        "error": "",
        "created_at": "2026-09-24T09:58:00Z",
        "finished_at": "2026-09-24T10:00:00Z"
      }
    ]
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

Which sites can be staged

GET/api/v1/staging/eligible

Scope staging:read

Your live sites and, for each, whether a staging copy can be made of it now and, when not, the reason in plain words (only WordPress and Static HTML sites that are running).

Example

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

Response

200

{
  "data": [
    {
      "site_id": 12345,
      "domain": "example.com",
      "type": "Wordpress",
      "ok": true,
      "why": ""
    }
  ]
}

Errors: rate_limited, scope_missing, unauthorized

Create a staging site

POST/api/v1/staging/sites

Scope staging:write

Makes a private staging copy of one of your live sites (files and database). It takes a few minutes for a typical site: poll staging.get until the state is "ready". Needs the staging add-on (staging.status) and a free slot; the live site is not changed.

Parameters

NameInTypeRequiredDescription
site_idbodyintegeryesThe live site to copy (one of yours; see staging.eligible).
labelbodystringnoAn optional name for this copy (60 characters).

Example

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

Response

202

{
  "data": {
    "id": 71,
    "site_id": 12345,
    "domain": "example.com",
    "type": "Wordpress",
    "label": "redesign",
    "state": "creating",
    "state_label": "Being created",
    "url": null,
    "open_url": null,
    "admin_login_url": null,
    "sftp_on": false,
    "files_mb": 412,
    "db_mb": 38,
    "last_copy_at": "2026-09-24T10:00:00Z",
    "error": "",
    "created_at": "2026-09-24T09:40:00Z",
    "last_job": {
      "kind": "create",
      "what": "full",
      "state": "queued",
      "error": "",
      "created_at": "2026-09-24T09:40:00Z",
      "finished_at": null
    }
  }
}

Errors: forbidden, rate_limited, scope_missing, unauthorized, unavailable, validation_failed

Copy staging to the live site

POST/api/v1/staging/sites/{staging_id}/copy-to-live

Scope staging:write · destructive

Publishes the staging copy: OVERWRITES the live site's files and/or database with the staging ones. An undo copy of what it replaced is kept for 7 days (ask support to put it back). Needs "confirm": true. Poll staging.get until the state is "ready" again.

Parameters

NameInTypeRequiredDescription
staging_idpathintegeryesThe staging site id (see staging.list).
whatbodystring (one of: full, db, files)noWhat to copy: full (files and database), db (database only, WordPress) or files (files only). Default: full.
confirmbodybooleanyesMust be true.

Example

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

Response

202

{
  "data": {
    "id": 71,
    "site_id": 12345,
    "domain": "example.com",
    "type": "Wordpress",
    "label": "redesign",
    "state": "busy",
    "state_label": "Copying",
    "url": "https://stg71-3f9a1c2b4d5e.srv7.previewhost.net",
    "open_url": "https://app.pbn.ltd/staging/71/open",
    "admin_login_url": "https://app.pbn.ltd/staging/71/wp-login",
    "sftp_on": false,
    "files_mb": 412,
    "db_mb": 38,
    "last_copy_at": "2026-09-24T10:00:00Z",
    "error": "",
    "created_at": "2026-09-24T09:40:00Z",
    "last_job": {
      "kind": "to_staging",
      "what": "full",
      "state": "done",
      "error": "",
      "created_at": "2026-09-24T09:58:00Z",
      "finished_at": "2026-09-24T10:00:00Z"
    }
  }
}

Errors: forbidden, not_found, rate_limited, scope_missing, unauthorized, validation_failed

Refresh staging from the live site

POST/api/v1/staging/sites/{staging_id}/copy-from-live

Scope staging:write · destructive

Copies the live site over the staging copy (files and/or database), replacing any changes made on staging. The live site is not changed. Needs "confirm": true.

Parameters

NameInTypeRequiredDescription
staging_idpathintegeryesThe staging site id (see staging.list).
whatbodystring (one of: full, db, files)noWhat to copy: full (files and database), db (database only, WordPress) or files (files only). Default: full.
confirmbodybooleanyesMust be true.

Example

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

Response

202

{
  "data": {
    "id": 71,
    "site_id": 12345,
    "domain": "example.com",
    "type": "Wordpress",
    "label": "redesign",
    "state": "busy",
    "state_label": "Copying",
    "url": "https://stg71-3f9a1c2b4d5e.srv7.previewhost.net",
    "open_url": "https://app.pbn.ltd/staging/71/open",
    "admin_login_url": "https://app.pbn.ltd/staging/71/wp-login",
    "sftp_on": false,
    "files_mb": 412,
    "db_mb": 38,
    "last_copy_at": "2026-09-24T10:00:00Z",
    "error": "",
    "created_at": "2026-09-24T09:40:00Z",
    "last_job": {
      "kind": "to_staging",
      "what": "full",
      "state": "done",
      "error": "",
      "created_at": "2026-09-24T09:58:00Z",
      "finished_at": "2026-09-24T10:00:00Z"
    }
  }
}

Errors: forbidden, not_found, rate_limited, scope_missing, unauthorized, validation_failed

Remove a staging site

DELETE/api/v1/staging/sites/{staging_id}

Scope staging:write · destructive

Removes the staging copy (its files, database and address). The live site is not touched and the slot is free again straight away. Needs "confirm": true.

Parameters

NameInTypeRequiredDescription
staging_idpathintegeryesThe staging site id (see staging.list).
confirmbodybooleanyesMust be true.

Example

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

Response

202

{
  "data": {
    "id": 71,
    "site_id": 12345,
    "domain": "example.com",
    "type": "Wordpress",
    "label": "redesign",
    "state": "destroying",
    "state_label": "Being removed",
    "url": "https://stg71-3f9a1c2b4d5e.srv7.previewhost.net",
    "open_url": "https://app.pbn.ltd/staging/71/open",
    "admin_login_url": "https://app.pbn.ltd/staging/71/wp-login",
    "sftp_on": false,
    "files_mb": 412,
    "db_mb": 38,
    "last_copy_at": "2026-09-24T10:00:00Z",
    "error": "",
    "created_at": "2026-09-24T09:40:00Z",
    "last_job": {
      "kind": "to_staging",
      "what": "full",
      "state": "done",
      "error": "",
      "created_at": "2026-09-24T09:58:00Z",
      "finished_at": "2026-09-24T10:00:00Z"
    }
  }
}

Errors: forbidden, not_found, rate_limited, scope_missing, unauthorized, validation_failed

Switch on SFTP / reset its password

POST/api/v1/staging/sites/{staging_id}/sftp

Scope staging:write

SFTP for one staging site. The first call switches it on and returns the login WITH a new password; later calls return the login with "password": "" (it is shown only once and never stored). Send "reset": true to set a new password (the old one stops working).

Parameters

NameInTypeRequiredDescription
staging_idpathintegeryesThe staging site id (see staging.list).
resetbodybooleannotrue = make a NEW password (the old one stops working). Default: False.

Example

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

Response

200

{
  "data": {
    "host": "sftp.example.com",
    "port": 2222,
    "user": "zg71",
    "password": "shown-once-only",
    "shown_once": true,
    "note": "Copy it now: for your security it is shown only this once."
  }
}

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

The share login of a staging site

POST/api/v1/staging/sites/{staging_id}/access

Scope staging:write

The username and password that protect the staging site, to share it with someone who has no account (the same "Password to share" the staging page shows the account holder). Account holder only.

Parameters

NameInTypeRequiredDescription
staging_idpathintegeryesThe staging site id (see staging.list).

Example

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

Response

200

{
  "data": {
    "url": "https://stg71-3f9a1c2b4d5e.srv7.previewhost.net",
    "user": "staging",
    "password": "the-share-password"
  }
}

Errors: forbidden, not_found, rate_limited, scope_missing, unauthorized

Cancel the staging add-on

POST/api/v1/staging/cancel

Scope staging:write · destructive

Ends the staging add-on now and removes EVERY staging site of the account. Months already paid are not refunded. Your live sites are not affected. Needs "confirm": true.

Parameters

NameInTypeRequiredDescription
confirmbodybooleanyesMust be true.

Example

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

Response

200

{
  "data": {
    "cancelled": 1,
    "message": "Staging is cancelled and your staging sites are being removed."
  }
}

Errors: forbidden, rate_limited, scope_missing, unauthorized, validation_failed