# Porkbun API v3 — Hosting

> Three products share these endpoints; the `sku` you pass to `/hosting/create` picks one:

• **Secure Static Hosting**: you upload the files; Porkbun serves them over HTTPS. SKUs `PIXIESECURESTATIC…`
• **Cloud for WordPress**: a managed WordPress site, managed through WordPress itself. SKUs `CLOUDWORDPRESS…`
• **Link in Bio** (paid): a page of links, edited with the `/linkinbio` endpoints. SKUs `PIXIELINKSM1` and `PIXIELINKSY1`. The free plan is turned on with `/linkinbio/create`, not here.

All three: `/hosting/plans`, `/hosting/create`, `/hosting/get`, `/hosting/updateAutoRenew`, `/hosting/delete`.
Static hosting only: `/hosting/deploy`, `/hosting/files`, `/hosting/getFile`, `/hosting/makeDir`, `/hosting/deleteFile` (anything else returns `NOT_SUPPORTED_FOR_PRODUCT`).
WordPress only: `/hosting/wpRequest` and `/hosting/wpUploadMedia` (edit content and add images, no credentials needed), `/hosting/createWpCredentials`, `/hosting/getWpCredentials`, `/hosting/deleteWpCredentials`, and the site management below.

A domain's first purchase of a product is a 15-day free trial that auto-renews at the plan price; buying the same product again after cancelling is charged (one free trial per domain per product).

**Cloud for WordPress site management:** `GET /hosting/get` shows the site's PHP and WordPress versions, SSL, storage, search visibility, staging and background jobs; `setPhpVersion`, `setWordPressVersion`, `reissueSsl` and `setSearchVisibility` change them; `backups`, `createBackup`, `deleteBackup` and `restoreBackup` manage backups (add `"staging": true` for the staging copy). **Staging:** `getStaging`, `createStaging`, `resetStaging`, `publishStaging`, `deleteStaging`, and its settings `setStagingGate` (password), `setStagingGuard` (outbound email/API) and `setStagingEnvironment`.

**Site-share keys** (`/apikey/share`, permission `site`, one domain) can use every call here except `/hosting/create`, `/hosting/delete` and `/hosting/updateAutoRenew`.

Part of the Porkbun API v3.77. Topic index: https://porkbun.com/llms · Full reference: https://porkbun.com/llms-full.txt · Overview: https://porkbun.com/llms.txt · OpenAPI spec: https://porkbun.com/api/json/v3/spec

**Auth:** send `X-API-Key` / `X-Secret-API-Key` headers (preferred) or `apikey` / `secretapikey` in the JSON body. Create keys at https://porkbun.com/account/api

---

# Endpoints

## GET /api/json/v3/hosting/plans

**List the hosting plans you can buy**

**Applies to:** Secure Static Hosting, Cloud for WordPress and paid Link in Bio.

Lists the plans `/hosting/create` sells, with price, interval, trial length and features. `product` tells them apart (`secureStaticHosting`, `cloudWordPress`, and `managedLinks` for Link in Bio). To buy one, send the row's `sku` to `/hosting/create` and its `price` (cents) as `acknowledgedCost`. Also available via POST.

Each row also has `productName`, `displayName`, `bestFor` (when to choose it) and `featuresText` (the features in plain words), to explain the choice to a person: static hosting for a site an agent builds and maintains as files, Cloud for WordPress for a site the owner edits or that needs plugins, Link in Bio for a single page of links.

**Key permission needed:** `read`

