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/plansScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
line | query | string (one of: wordpress, applications, web, developer, lms, all) | no | wordpress = 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-digitalScope 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-sitesScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
money_site_id | path | integer | yes | The 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/refreshScope 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/ordersScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
order_id | path | integer | yes | The 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/ordersScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
plan | body | string | yes | A plan code from /money-sites/plans. |
months | body | integer (one of: 1, 3, 6, 12) | no | How many months to pay for. Default: 1. |
domain | body | string | no | The domain for the site. Leave it out to start on a temporary address. |
woocommerce | body | boolean | no | WordPress 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. |
application | body | string | no | Web 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. |
notes | body | string | no | Anything our team should know. |
addons | body | array | no | Optional 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_terms | body | boolean | no | Must 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}/cancelScope moneysites:write · destructive
Cancel an order that has not been paid. Nothing was charged, so nothing is refunded.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
order_id | path | integer | yes | The 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}/woocommerceScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | A 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}/woocommerceScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | A PBN-line site id (the same id the /sites endpoints use). |
on | body | boolean | no | true 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}/addonsScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
money_site_id | path | integer | yes | The 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}/addonsScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
money_site_id | path | integer | yes | The money site id (GET /money-sites). |
addons | body | array | yes | Add-on codes from offered on GET /money-sites/{money_site_id}/addons (at most 20). |
accept_terms | body | boolean | no | Must 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}/payScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
money_site_id | path | integer | yes | The money site id (GET /money-sites). |
addon_order_id | path | integer | yes | The 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}/planScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
money_site_id | path | integer | yes | The 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/cancelScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
money_site_id | path | integer | yes | The money site id (GET /money-sites). |
confirm | body | boolean | yes | Must 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-moveScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The 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-moveScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The PBN site id (the same id the /sites endpoints use). |
confirm | body | boolean | yes | Must 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_id | body | string | no | Optional: 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-movesScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
move_id | path | integer | yes | The 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}/cancelScope 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
move_id | path | integer | yes | The 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