# Porkbun API v3 — Hosting

> Two hosting products share these endpoints — pick one with the `sku` you pass to `/hosting/create`:

• **Secure Static Hosting** — you upload the files; Porkbun serves them over HTTPS. SKUs `PIXIESECURESTATIC…`
• **Cloud for WordPress** — a managed WordPress site; you manage it through WordPress itself. SKUs `CLOUDWORDPRESS…`

Shared by both: `/hosting/plans`, `/hosting/create`, `/hosting/get`, `/hosting/delete`.
Static hosting only: `/hosting/deploy`, `/hosting/files`, `/hosting/makeDir`, `/hosting/deleteFile` (they return `NOT_SUPPORTED_FOR_PRODUCT` on a WordPress site).
WordPress only: `/hosting/createWpCredentials`, `/hosting/getWpCredentials`, `/hosting/deleteWpCredentials`.

Both products: the domain's first provision is a 15-day free trial that auto-renews at the plan price; a re-provision after deprovision is charged (one free trial per domain).

Part of the Porkbun API v3.63. 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 provisionable hosting plans (static + WordPress)**

**Applies to:** Both products.

List the hosting plans that can be provisioned via the API, with price, interval, trial length, and features. Pass a row's `plan` to `/hosting/create` and its `price` (cents) as `acknowledgedCost`. Currently Secure Static Hosting; more products are added over time. Also available via POST. Includes both Secure Static Hosting and Cloud for WordPress (managed WordPress) plans; the `product` field distinguishes them.

**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}

**Provision hosting — a static site or a WordPress site**

**Applies to:** Both products — the `sku` decides which.

Provision hosting (Secure Static Hosting or Cloud for WordPress) for a domain in the account. The FIRST provision for a domain starts a **15-day free trial** ($0 now) that **auto-renews** at the plan price when the trial ends; a re-provision after deprovision is charged immediately, to account credit or directly 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), and counts toward the monthly API spend limit (one free trial per domain). Provisioning **switches the domain to Porkbun nameservers** if it isn't already — pass `agreeToNameserverChange: true` to allow that. Supports `dryRun`. Remote setup can be async: `status` may be `PENDING` — poll `/hosting/get` until `ACTIVE` before deploying.

**Cloud for WordPress:** pass a `CLOUDWORDPRESS…` sku to provision a managed WordPress site instead of static hosting. The file endpoints (deploy/files/deleteFile/makeDir) do not apply — manage the site through WordPress, using `/hosting/createWpCredentials/{domain}` for REST API credentials.

**Rate limit:** 10 provisions 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`. Currently Secure Static Hosting: PIXIESECURESTATICM2 ($3.00/mo) or PIXIESECURESTATICY2 ($30.00/yr). |
| `acknowledgedCost` | integer | yes | Echo the plan price in cents (300 monthly / 3000 yearly) 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_...","plan":"monthly","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 hosting status (either product)**

**Applies to:** Both products.

Return the Secure Static Hosting status for a domain (plan, server, trial, expiry, auto-renew), or `hosting: null` if none. Also available via POST.

**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 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.

**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 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/deleteFile/{domain}

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

**Applies to:** Secure Static Hosting only — a WordPress site 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}

**Deprovision hosting (either product)**

**Applies to:** Both products.

Deprovision (cancel) Secure Static Hosting for a domain; teardown is scheduled and completed by Porkbun. Note: the domain has already used its one free trial, so provisioning it again later will be charged (no second free trial).

**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 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/createWpCredentials/{domain}

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

**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 |  |

---

## More

- Guides (how-tos): https://porkbun.com/llms/guides
- 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