```bash
curl https://api.porkbun.com/api/json/v3/hosting/plans \
  -H 'X-API-Key: pk1_...' -H 'X-Secret-API-Key: sk1_...'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `plans` | object[] |  |

## POST /api/json/v3/hosting/create/{domain}

**Buy a hosting plan (static, WordPress or paid Link in Bio)**

**Applies to:** Secure Static Hosting, Cloud for WordPress and paid Link in Bio. The `sku` decides which; list them with `/hosting/plans`.

Buys a hosting plan for a domain in the account. Send the plan's `sku`, and its `price` from `/hosting/plans` as `acknowledgedCost`, which confirms the person was told the price.

**Price and trial:** the first time a domain gets a product, it starts with a **15-day free trial** ($0 now) and then **auto-renews** at the plan price. One trial per domain per product: buying the same product again after cancelling is charged immediately, to account credit, in USDC with `"payWith": "usdc"` (HTTP 402, then repeat with `PAYMENT-SIGNATURE`; kept as account credit if provisioning fails), or by card with `"payWith": "card"` (a hold, captured only if provisioning succeeds). Charges count toward the monthly API spend limit.

**Nameservers:** the domain is switched to Porkbun nameservers if it isn't on them already; send `agreeToNameserverChange: true` to allow that.

**By product:**
- **Secure Static Hosting** (`PIXIESECURESTATIC…`): setup can take a moment; if `status` is `PENDING`, poll `/hosting/get` until `ACTIVE`, then upload with `/hosting/deploy`.
- **Cloud for WordPress** (`CLOUDWORDPRESS…`): a managed WordPress site. Edit its content with `/hosting/wpRequest` and `/hosting/wpUploadMedia` (no WordPress password needed), or mint REST API credentials with `/hosting/createWpCredentials` for your own code; the file endpoints don't apply.
- **Link in Bio** (`PIXIELINKSM1` monthly, `PIXIELINKSY1` yearly): a paid page of links (1000 links and every feature). Over a free Link in Bio page (most .com domains registered at Porkbun have one) it upgrades the page and keeps its title, links and settings. For a free page, use `/linkinbio/create` instead. Edit the page with the `/linkinbio` endpoints.

The free Link in Bio page and the free WordPress preview that come with a domain never block a purchase: the product you buy replaces them.

The call can take 30 seconds or more while the site is set up; wait for it rather than retrying (an `Idempotency-Key` makes a retry safe). Supports `dryRun`. **Rate limit:** 10 purchases per account per hour (`dryRun` calls are free).

**Key permission needed:** `purchase`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `sku` | string | yes | The hosting plan SKU to provision. Discover the provisionable SKUs (and each one’s price/interval/trial) via GET /hosting/plans, then pass the row’s `sku`. Families: Secure Static Hosting `PIXIESECURESTATIC…`, Cloud for WordPress `CLOUDWORDPRESS…`, and paid Link in Bio `PIXIELINKSM1` ($3/month) or `PIXIELINKSY1` ($30/year). |
| `acknowledgedCost` | integer | yes | The plan's `price` in cents, from the same `/hosting/plans` row as the `sku` (e.g. 300 for `PIXIELINKSM1` or `PIXIESECURESTATICM2`, 1200 for `CLOUDWORDPRESSM1`), to confirm the account holder understands the auto-renew / charge. Mismatch returns COST_ACKNOWLEDGMENT_REQUIRED. |
| `agreeToTerms` | string | yes |  |
| `agreeToNameserverChange` | boolean | no | Required (true) when the domain is not already on Porkbun nameservers — provisioning will switch them. |
| `payWith` | string | no | Optional. `"usdc"` pays for this purchase directly in USDC on Base over x402 instead of from account credit. The first call answers HTTP 402 (`PAYMENT_REQUIRED`) with Coinbase's terms in the `PAYMENT-REQUIRED` header and charges nothing; an x402 client pays and repeats the same request with a `PAYMENT-SIGNATURE` header, and the purchase completes in that call. If it then fails, the payment stays on the account as account credit; it is not sent back to the wallet. If your wallet pays URLs itself instead, pay the 402's `x402Url` (or have a person pay `payUrl`) and repeat the request with `usdcCheckoutId`. See the guide: https://porkbun.com/llms/guides/pay-with-usdc-x402. `"card"` pays by card through a Stripe Link agent wallet over MPP instead, with a real hold: the 402 carries a single-use `payUrl` and a `cardPaymentId`; pay the link (it authorizes, not charges), then repeat with `cardPaymentId`; the hold is captured if the purchase succeeds and released if it fails. Guide: https://porkbun.com/llms/guides/pay-with-card-mpp |
| `usdcCheckoutId` | string | no | With `payWith: "usdc"`: the `checkoutId` from an earlier `PAYMENT_REQUIRED`, once its `x402Url` or `payUrl` has been paid. Send it with otherwise the same request; the purchase completes if the checkout is paid and was opened for this purchase. |
| `cardPaymentId` | string | no | With `payWith: "card"`: the `cardPaymentId` from an earlier `PAYMENT_REQUIRED`, once its `payUrl` has been paid (the card is then held). Send it with otherwise the same request; the hold is captured if the purchase succeeds and released if not. |
| `dryRun` | boolean | no | Validate + preview without provisioning or charging. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/hosting/create/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","sku":"PIXIELINKSM1","acknowledgedCost":300,"agreeToTerms":"yes"}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `orderId` | integer |  |
| `hosting` | object |  |
| `charged` | integer | Cents captured now (0 on the free trial). |
| `cost` | object |  |
| `message` | string |  |

## GET /api/json/v3/hosting/get/{domain}

**Get the hosting on a domain**

**Applies to:** Secure Static Hosting, Cloud for WordPress and Link in Bio.

Returns the hosting on a domain (`product`, plan, status, trial, expiry, auto-renew), or `hosting: null` if there is none. Also available via POST.

For a live Cloud for WordPress site, `wordpress.site` adds what the hosting page on porkbun.com shows: PHP version (and the versions available), WordPress version setting, SSL, storage used, search visibility, staging, and the latest background jobs (restores, factory resets, site copies). Reading it takes a couple of seconds, because it asks WP Cloud. For a Link in Bio page's content, use `/linkinbio/get`.

**Key permission needed:** `read`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |

```bash
curl https://api.porkbun.com/api/json/v3/hosting/get/example.com \
  -H 'X-API-Key: pk1_...' -H 'X-Secret-API-Key: sk1_...'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `hosting` | object |  |

