PBN.LTD API docs
View as Markdown

Money sites

Hosting on Zinn Digital's platform: plans, orders, sites, usage, and the free WooCommerce tick on a PBN-line WordPress site.

Money-site plans and prices

GET/api/v1/money-sites/plans

Scope moneysites:read

Every money-site hosting plan on sale - Managed WordPress hosting (Start to Enterprise; any of them can be ordered with WooCommerce installed and set up, ready to sell), application hosting (each application's Business and/or Pro tier), PHP web hosting, developer hosting and LMS hosting - with the price for 1, 3, 6 and 12 months, what the plan includes, and which terms Zinn Digital takes online (online_terms; any other term is set up by our team, who send the payment link). Money sites run on Zinn Digital's platform - separate infrastructure from the PBN network - and Zinn Digital LTD bills them: pay by card, PayPal or crypto.

Parameters

NameInTypeRequiredDescription
linequerystring (one of: wordpress, applications, web, developer, lms, all)nowordpress = Managed WordPress hosting (Start to Enterprise, with the "Install WooCommerce" option); applications = application hosting (every application's Business and/or Pro tier); web = PHP web hosting (an empty PHP account or a static site); developer = Node.js / Python / Ruby apps from Git; lms = LMS hosting (WordPress LMS or Moodle); all = every line. Default: all.

Example

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

Response

200

{
  "data": {
    "currency": "USD",
    "terms": [
      1,
      3,
      6,
      12
    ],
    "can_order": false,
    "plans": [
      {
        "code": "mainstream_starter",
        "name": "WordPress Starter",
        "line": "wordpress",
        "line_name": "Managed WordPress hosting",
        "application": "WordPress",
        "monthly": "7.99 USD",
        "currency": "USD",
        "on_sale": true,
        "woocommerce_tick": true,
        "terms": [
          {
            "months": 1,
            "total": "7.99 USD",
            "per_month": "7.99 USD",
            "saving": "0.00 USD"
          },
          {
            "months": 12,
            "total": "76.70 USD",
            "per_month": "6.39 USD",
            "saving": "19.18 USD"
          }
        ],
        "includes": [
          {
            "label": "Websites",
            "value": "1"
          },
          {
            "label": "Disk",
            "value": "10 GB"
          }
        ]
      }
    ],
    "applications": [
      {
        "key": "drupal",
        "name": "Drupal",
        "from": "7.99 USD",
        "plans": [
          "app_drupal_business",
          "app_drupal_pro"
        ]
      }
    ]
  }
}

Errors: rate_limited, scope_missing, unauthorized

More hosting from Zinn Digital

GET/api/v1/money-sites/zinn-digital

Scope moneysites:read

The other hosting lines of our sister company Zinn Digital - agency, reseller, enterprise, cloud, AI, developer, LMS and mail hosting - each with its live "from" price (USD a month, read from Zinn Digital's catalogue) and its sign-up page. These are links only: the account, billing and support for them are at Zinn Digital.

Example

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

Response

200

{
  "data": {
    "lines": [
      {
        "key": "agency",
        "title": "Agency hosting",
        "from": "49.00 USD",
        "url": "https://zinndigital.com/hosting/agency?utm_source=pbn.ltd&..."
      }
    ]
  }
}

Errors: rate_limited, scope_missing, unauthorized

My money sites

GET/api/v1/money-sites

Scope moneysites:read

Every money site on this account, with its state, plan, application and last known usage.

Example

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

Response

200

{
  "data": [
    {
      "id": 7,
      "domain": "example.com",
      "state": "live",
      "state_label": "Live",
      "application": "WordPress",
      "woocommerce": false,
      "hosted_by": "Zinn Digital LTD",
      "plan": {
        "code": "mainstream_plus",
        "name": "WordPress Plus",
        "monthly": "15.00 USD"
      },
      "usage": [
        {
          "label": "Disk",
          "used": "2,100",
          "allowed": "30,720",
          "percent": 7
        }
      ],
      "panel_url": "https://app.zinndigital.com/sites/abc123"
    }
  ]
}

Errors: rate_limited, scope_missing, unauthorized

One money site

GET/api/v1/money-sites/{money_site_id}

Scope moneysites:read

One money site in full: state, plan, hostnames, PHP version, region, usage against the plan's allowances, recent jobs and errors, and the links for the few jobs that are finished in the Zinn Digital panel.

Parameters

NameInTypeRequiredDescription
money_site_idpathintegeryesThe money site id (see GET /money-sites).

Example

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

Response

200

{
  "data": {
    "id": 7,
    "domain": "example.com",
    "state": "live",
    "state_label": "Live",
    "application": "WordPress",
    "woocommerce": false,
    "hosted_by": "Zinn Digital LTD",
    "plan": {
      "code": "mainstream_plus",
      "name": "WordPress Plus",
      "monthly": "15.00 USD"
    },
    "usage": [
      {
        "label": "Disk",
        "used": "2,100",
        "allowed": "30,720",
        "percent": 7
      }
    ],
    "panel_url": "https://app.zinndigital.com/sites/abc123"
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

Re-read everything from Zinn Digital

POST/api/v1/money-sites/refresh

Scope moneysites:read

Ask Zinn Digital for this account's sites and orders again. Use it after paying, or if a site was set up for you by our team and is not listed yet.

Example

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

Response

200

{
  "data": {
    "found": 1,
    "sites": 2
  }
}

Errors: rate_limited, scope_missing, unauthorized

My money-site orders

GET/api/v1/money-sites/orders

Scope moneysites:read

Every money-site order on this account, newest first, with its state and what it cost.

Example

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

Response

200

{
  "data": [
    {
      "id": 12,
      "state": "paying",
      "status": "Awaiting payment",
      "plan": {
        "code": "mainstream_plus",
        "name": "WordPress Plus",
        "line": "Managed WordPress hosting"
      },
      "term_months": 12,
      "domain": "example.com",
      "application": "WordPress + WooCommerce",
      "woocommerce": true,
      "total": "153.00 USD",
      "reference": "ZD-10023",
      "billed_by": "Zinn Digital LTD"
    }
  ]
}

Errors: rate_limited, scope_missing, unauthorized

One money-site order

GET/api/v1/money-sites/orders/{order_id}

Scope moneysites:read

One order, re-read from Zinn Digital if it is still open.

Parameters

NameInTypeRequiredDescription
order_idpathintegeryesThe order id.

Example

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

Response

200

{
  "data": {
    "id": 12,
    "state": "paying",
    "status": "Awaiting payment",
    "plan": {
      "code": "mainstream_plus",
      "name": "WordPress Plus",
      "line": "Managed WordPress hosting"
    },
    "term_months": 12,
    "domain": "example.com",
    "application": "WordPress + WooCommerce",
    "woocommerce": true,
    "total": "153.00 USD",
    "reference": "ZD-10023",
    "billed_by": "Zinn Digital LTD"
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

Order a money-site plan

POST/api/v1/money-sites/orders

Scope moneysites:write

Place a money-site order (a WordPress plan, optionally with WooCommerce, or an application plan). Zinn Digital LTD bills it, so the answer carries a one-time pay_url on Zinn Digital's own secure page - open it to pay by card, PayPal or crypto. Nothing is charged by this call. If Zinn Digital cannot take that order online (a term it does not price online, or its partner ordering is not open), the order is recorded as a request, a support ticket is opened, our team sends the payment link, and the answer says so plainly instead of pretending.

Parameters

NameInTypeRequiredDescription
planbodystringyesA plan code from /money-sites/plans.
monthsbodyinteger (one of: 1, 3, 6, 12)noHow many months to pay for. Default: 1.
domainbodystringnoThe domain for the site. Leave it out to start on a temporary address.
woocommercebodybooleannoWordPress plans only: deploy with WooCommerce installed and set up, ready to sell (shop, cart, checkout and account pages, store open). Ignored for application plans. Default: False.
applicationbodystringnoWeb and developer plans: what to build, one of the plan's stacks keys (web: php = an empty PHP account, the default, or static; developer: node, python or ruby). A WordPress plan installs WordPress and an application or LMS plan installs its own application, so it is not needed there.
notesbodystringnoAnything our team should know.
addonsbodyarraynoOptional add-on codes for this plan, from addons_offered on /money-sites/plans. They go on the same order and the same payment; each is priced by the server and checked to the cent before the pay_url is returned.
accept_termsbodybooleannoMust be true: it records that this account accepts the Terms and Conditions for this purchase, exactly as the tick box on the website does. Default: False.

Example

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

Response

201

{
  "data": {
    "order": {
      "id": 12,
      "state": "paying",
      "status": "Awaiting payment",
      "plan": {
        "code": "mainstream_plus",
        "name": "WordPress Plus",
        "line": "Managed WordPress hosting"
      },
      "term_months": 12,
      "domain": "example.com",
      "application": "WordPress + WooCommerce",
      "woocommerce": true,
      "total": "153.00 USD",
      "reference": "ZD-10023",
      "billed_by": "Zinn Digital LTD"
    },
    "pay_url": "https://zinndigital.com/pay/...",
    "requested": false,
    "ticket_id": null
  }
}

Errors: conflict, rate_limited, scope_missing, unauthorized, unavailable

Cancel an unpaid money-site order

POST/api/v1/money-sites/orders/{order_id}/cancel

Scope moneysites:write · destructive

Cancel an order that has not been paid. Nothing was charged, so nothing is refunded.

Parameters

NameInTypeRequiredDescription
order_idpathintegeryesThe order id.

Example

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

Response

200

{
  "data": {
    "id": 12,
    "state": "paying",
    "status": "Awaiting payment",
    "plan": {
      "code": "mainstream_plus",
      "name": "WordPress Plus",
      "line": "Managed WordPress hosting"
    },
    "term_months": 12,
    "domain": "example.com",
    "application": "WordPress + WooCommerce",
    "woocommerce": true,
    "total": "153.00 USD",
    "reference": "ZD-10023",
    "billed_by": "Zinn Digital LTD"
  }
}

Errors: conflict, not_found, rate_limited, scope_missing, unauthorized

WooCommerce on a PBN-line site

GET/api/v1/sites/{site_id}/woocommerce

Scope moneysites:read

Whether WooCommerce is installed on one of your PBN-line WordPress sites, which version, and the health checks: plugin active, shop/cart/checkout pages, a currency, pretty permalinks, HTTPS, and cart and checkout kept out of any page cache.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesA PBN-line site id (the same id the /sites endpoints use).

Example

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

Response

200

{
  "data": {
    "site_id": 12345,
    "eligible": true,
    "on": true,
    "busy": false,
    "version": "9.3.3",
    "free": true,
    "health": [
      {
        "key": "plugin_active",
        "label": "WooCommerce is active",
        "ok": true
      }
    ]
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

Add or remove WooCommerce (free)

POST/api/v1/sites/{site_id}/woocommerce

Scope moneysites:write · returns a job

Adds WooCommerce to a PBN-line WordPress site, free of charge, or switches it off again. The install runs in the background and usually takes a minute or two; poll GET /sites/{site_id}/woocommerce for the result. WooCommerce is a WordPress plugin, so a site of another type is refused with the reason - a WordPress money-site plan with the WooCommerce option is the answer there. To have it installed as a NEW site is built, send install_woocommerce: true to POST /sites.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesA PBN-line site id (the same id the /sites endpoints use).
onbodybooleannotrue installs and activates WooCommerce; false deactivates it (products and orders stay in the database). Default: True.

Example

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

Response

200

{
  "data": {
    "site_id": 12345,
    "state": "queued",
    "free": true
  },
  "job": {
    "id": "job_4f1c0a9e2b7d6c5a3e10",
    "kind": "...",
    "status": "running",
    "url": "https://app.pbn.ltd/api/v1/jobs/job_4f1c0a9e2b7d6c5a3e10"
  }
}

Errors: conflict, not_found, rate_limited, scope_missing, unauthorized

Add-ons for one money site

GET/api/v1/money-sites/{money_site_id}/addons

Scope moneysites:read

What the site's plan already includes, the add-ons that can be added to THIS site (valid for its plan, priced by the server), and the add-on orders already made for it with their status. online says whether an add-on can be placed and paid online right now (answered with a pay link) or is taken as a request that our team places and sends the payment link for - nothing is charged either way until you pay on Zinn Digital LTD's own page.

Parameters

NameInTypeRequiredDescription
money_site_idpathintegeryesThe money site id (GET /money-sites).

Example

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

Response

200

{
  "data": {
    "money_site_id": 7,
    "plan": "WordPress Plus",
    "included": [
      {
        "label": "Daily backups",
        "value": "Yes"
      }
    ],
    "offered": [
      {
        "code": "extra_storage_10gb",
        "name": "Extra storage 10 GB",
        "summary": "",
        "category": "storage",
        "price": "3.00 USD",
        "price_text": "3.00 USD a month",
        "monthly": true,
        "set_up_by_our_team": false
      }
    ],
    "orders": [
      {
        "id": 21,
        "state": "placed",
        "status": "Awaiting payment",
        "addons": "Extra storage 10 GB (3.00 USD a month)",
        "codes": [
          "extra_storage_10gb"
        ],
        "quoted": "3.00 USD",
        "total": "3.00 USD",
        "reference": "ZD-A-1042",
        "delivery": "",
        "created": "2026-09-24T20:00:00Z"
      }
    ],
    "online": false,
    "how_it_works": "request"
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

Add add-ons to a money site

POST/api/v1/money-sites/{money_site_id}/addons

Scope moneysites:write

Asks for add-ons on this site. The server checks every code against the site's plan and prices it. When add-ons can be placed online, the answer carries Zinn Digital LTD's one-time pay_url (open it to pay); otherwise it is recorded as a request, our team places it and sends you the payment link (requested: true, pay_url null). Nothing is charged by this call. The same set asked twice while the first is still open returns the first.

Parameters

NameInTypeRequiredDescription
money_site_idpathintegeryesThe money site id (GET /money-sites).
addonsbodyarrayyesAdd-on codes from offered on GET /money-sites/{money_site_id}/addons (at most 20).
accept_termsbodybooleannoMust be true (once per Terms version): records that this account accepts the Terms and Conditions, exactly as the tick box on the website does. Default: False.

Example

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

Response

201

{
  "data": {
    "order": {
      "id": 21,
      "state": "placed",
      "status": "Awaiting payment",
      "addons": "Extra storage 10 GB (3.00 USD a month)",
      "codes": [
        "extra_storage_10gb"
      ],
      "quoted": "3.00 USD",
      "total": "3.00 USD",
      "reference": "ZD-A-1042",
      "delivery": "",
      "created": "2026-09-24T20:00:00Z"
    },
    "pay_url": "https://zinndigital.com/pay/...",
    "requested": false,
    "held_for_review": false
  }
}

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

A fresh pay link for an add-on order

POST/api/v1/money-sites/{money_site_id}/addons/{addon_order_id}/pay

Scope moneysites:write

For an add-on order that is awaiting payment: the same order's pay link again (never a second order). Refused when there is nothing to pay.

Parameters

NameInTypeRequiredDescription
money_site_idpathintegeryesThe money site id (GET /money-sites).
addon_order_idpathintegeryesThe add-on order id (GET /money-sites/{money_site_id}/addons).

Example

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

Response

200

{
  "data": {
    "pay_url": "https://zinndigital.com/pay/..."
  }
}

Errors: conflict, not_found, rate_limited, scope_missing, unauthorized

The plan that pays for a money site

GET/api/v1/money-sites/{money_site_id}/plan

Scope moneysites:read

The Zinn Digital subscription that pays for this money site, read live: its plan, price, whether it renews and on which date, or the date it ends when it will not renew, and whether it can be cancelled here (can_cancel).

Parameters

NameInTypeRequiredDescription
money_site_idpathintegeryesThe money site id (GET /money-sites).

Example

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

Response

200

{
  "data": {
    "money_site_id": 7,
    "subscription_id": "01a0...",
    "plan": "Starter",
    "status": "active",
    "renews": true,
    "renews_on": "2026-10-25T03:10:56Z",
    "ends_on": null,
    "price": "7.99 USD a month",
    "can_cancel": true
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

Cancel the plan for a money site (at period end, no refund)

POST/api/v1/money-sites/{money_site_id}/plan/cancel

Scope moneysites:write · destructive

Cancels the plan that pays for this money site AT THE END OF THE PAID PERIOD: renewal is switched off, the site stays live until ends_on, then the plan ends and nothing more is charged. No refund is made. Safe to repeat: a plan that is already cancelled answers its current state. A plan paid through a PayPal subscription is cancelled in PayPal instead (the error says how).

Parameters

NameInTypeRequiredDescription
money_site_idpathintegeryesThe money site id (GET /money-sites).
confirmbodybooleanyesMust be true: you understand the site stays live until the period end, then the plan ends, and that no refund is made.

Example

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

Response

200

{
  "data": {
    "money_site_id": 7,
    "subscription_id": "01a0...",
    "plan": "Starter",
    "status": "active",
    "renews": true,
    "renews_on": "2026-10-25T03:10:56Z",
    "ends_on": null,
    "price": "7.99 USD a month",
    "can_cancel": true
  }
}

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

Can this PBN site move to money-site hosting?

GET/api/v1/sites/{site_id}/money-site-move

Scope moneysites:read

Whether the site can be moved now, into which money-site plan and slot, and what happens. When it cannot, reason_code says why (no_plan, no_free_slot, type_unsupported, site_busy, already_moving, dns_unsupported, ...) and buy_url points at the plan to buy. current_move is the move in progress, if any.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe PBN site id (the same id the /sites endpoints use).

Example

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

Response

200

{
  "data": {
    "can_move": true,
    "reason_code": null,
    "reason": null,
    "hint": null,
    "buy_url": null,
    "target": {
      "subscription_id": "01a0...",
      "plan_code": "mainstream_plus",
      "plan_name": "Plus",
      "free_slots": 4,
      "max_sites": 5
    },
    "paid_addons_that_end": [],
    "email_kept": true,
    "current_move": null,
    "what_happens": [
      "We make a fresh copy of the site (files and database)."
    ]
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

Move a PBN site to money-site hosting

POST/api/v1/sites/{site_id}/money-site-move

Scope moneysites:write

Starts the one-click move. Needs the sites:delete scope too, because the PBN copy is deleted at the end. The domain keeps its nameservers and DNS account: only its web records are pointed at the new hosting, after the copy has been checked; email and other records are not touched. Nothing is charged. Follow it with GET /money-site-moves/{move_id}.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe PBN site id (the same id the /sites endpoints use).
confirmbodybooleanyesMust be true: you accept that once the site is live on money-site hosting its PBN copy and its PBN backups are deleted automatically (the PBN slot is freed).
subscription_idbodystringnoOptional: which money-site plan to move into, when you have more than one (target.subscription_id from the GET).

Example

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

Response

201

{
  "data": {
    "move": {
      "id": 12,
      "site_id": 246415,
      "domain": "example.com",
      "site_type": "Wordpress",
      "state": "importing",
      "status": "Setting it up on money-site hosting",
      "detail": "Setting your site up on money-site hosting.",
      "active": true,
      "steps": [
        {
          "key": "backing_up",
          "label": "Copying your site",
          "state": "done"
        },
        {
          "key": "importing",
          "label": "Setting it up on money-site hosting",
          "state": "now"
        }
      ],
      "plan_code": "mainstream_plus",
      "app": "wordpress",
      "money_site_id": null,
      "error": "",
      "error_code": "",
      "created": "2026-09-28T12:00:00Z",
      "finished": null,
      "events": [
        {
          "at": "2026-09-28T12:01:00Z",
          "step": "backing_up",
          "result": "ok",
          "text": "copy made: 41.2 MiB, 2310 files, 12 tables"
        }
      ]
    }
  }
}

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

Moves to money-site hosting

GET/api/v1/money-site-moves

Scope moneysites:read

Every move of a PBN site to money-site hosting on this account, newest first, with its steps and state.

Example

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

Response

200

{
  "data": {
    "moves": [
      {
        "id": 12,
        "site_id": 246415,
        "domain": "example.com",
        "site_type": "Wordpress",
        "state": "importing",
        "status": "Setting it up on money-site hosting",
        "detail": "Setting your site up on money-site hosting.",
        "active": true,
        "steps": [
          {
            "key": "backing_up",
            "label": "Copying your site",
            "state": "done"
          },
          {
            "key": "importing",
            "label": "Setting it up on money-site hosting",
            "state": "now"
          }
        ],
        "plan_code": "mainstream_plus",
        "app": "wordpress",
        "money_site_id": null,
        "error": "",
        "error_code": "",
        "created": "2026-09-28T12:00:00Z",
        "finished": null,
        "events": [
          {
            "at": "2026-09-28T12:01:00Z",
            "step": "backing_up",
            "result": "ok",
            "text": "copy made: 41.2 MiB, 2310 files, 12 tables"
          }
        ]
      }
    ]
  }
}

Errors: rate_limited, scope_missing, unauthorized

One move

GET/api/v1/money-site-moves/{move_id}

Scope moneysites:read

One move: the step it is on, what it is doing now, and its log (what was checked and when).

Parameters

NameInTypeRequiredDescription
move_idpathintegeryesThe move id (GET /money-site-moves).

Example

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

Response

200

{
  "data": {
    "move": {
      "id": 12,
      "site_id": 246415,
      "domain": "example.com",
      "site_type": "Wordpress",
      "state": "importing",
      "status": "Setting it up on money-site hosting",
      "detail": "Setting your site up on money-site hosting.",
      "active": true,
      "steps": [
        {
          "key": "backing_up",
          "label": "Copying your site",
          "state": "done"
        },
        {
          "key": "importing",
          "label": "Setting it up on money-site hosting",
          "state": "now"
        }
      ],
      "plan_code": "mainstream_plus",
      "app": "wordpress",
      "money_site_id": null,
      "error": "",
      "error_code": "",
      "created": "2026-09-28T12:00:00Z",
      "finished": null,
      "events": [
        {
          "at": "2026-09-28T12:01:00Z",
          "step": "backing_up",
          "result": "ok",
          "text": "copy made: 41.2 MiB, 2310 files, 12 tables"
        }
      ]
    }
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

Cancel a move

POST/api/v1/money-site-moves/{move_id}/cancel

Scope moneysites:write

Cancels a move before the domain is switched. The site stays exactly as it is on PBN hosting. Refused once the domain switch has started.

Parameters

NameInTypeRequiredDescription
move_idpathintegeryesThe move id (GET /money-site-moves).

Example

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

Response

200

{
  "data": {
    "move": {
      "id": 12,
      "site_id": 246415,
      "domain": "example.com",
      "site_type": "Wordpress",
      "state": "importing",
      "status": "Setting it up on money-site hosting",
      "detail": "Setting your site up on money-site hosting.",
      "active": true,
      "steps": [
        {
          "key": "backing_up",
          "label": "Copying your site",
          "state": "done"
        },
        {
          "key": "importing",
          "label": "Setting it up on money-site hosting",
          "state": "now"
        }
      ],
      "plan_code": "mainstream_plus",
      "app": "wordpress",
      "money_site_id": null,
      "error": "",
      "error_code": "",
      "created": "2026-09-28T12:00:00Z",
      "finished": null,
      "events": [
        {
          "at": "2026-09-28T12:01:00Z",
          "step": "backing_up",
          "result": "ok",
          "text": "copy made: 41.2 MiB, 2310 files, 12 tables"
        }
      ]
    }
  }
}

Errors: conflict, not_found, rate_limited, scope_missing, unauthorized