## POST /api/json/v3/hosting/deploy/{domain}

**Upload site files (static hosting only)**

**Applies to:** Secure Static Hosting only — a WordPress site or Link in Bio page returns `NOT_SUPPORTED_FOR_PRODUCT`; manage its content through WordPress instead.

Upload static files to the domain's Secure Static Hosting space. Send `files` as an array of `{ path, content }` where `content` is base64. Total payload ≤ 10 MB per request (split larger sites across calls). Only static-web file types are accepted (html/css/js/images/fonts/…); server-executable types are rejected. Hosting must be ACTIVE. A file’s `path` may include directories (e.g. `assets/css/style.css`); any missing parent directories are created automatically. Paths are sanitized (no traversal/control chars) and the filename extension must be an allowed static-web type.

Each file is `{path, text}` for text files (the content as written, no encoding) or `{path, content}` with base64 for images and other binary files. A file with neither is skipped and listed in `skipped`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `files` | object[] | yes |  |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/hosting/deploy/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","files":[{"path":"index.html","content":"PGgxPkhlbGxvPC9oMT4="}]}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `deployed` | string[] |  |
| `skipped` | object[] |  |

## GET /api/json/v3/hosting/files/{domain}

**List site files (static hosting only)**

**Applies to:** Secure Static Hosting only — a WordPress site or Link in Bio page returns `NOT_SUPPORTED_FOR_PRODUCT`.

List file/directory names under an optional `path` in the domain's hosting space. Also available via POST (send `path` in the body).

**Key permission needed:** `read`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `path` | string |  |
| `files` | string[] |  |

## POST /api/json/v3/hosting/getFile/{domain}

**Read files from a static site**

**Applies to:** Secure Static Hosting only — a WordPress site or Link in Bio page returns `NOT_SUPPORTED_FOR_PRODUCT`.

Reads files back so an agent can change a page without rebuilding it: read `index.html`, change only what was asked, then publish it with `/hosting/deploy`. Send `path` (one) or `paths` (up to 20). Text files (valid UTF-8) come back as `text`, the exact source; anything else as base64 `content`. A file that is missing, unreadable or too large comes back with an `error` instead. Up to about 4 MB per call. `/hosting/files` lists what is there. Needs only the `read` permission; site-share keys can use it.

**Errors:** `INVALID_INPUT`, `HOSTING_NOT_FOUND`, `HOSTING_NOT_READY`, `HOSTING_READ_FAILED`, `NOT_SUPPORTED_FOR_PRODUCT`.

**Key permission needed:** `read`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `path` | string | no |  |
| `paths` | string[] | no |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `files` | object[] |  |

## POST /api/json/v3/hosting/deleteFile/{domain}

**Delete a site file (static hosting only)**

**Applies to:** Secure Static Hosting only — a WordPress site or Link in Bio page returns `NOT_SUPPORTED_FOR_PRODUCT`.

Delete a file or directory at `path` in the domain's hosting space. Idempotent: a path that is already gone returns `SUCCESS` with `alreadyAbsent: true`. A directory that still has files returns `HOSTING_DIR_NOT_EMPTY` with its `contents`, unless `recursive: true` is sent, which deletes it with everything inside (the site root can never be deleted).

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `path` | string | yes |  |
| `recursive` | boolean | no | Delete a directory together with everything in it (up to 2,000 items per call; call again to continue). Without it, a non-empty directory returns HOSTING_DIR_NOT_EMPTY. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `deleted` | string |  |

## POST /api/json/v3/hosting/delete/{domain}

**Cancel a hosting plan**

**Applies to:** Secure Static Hosting, Cloud for WordPress and paid Link in Bio.

Cancels the hosting on a domain now. To let a plan run to the end of its term instead, turn auto-renew off with `/hosting/updateAutoRenew`.

**By product:**
- **Secure Static Hosting and Cloud for WordPress:** the site is taken down (Porkbun schedules the teardown). The domain has used its free trial, so buying the same product again later is charged.
- **Link in Bio:** a paid page is not taken down. It moves to the free plan (50 links, without scheduling, animation, passcode protection or analytics IDs) and keeps its content. A free page has nothing to cancel and returns `NOT_SUPPORTED_FOR_PRODUCT`; free pages can't be turned off through the API.

**Errors:** `HOSTING_NOT_FOUND`, `NOT_SUPPORTED_FOR_PRODUCT`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `warnings` | string[] | Advisory, and present only when there is something to say. It never means the call failed. The one to handle: a DNS write is accepted and stored even when the domain is NOT delegated to our nameservers -- we keep the zone ready in case the delegation comes back -- so the write changed nothing that resolves, and this field says so. Show these to the user as written. |
| `status` | string |  |
| `message` | string | Human-readable message. Present on ERROR, sometimes on SUCCESS. |
| `code` | string | Machine-readable error code. Present when status is ERROR. |

## POST /api/json/v3/hosting/makeDir/{domain}

**Create a directory (static hosting only)**

**Applies to:** Secure Static Hosting only — a WordPress site or Link in Bio page returns `NOT_SUPPORTED_FOR_PRODUCT`.

Create a directory (and any missing parent directories) at `path` in the domain’s hosting space. Deploy already auto-creates directories in a file’s path, so use this to stand up an empty directory explicitly. Path is sanitized segment-by-segment (no traversal / control chars).

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `path` | string | yes |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `created` | string |  |

## POST /api/json/v3/hosting/wpRequest/{domain}

**Edit a WordPress site: one REST API call, run inside the site**

**Applies to:** Cloud for WordPress (not the free preview).

The way to change a WordPress site's content without credentials: one WordPress REST API call, e.g. `GET /wp/v2/pages` (list), `GET /wp/v2/pages/12` with `{"context": "edit"}` (raw block markup to edit), `POST /wp/v2/pages/12` with `{"content": "..."}` (update), `POST /wp/v2/posts` (create). Runs inside the site: WordPress's own REST handling is called in-process over WP-CLI, as a WordPress user, so no application password is created, stored or returned, and it works whatever the domain's DNS points at. By default it acts as the dedicated `porkbun-agent` editor (created on first use): pages, posts, media, comments, menus. Site settings (title, tagline, homepage), plugins, themes and users need an administrator: send `asAdministrator` and `acknowledgeFullAccess`. Each call takes a few seconds.

The response is WordPress's own: `httpStatus`, `data` (its JSON), and `total`/`totalPages` on lists. A WordPress error (4xx/5xx) returns `status: ERROR`, `code: WP_REST_ERROR`, with WordPress's code and message in `data`. Answers over about 400 KB return `WP_RESPONSE_TOO_LARGE`: narrow them with `_fields`, `per_page` or a single id. Writes support `dryRun`. Rate limit: 600 calls per account per hour. Site-share keys (`site`) can use it.

Agents that make their own HTTP requests can instead mint credentials with `/hosting/createWpCredentials` and call `https://{domain}/wp-json/` directly.

**Errors:** `WP_REST_ERROR`, `WP_RESPONSE_TOO_LARGE`, `FULL_ACCESS_ACKNOWLEDGMENT_REQUIRED`, `WP_CLI_FAILED` (the site did not answer), `INVALID_INPUT`, `HOSTING_NOT_FOUND`, `PREVIEW_SITE_NOT_SUPPORTED`, `NOT_SUPPORTED_FOR_PRODUCT`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `method` | string | no |  |
| `path` | string | yes | REST route without /wp-json, e.g. `/wp/v2/pages/12`. No query string. |
| `query` | object | no | Query parameters, e.g. `{"per_page": 10, "context": "edit", "_fields": "id,title"}`. |
| `body` | object | no | JSON body for POST/PUT/PATCH, up to 1 MB. |
| `embed` | boolean | no | Include linked resources (`_embed`). |
| `asAdministrator` | boolean | no | Act as the site's administrator instead of the `porkbun-agent` editor. Needed for site settings, plugins, themes and users. Requires acknowledgeFullAccess. |
| `acknowledgeFullAccess` | boolean | no | Confirms administrator-level changes were agreed. |
| `dryRun` | boolean | no |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `httpStatus` | integer |  |
| `actsAs` | string | The WordPress user it ran as. |
| `data` |  | WordPress's JSON response. |
| `total` | integer |  |
| `totalPages` | integer |  |
| `code` | string |  |
| `message` | string |  |

## POST /api/json/v3/hosting/wpUploadMedia/{domain}

**Add an image or file to a WordPress site's media library**

**Applies to:** Cloud for WordPress (not the free preview).

Upload from a public `url` (WordPress downloads it, refusing private and local addresses) or from base64 `data` with a `filename`, up to about 10 MB. Returns the attachment `id` (use as `featured_media` on a post or page) and its `url` (use in content). Runs inside the site: WordPress's own REST handling is called in-process over WP-CLI, as a WordPress user, so no application password is created, stored or returned, and it works whatever the domain's DNS points at. By default it acts as the dedicated `porkbun-agent` editor (created on first use): pages, posts, media, comments, menus. Site settings (title, tagline, homepage), plugins, themes and users need an administrator: send `asAdministrator` and `acknowledgeFullAccess`. Each call takes a few seconds.

Rate limit: 200 uploads per account per hour. Supports `dryRun`. Site-share keys (`site`) can use it.

**Errors:** `INVALID_INPUT`, `WP_CLI_FAILED` (with WordPress's reason, e.g. a file type it does not allow), `FULL_ACCESS_ACKNOWLEDGMENT_REQUIRED`, `HOSTING_NOT_FOUND`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | string | no | Public https:// address of the file. Use this or data. |
| `data` | string | no | The file as base64 (a data: URL works). Use this or url. |
| `filename` | string | no | With data: the file name with extension, e.g. `storefront.jpg`. |
| `title` | string | no |  |
| `altText` | string | no |  |
| `caption` | string | no |  |
| `postId` | integer | no | Attach to this post or page. |
| `asAdministrator` | boolean | no | Act as the site's administrator instead of the `porkbun-agent` editor. Needed for site settings, plugins, themes and users. Requires acknowledgeFullAccess. |
| `acknowledgeFullAccess` | boolean | no | Confirms administrator-level changes were agreed. |
| `dryRun` | boolean | no |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `media` | object |  |
| `actsAs` | string |  |
| `message` | string |  |

## POST /api/json/v3/hosting/createWpCredentials/{domain}

**Mint WordPress REST API credentials (WordPress only)**

**To edit a site's content from an agent, use `/hosting/wpRequest` and `/hosting/wpUploadMedia` instead: they run inside the site and need no password.** This endpoint is for code that calls the WordPress REST API itself.

**Applies to:** Cloud for WordPress only.

Creates a WordPress **Application Password** so an agent or integration can drive the site over the WP REST API at `https://{domain}/wp-json/` using HTTP Basic auth. The password is returned **once** — WordPress stores only a hash.

Defaults to a dedicated least-privilege `porkbun-agent` user with the `editor` role (created on first use), which can manage content but not install code. `role: "administrator"` grants full site control **including plugin installation (arbitrary code execution on the site)** and therefore requires `acknowledgeFullAccess: true`.

Revoke any time via `/hosting/deleteWpCredentials/{domain}` or in wp-admin under Users → Profile. Requires the site to be provisioned and ACTIVE (poll `/hosting/get/{domain}`).

Free **preview** sites (the $0 parked plan) are excluded — they return `PREVIEW_SITE_NOT_SUPPORTED`; upgrade to a paid plan first. Works on any Cloud for WordPress site in the account regardless of whether it was provisioned via the API or the website, including sites migrated onto WP Cloud from the legacy WordPress product. For `role: "administrator"` the site's actual administrator account is looked up rather than assumed, so a renamed admin user is handled.

**Rate limit:** 20 mints per account per hour (`dryRun` calls are free).

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `role` | string | no | Least privilege by default. `editor` = content only (recommended for agents). `administrator` = full control incl. plugin install; requires acknowledgeFullAccess. |
| `acknowledgeFullAccess` | boolean | no | Required when role=administrator: confirms you understand the credential can run arbitrary code on the site. |
| `name` | string | no | Label shown in wp-admin (sanitized to letters, digits and dashes). |
| `dryRun` | boolean | no | Validate without creating anything. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `credentials` | object |  |
| `message` | string |  |

## GET /api/json/v3/hosting/getWpCredentials/{domain}

**List WordPress application passwords (WordPress only)**

**Applies to:** Cloud for WordPress only.

Lists the application passwords on the site (uuid, name, created, last used) so you can audit or pick one to revoke. Metadata only — WordPress stores just a hash, so a password can never be re-read. Optional `wpUser` (defaults to the dedicated `porkbun-agent` user).

Free preview sites are excluded (`PREVIEW_SITE_NOT_SUPPORTED`).

**Key permission needed:** `read`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `wpUser` | query | no |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `wpUser` | string |  |
| `credentials` | object[] |  |

## POST /api/json/v3/hosting/deleteWpCredentials/{domain}

**Revoke WordPress application passwords (WordPress only)**

**Applies to:** Cloud for WordPress only.

Revokes an application password by `uuid` (from `/hosting/getWpCredentials`), or every one for the user with `all: true`. Any integration using it stops authenticating immediately.

Free preview sites are excluded (`PREVIEW_SITE_NOT_SUPPORTED`).

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `uuid` | string | no | The application password uuid to revoke. |
| `all` | boolean | no | Revoke every application password for the user. |
| `wpUser` | string | no |  |
| `dryRun` | boolean | no |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `revoked` | string |  |
| `message` | string |  |

## POST /api/json/v3/hosting/updateAutoRenew/{domain}

**Turn hosting auto-renew on or off**

**Applies to:** Secure Static Hosting, Cloud for WordPress and paid Link in Bio.

The same switch as on the hosting page on porkbun.com. Off: the plan stays active until `expireDate` and is not charged again (a trial ends without a charge); after that the hosting is removed, except a paid Link in Bio page, which moves to the free plan with its content kept. On: it renews before `expireDate` at the plan price. `GET /hosting/get` shows the current setting (`autoRenew`).

Free plans never renew, so they return `NOT_SUPPORTED_FOR_PRODUCT`. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `HOSTING_NOT_FOUND`, `NOT_SUPPORTED_FOR_PRODUCT`, `INVALID_INPUT`, `UPDATE_FAILED`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `autoRenew` | boolean | yes | true to renew automatically, false to let the plan end at expireDate. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `product` | string |  |
| `autoRenew` | boolean |  |
| `isTrial` | boolean |  |
| `expireDate` | string | When the current term (or trial) ends. |
| `cost` | object |  |
| `message` | string | What happens next, in words to relay to the user. |

## POST /api/json/v3/hosting/setPhpVersion/{domain}

**Set the PHP version**

**Applies to:** Cloud for WordPress (not the free preview).

Changes the PHP version the site runs. `version` must be one of `wordpress.site.availablePhpVersions` from `GET /hosting/get`. Takes effect within a minute or two; switch back the same way if the site misbehaves. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `INVALID_INPUT` (lists the versions), `UPSTREAM_UNAVAILABLE`, `UPDATE_FAILED`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `version` | string | yes |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `message` | string | What happens next, to relay to the user. |

## POST /api/json/v3/hosting/setWordPressVersion/{domain}

**Set the WordPress version**

**Applies to:** Cloud for WordPress (not the free preview).

`latest` follows WordPress releases; `previous` holds one major release back, the escape hatch when an update breaks the site. While on `previous` the site gets no newer releases, so set `latest` again once fixed. No pinned version numbers. Takes a few minutes. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `INVALID_INPUT`, `UPDATE_FAILED`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `version` | string | yes |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `message` | string | What happens next, to relay to the user. |

## POST /api/json/v3/hosting/reissueSsl/{domain}

**Restart SSL issuance**

**Applies to:** Cloud for WordPress (not the free preview).

Restarts certificate issuance for the domain and its www. host when it is stuck (check `wordpress.site.ssl`). The domain must point at the site. Once per 5 minutes per site. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `RATE_LIMIT_EXCEEDED` (with ttlRemaining), `UPDATE_FAILED`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `message` | string | What happens next, to relay to the user. |

## POST /api/json/v3/hosting/setSearchVisibility/{domain}

**Show or hide the site from search engines**

**Applies to:** Cloud for WordPress (not the free preview).

`visible: false` asks search engines not to index the site (WordPress "discourage search engines"); `true` allows it again. Pages already indexed drop out over time. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `INVALID_INPUT`, `UPSTREAM_UNAVAILABLE` (the site could not be reached; nothing changed).

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `visible` | boolean | yes |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `message` | string | What happens next, to relay to the user. |

## GET /api/json/v3/hosting/backups/{domain}

**List backups**

**Applies to:** Cloud for WordPress (not the free preview).

The site's backups, newest first: automatic file backups daily and database backups hourly (when it changes), kept 30 days, plus up to one on-demand backup of each type. Returns the newest `limit` (default 100, max 1000; on-demand ones are always included) and `total`. `type` filters to `files` or `database`.

**Errors:** `STAGING_NOT_FOUND`, `UPSTREAM_UNAVAILABLE`.

**Key permission needed:** `read`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `staging` | query | no |  |
| `limit` | query | no |  |
| `type` | query | no |  |
| `X-API-Key` | header | no |  |
| `X-Secret-API-Key` | header | no |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `target` | string |  |
| `backups` | object[] |  |
| `count` | integer |  |
| `total` | integer | All backups (of `type`, if given). |
| `retention` | string |  |
| `message` | string |  |

## POST /api/json/v3/hosting/backups/{domain}

**List backups**

**Applies to:** Cloud for WordPress (not the free preview).

The site's backups, newest first: automatic file backups daily and database backups hourly (when it changes), kept 30 days, plus up to one on-demand backup of each type. Returns the newest `limit` (default 100, max 1000; on-demand ones are always included) and `total`. `type` filters to `files` or `database`.

**Errors:** `STAGING_NOT_FOUND`, `UPSTREAM_UNAVAILABLE`.

**Key permission needed:** `read`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `staging` | boolean | no | true: act on the site's staging copy instead of the live site. |
| `limit` | integer | no | Default 100. |
| `type` | string | no |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `target` | string |  |
| `backups` | object[] |  |
| `count` | integer |  |
| `total` | integer | All backups (of `type`, if given). |
| `retention` | string |  |
| `message` | string |  |

## POST /api/json/v3/hosting/createBackup/{domain}

**Take an on-demand backup**

**Applies to:** Cloud for WordPress (not the free preview).

Starts a backup of the site's files and database now. Only one on-demand backup of each type can exist: delete the old one first (`BACKUP_FAILED` says "Quota Reached" otherwise). It appears in `/hosting/backups` when finished, usually within a few minutes. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `BACKUP_FAILED`, `STAGING_NOT_FOUND`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `staging` | boolean | no | true: act on the site's staging copy instead of the live site. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `message` | string | What happens next, to relay to the user. |

## POST /api/json/v3/hosting/deleteBackup/{domain}

**Delete an on-demand backup**

**Applies to:** Cloud for WordPress (not the free preview).

Deletes an on-demand backup by `id` (from `/hosting/backups`). Automatic backups cannot be deleted. Deletion finishes in the background within a minute or two. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `BACKUP_NOT_FOUND`, `NOT_SUPPORTED_FOR_PRODUCT` (an automatic backup), `UPDATE_FAILED`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `staging` | boolean | no | true: act on the site's staging copy instead of the live site. |
| `id` | string | yes |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `message` | string | What happens next, to relay to the user. |

## POST /api/json/v3/hosting/restoreBackup/{domain}

**Restore from a backup**

**Applies to:** Cloud for WordPress (not the free preview).

**Replaces the site's files and database** with one files backup (`fsId`) and one database backup (`dbId`) from `/hosting/backups`. Anything changed since then is lost. The restore is queued; the site is unavailable while it runs (usually a few minutes) and the account holder is emailed when it finishes. Poll `GET /hosting/get` (`wordpress.site.operations`) for its state. One restore or factory reset at a time, and not while a staging job runs. Confirm with the user first; a dry run shows the exact backups and dates. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `BACKUP_NOT_FOUND`, `OPERATION_IN_PROGRESS`, `STAGING_NOT_FOUND`, `UPDATE_FAILED`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `staging` | boolean | no | true: act on the site's staging copy instead of the live site. |
| `fsId` | string | yes | A files backup id. |
| `dbId` | string | yes | A database backup id. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `target` | string |  |
| `operation` | object |  |
| `message` | string |  |

## GET /api/json/v3/hosting/getStaging/{domain}

**Get the staging site**

**Applies to:** Cloud for WordPress (not the free preview).

The staging copy: URL, status, the latest job and its state, password protection (with the password), outbound email/API blocking and environment type. `staging` is null when there is none. Poll this after createStaging, resetStaging, publishStaging or deleteStaging. Needs the `hosting` permission because it returns the staging password.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `X-API-Key` | header | no |  |
| `X-Secret-API-Key` | header | no |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `staging` | object |  |

## POST /api/json/v3/hosting/getStaging/{domain}

**Get the staging site**

**Applies to:** Cloud for WordPress (not the free preview).

The staging copy: URL, status, the latest job and its state, password protection (with the password), outbound email/API blocking and environment type. `staging` is null when there is none. Poll this after createStaging, resetStaging, publishStaging or deleteStaging. Needs the `hosting` permission because it returns the staging password.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `staging` | object |  |

## POST /api/json/v3/hosting/createStaging/{domain}

**Create a staging site**

**Applies to:** Cloud for WordPress (not the free preview).

Builds a private copy of the live site on a `*.stagebun.com` address to try changes safely: from the live site as it is now, or from a backup pair (`fsId` + `dbId` from `/hosting/backups`). One per site. It starts password protected (the password is in the response), with outgoing email blocked and search engines asked not to index it. Queued; usually a few minutes, and the account holder is emailed when it is ready. Poll `getStaging`. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `STAGING_ALREADY_EXISTS`, `OPERATION_IN_PROGRESS`, `BACKUP_NOT_FOUND`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `fsId` | string | no | Optional: build from this files backup of the live site (with dbId). |
| `dbId` | string | no | Optional: the database backup to go with fsId. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `staging` | object |  |
| `message` | string |  |

## POST /api/json/v3/hosting/resetStaging/{domain}

**Rebuild the staging site**

**Applies to:** Cloud for WordPress (not the free preview).

Replaces everything on the staging site with a fresh copy of the live site, or of a backup pair (`fsId` + `dbId`). Changes made on staging are lost. Queued; poll `getStaging`. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `STAGING_NOT_FOUND`, `STAGING_NOT_READY`, `OPERATION_IN_PROGRESS`, `BACKUP_NOT_FOUND`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `fsId` | string | no | Optional: build from this files backup of the live site (with dbId). |
| `dbId` | string | no | Optional: the database backup to go with fsId. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `operation` | object |  |
| `message` | string | What happens next, to relay to the user. |

## POST /api/json/v3/hosting/publishStaging/{domain}

**Publish staging to the live site**

**Applies to:** Cloud for WordPress (not the free preview).

**Replaces the live site's files and database with the staging site's.** A fresh on-demand backup of the live site is taken first (it replaces any existing on-demand backup), so the live site can be restored from `/hosting/backups` if needed. Queued; several minutes, and the account holder is emailed when it is done. Confirm with the user first; poll `getStaging`. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `STAGING_NOT_FOUND`, `STAGING_NOT_READY`, `OPERATION_IN_PROGRESS`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `operation` | object |  |
| `message` | string | What happens next, to relay to the user. |

## POST /api/json/v3/hosting/deleteStaging/{domain}

**Delete the staging site**

**Applies to:** Cloud for WordPress (not the free preview).

Deletes the staging site and its backups. The live site is not affected. Queued; `getStaging` returns `staging: null` once it is gone. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `STAGING_NOT_FOUND`, `OPERATION_IN_PROGRESS`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `operation` | object |  |
| `message` | string | What happens next, to relay to the user. |

## POST /api/json/v3/hosting/setStagingGate/{domain}

**Set staging password protection**

**Applies to:** Cloud for WordPress (not the free preview).

`enabled` turns the password page on or off; `password` (4-64 characters) changes it. Turning protection off makes a copy of the live site public (it stays marked not to be indexed). The response includes the current password. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `STAGING_NOT_FOUND`, `STAGING_NOT_READY`, `INVALID_INPUT`, `UPSTREAM_UNAVAILABLE`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `enabled` | boolean | no |  |
| `password` | string | no | 4-64 characters. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `passwordProtection` | object |  |
| `message` | string |  |

## POST /api/json/v3/hosting/setStagingGuard/{domain}

**Block or allow outbound email and API calls from staging**

**Applies to:** Cloud for WordPress (not the free preview).

`blockEmail` swallows outgoing email (so a copy of the site cannot email real customers); `blockApiRequests` blocks outbound WordPress HTTP API requests (payment gateways, CRMs, webhooks). A new staging site blocks email and allows API requests. Only the fields sent change. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `STAGING_NOT_FOUND`, `STAGING_NOT_READY`, `UPSTREAM_UNAVAILABLE`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `blockEmail` | boolean | no |  |
| `blockApiRequests` | boolean | no |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `operation` | object |  |
| `message` | string | What happens next, to relay to the user. |

## POST /api/json/v3/hosting/setStagingEnvironment/{domain}

**Set the staging environment type**

**Applies to:** Cloud for WordPress (not the free preview).

Sets WP_ENVIRONMENT_TYPE on the staging site, the signal plugins read to decide how to behave. `production` may make plugins act live (payments, email, caching); use it only to test production behaviour. Default `staging`. Supports `dryRun`. Not available with sandbox keys.

**Errors:** `STAGING_NOT_FOUND`, `STAGING_NOT_READY`, `INVALID_INPUT`, `UPSTREAM_UNAVAILABLE`.

**Key permission needed:** `hosting`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `type` | string | yes |  |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `operation` | object |  |
| `message` | string | What happens next, to relay to the user. |

---

## More

- Guides (how-tos): https://porkbun.com/llms/guides
- Help articles (knowledge base) as Markdown: https://porkbun.com/llms/kb
- Topic index: https://porkbun.com/llms
- Full reference (one file): https://porkbun.com/llms-full.txt
- OpenAPI spec (full schemas): https://porkbun.com/api/json/v3/spec
- Short overview: https://porkbun.com/llms.txt
- Official MCP server: https://porkbun.com/mcp (`npx -y @porkbunllc/mcp-server`)
- Create API keys: https://porkbun.com/account/api
