# Porkbun API v3 — v3.15

> Full Markdown reference for LLMs and agents. Machine-readable OpenAPI spec: https://porkbun.com/api/json/v3/spec
> Prefer a single topic? Per-area pages are indexed at https://porkbun.com/llms

The Porkbun API enables programmatic domain registration, DNS management, SSL certificate retrieval, and related operations. It is fully usable by AI agents, automation scripts, and developer tools.
## Sandbox / test mode

Build and test an integration end-to-end with **no real registry actions, no DNS changes, no certificates, and no charges** by using a **sandbox API key** — a public key prefixed `pk1_sb_` (secret `sk1_sb_`), created at https://porkbun.com/account/api. Same base URL; just swap the key. In sandbox:
- Every response includes `"sandbox": true` and an `X-Porkbun-Sandbox: true` header.
- Registrations, renewals, transfers, DNS, contacts, nameservers, glue, and DNSSEC are simulated against an isolated datastore; your account starts with fake credit (top up/reset via `/sandbox/topup` and `/sandbox/reset`). Availability and pricing reflect the real catalog so quotes match production.
- Endpoints that can't be simulated — **hosting** and **email** — return `SANDBOX_UNSUPPORTED`.
- **Webhooks are delivered in the sandbox.** Register an endpoint with `POST /webhook/create`, then either perform an operation (the matching signed event is delivered just like production) or fire any event on demand with `POST /sandbox/triggerWebhook` to test your handler and signature verification.

## Mock server (no credentials)

Learn any endpoint's exact response shape with **zero setup** — no key, no account. Every real path is mirrored under `/mock`:
- `GET /mock` lists every mockable endpoint.
- `GET|POST /mock/<path>` returns a schema-accurate example response for that operation (e.g. `/mock/domain/listAll`). Append `?status=error` for the error shape.

Mock responses touch no datastore, are identical in shape to the live API, and are signalled by an `X-Porkbun-Mock: true` header. Use it to build client code before you have credentials; switch to a sandbox key when you want real behavior with fake money.

## Quickstart

**1. Get your API keys** — visit https://porkbun.com/account/api

**2. Test connectivity**
```bash
curl https://api.porkbun.com/api/json/v3/ip
```
Returns your public IP. No credentials required.

**3. Check domain availability** (also verifies your credentials)
```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/checkDomain/example.com \
  -H "Content-Type: application/json" \
  -d '{"apikey":"your_api_key","secretapikey":"your_secret_key"}'
```
Returns `avail: "yes"/"no"` and `price` in USD. Returns an error if credentials are invalid.

**4. Register the domain** — convert `price` to pennies (e.g. $9.73 → 973) for `cost`
```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/create/example.com \
  -H "Content-Type: application/json" \
  -d '{"apikey":"your_api_key","secretapikey":"your_secret_key","cost":973,"agreeToTerms":"yes"}'
```

**5. Add a DNS record**
```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/create/example.com \
  -H "Content-Type: application/json" \
  -d '{"apikey":"your_api_key","secretapikey":"your_secret_key","type":"A","content":"1.2.3.4","ttl":"600"}'
```

## AI agents (MCP)

If you're building with Claude Desktop, Cursor, Cline, or another [Model Context Protocol](https://modelcontextprotocol.io) client, install our official Porkbun MCP server — no integration code required, your AI client gets domain registration, DNS, SSL, marketplace, and account-management tools natively.

```bash
npx -y @porkbunllc/mcp-server
```

Repo and Claude Desktop config: [github.com/oborseth/Porkbun-MCP](https://github.com/oborseth/Porkbun-MCP). It covers everything documented below, plus documentation-search tools that work without credentials. See the repo README for the current tool list. All write operations automatically attach an `Idempotency-Key` so agent retries don't double-charge.

## What you can build

- **Agentic domain registration** — Search availability and pricing across hundreds of TLDs, then register domains on behalf of users with a single API call
- **Automated DNS management** — Provision, update, and tear down DNS records as part of infrastructure automation or app deployment pipelines
- **Dynamic DNS clients** — Use `/ping` or `/ip` to detect IP address changes, then update A/AAAA records automatically
- **Domain portfolio tools** — List, monitor expiry, and configure auto-renewal settings across all domains in an account
- **SSL automation** — Retrieve free SSL certificate bundles for domains registered at Porkbun
- **Domain availability search** — Check availability and real-time pricing across all supported TLDs

## Agent-friendly design

- **Machine-readable error codes** — Every error response includes a `code` field (e.g. `INVALID_DOMAIN`, `INSUFFICIENT_FUNDS`) for programmatic branching
- **Header authentication** — Pass `X-API-Key` / `X-Secret-API-Key` as request headers; no JSON body required for read operations
- **GET support on read endpoints** — All read-only endpoints accept `GET` requests, making safe/idempotent operations distinguishable by HTTP method
- **Rate limit headers** — `Retry-After` (seconds to wait) on every 429, plus `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` on rate-limited endpoints, enable intelligent backoff
- **Spec discovery** — Every API response includes `Link: <https://porkbun.com/api/json/v3/spec>; rel="describedby"` so clients can self-discover this spec
- **Idempotency keys** — Send `Idempotency-Key: <unique-string>` on POST endpoints; retries within 24h return the cached response so a network blip can't double-charge
- **Official MCP server** — `npx -y @porkbunllc/mcp-server` exposes this entire API as native tools for Claude Desktop, Cursor, and other Model Context Protocol clients
- **Request IDs** — every response carries an `X-Request-Id` header and a `requestId` field in the JSON body; reference these in support tickets, retry-deduplication logic, or log correlation
- **Version signalling** — every response carries an `X-API-Version` header (matching this spec's `info.version`). The URL path stays at `/api/json/v3/` across all minor versions; minor bumps are always backward-compatible, so you can pin to `v3` and watch the header (or the Changelog below) to know what's available
- **Per-key scoping** — restrict each API key to specific source IPs (with CIDR support) and/or specific target domains. Lets you hand an agent a key that can only operate on the domains you intend, from the network you expect
- **Plain-text docs for LLMs** — a short overview at [/llms.txt](https://porkbun.com/llms.txt) and the full reference as flat Markdown at [/llms-full.txt](https://porkbun.com/llms-full.txt), and per-topic pages at [/llms](https://porkbun.com/llms) (no JavaScript required, generated from this spec)
- **Per-TLD registration requirements (JSON Schema)** — `GET /domain/getRegistrationRequirements/{tld}` returns whether a TLD is registerable via the API, the `/domain/create` body as a JSON Schema, and (for TLDs with registry eligibility rules like `.us`/`.ca`) a second JSON Schema of the required fields and allowed values. Assemble and validate a registration payload before sending it, instead of discovering requirements through failed attempts.
- **Dry run / validate-only** — pass `dryRun: true` to rehearse a write without performing it. On the billable ops (`/domain/create`, `/domain/renew`, `/domain/transfer`) it runs every pre-flight check and returns the would-be cost, balance, and `wouldSucceed` WITHOUT charging. It also works on **DNS record writes** (`/dns/create`, `/dns/edit`, `/dns/editByNameType`, `/dns/delete`, `/dns/deleteByNameType`) and **nameserver updates** (`/domain/updateNs`): validates ownership, the target record, and permissions, returns `wouldSucceed` WITHOUT mutating anything — so an agent can safely rehearse a destructive change before applying it
- **Per-operation safety metadata** — every operation in this spec carries an `x-porkbun-agent` vendor extension `{safe, cost, destructive, reversible, requiresConfirmation}` so a tool generator or planner can tell a read from a billable or destructive write without heuristics (the official MCP exposes equivalent read-only/destructive hints as standard MCP tool annotations)
- **Retryability on errors** — each error's `next_action` includes a `retryable` boolean: `true` only for transient conditions where re-sending the same request can succeed (rate limits, in-flight idempotency, provisioning-not-ready); `false` when you must change something first. Branch on it instead of guessing from the message

## Intended use

The Porkbun API is not a reseller service as defined under ICANN’s Registrar Accreditation Agreement (RAA). All domain registrations are processed directly by Porkbun as the registrar of record. The API is intended for managing domains within your own account or on behalf of clients, and does not establish a reseller relationship.

## Authentication

**1. JSON body (primary)** — Include `apikey` and `secretapikey` in the JSON request body. This is the standard method.

**2. Request headers** — Pass `X-API-Key: <apikey>` and `X-Secret-API-Key: <secretapikey>` as headers instead of in the body. Header auth takes effect only when no body credentials are present.

## HTTP status codes

- **400** — Request error (see `code` and `message` in response body)
- **403** — Additional authentication required (e.g. two-factor code)
- **429** — Rate limit exceeded (see the `Retry-After` header for seconds to wait; also `X-RateLimit-Reset`)

## Error codes

Every error response includes a `code` string field alongside `status: "ERROR"` and `message`. Use `code` for programmatic error handling; use `message` for display to users.

**Authentication and protocol**

| Code | Meaning |
|------|---------|
| `INVALID_PROTOCOL` | Request was not made over HTTPS |
| `METHOD_NOT_ALLOWED` | HTTP method not allowed for this endpoint |
| `INVALID_OR_EMPTY_JSON` | Request body is missing or not valid JSON |
| `API_KEY_REQUIRED` | No API key or token was provided |
| `INVALID_API_KEYS_001` | API key and secret combination is invalid |
| `INVALID_TOKEN` | Bearer token is invalid or expired |
| `INVALID_USER` | Account associated with the API key was not found or is not active |
| `IP_NOT_ALLOWED` | This API key has an IP allowlist configured and the request source IP is not in it. HTTP 403. |
| `DOMAIN_NOT_ALLOWED` | This API key has a domain allowlist configured and the target domain is not in it. HTTP 403. |

**Rate limiting**

| Code | Meaning |
|------|---------|
| `RATE_LIMIT_EXCEEDED` | Request rate limit reached; wait the seconds in the `Retry-After` header (also `ttlRemaining` field / `X-RateLimit-Reset` header) before retrying |

**Domain operations**

| Code | Meaning |
|------|---------|
| `INVALID_DOMAIN` | Domain parameter is invalid or not in your account |
| `DOMAIN_NOT_AVAILABLE` | Domain is not available for registration |
| `INSUFFICIENT_FUNDS` | Account credit is insufficient to complete the purchase |
| `REGISTRANT_CHANGE_NOT_SUPPORTED` | A registrant name/org change on a .au domain is a paid auDA ownership trade - do it at porkbun.com; admin/tech/billing edits work via the API |
| `ADDRESS_VALIDATION_REQUIRED` | The registrant address for an address-validated TLD needs validation; re-submit with addressValidationChoice using the returned suggestedAddress |

**DNS operations**

| Code | Meaning |
|------|---------|
| `INVALID_TYPE` | DNS record type is not supported |
| `INVALID_RECORD_ID` | DNS record ID was not found or is not owned by your account |

Additional endpoint-specific codes may be returned; always check `message` for details.

## API key scoping (IP &amp; domain restrictions)

Each API key can optionally be restricted to specific source IPs and/or specific target domains. Both restrictions are configured per key at [porkbun.com/account/api](https://porkbun.com/account/api) (click the gear icon next to any key). Empty/unset = no restriction.

**Source IP allowlist.** When set, requests from any other IP fail immediately with HTTP 403 `IP_NOT_ALLOWED`, before any other endpoint logic runs. Supports IPv4 and IPv6, both bare addresses and CIDR ranges. One entry per line in the UI. Examples:

```
203.0.113.10
198.51.100.0/24
2001:db8::/32
```

**Target domain allowlist.** When set, any operation against a domain not in the list fails with HTTP 403 `DOMAIN_NOT_ALLOWED`. Exact match only — `example.com` does not implicitly include `foo.example.com`, because each registered domain is independent. (Subdomains inside DNS records are scoped under the parent domain, which is what gets checked.) Example:

```
example.com
myothersite.io
```

**Recommended pattern for AI agents.** Hand the agent its own dedicated API key, restricted to the domains it actually needs to manage and (if you know the agent's egress IP) restricted to that IP. The blast radius of an accidentally-leaked key drops to `operations on these domains from this IP` instead of `anything on the account`.

## Idempotency

All v3 POST endpoints (excluding partner-only routes) accept an optional `Idempotency-Key` request header. When present, the API stores the response for 24 hours and replays it for any retry of the same request — so an agent that retries after a network blip will not double-charge or double-register.

**Key format:** any non-empty string up to 255 characters. UUIDs work well; agents can also use their own internal request IDs.

**Behavior on retry:**

- **Same key, same request body** within 24h → returns the original response with header `Idempotent-Replayed: true`
- **Same key, different request body** → returns 409 with `code: IDEMPOTENCY_KEY_MISMATCH` (catches accidental key reuse)
- **Same key, original request still in flight** → returns 409 with `code: IDEMPOTENCY_KEY_IN_USE` (retry shortly)
- **No header sent** → behavior is unchanged from before; no caching happens

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/create/example.com \
  -H "Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "X-API-Key: pk1_..." -H "X-Secret-API-Key: sk1_..." \
  -d '{"cost":973,"agreeToTerms":"yes"}'
```

## Rate limiting

Some endpoints are rate limited. When exceeded, the API returns HTTP 429 with a `RATE_LIMIT_EXCEEDED` body and a `Retry-After` header telling you how many seconds to wait before retrying (the same value as the `ttlRemaining` body field). Fixed-limit endpoints (`/apikey/request`, `/apikey/retrieve`) also return `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers on every call.

## Webhooks

Subscribe HTTPS endpoints to account events and Porkbun will `POST` a signed JSON payload to them as those events happen — no polling required. Manage endpoints with the **Webhooks** endpoints in this reference, the MCP tools (`create_webhook`, `list_webhooks`, …), or the web UI at `porkbun.com/account/api`.

**Event types:** `domain.registered`, `domain.renewed`, `domain.transfer.completed`, `domain.expiring` (fires at 60/30/5 days before expiry), `dns.record.created`, `dns.record.updated`, `dns.record.deleted`. Subscribe to specific types, to a prefix wildcard like `dns.*`, or to `*` for everything (recommended — you'll receive new event types automatically). Call `GET /webhook/eventTypes` for the live catalog.

**Payload envelope** — every delivery has the same outer shape:

```json
{
  "event": "domain.registered",
  "id": "018f9c2a-7b3e-7c41-9b8a-2f1e6d4c5a90",
  "createdAt": "2026-06-17T18:30:00Z",
  "data": { "domain": "example.com", "tld": "com", "expireDate": "2027-06-17 18:30:00" }
}
```

`id` is a UUIDv7 (time-ordered) and is also sent as the `X-Porkbun-Webhook-Id` header; use it to dedupe, since an endpoint may occasionally receive the same event more than once. The `data` object is event-specific.

**Delivery headers:**

- `X-Porkbun-Event` — the event type (e.g. `domain.renewed`).
- `X-Porkbun-Webhook-Id` — the event UUID (matches `id` in the body).
- `X-Porkbun-Webhook-Timestamp` — Unix seconds when the request was signed.
- `X-Porkbun-Signature` — `sha256=` + the signature (see below).

**Verify the signature.** The signature is `HMAC-SHA256(secret, "{timestamp}.{rawBody}")` where `secret` is the endpoint's signing secret, `{timestamp}` is the `X-Porkbun-Webhook-Timestamp` header value, and `{rawBody}` is the exact bytes of the request body (verify before parsing). Compare using a constant-time equality check, and reject timestamps that are too old (e.g. >5 minutes) to blunt replay attacks.

```php
$timestamp = $_SERVER['HTTP_X_PORKBUN_WEBHOOK_TIMESTAMP'];
$signature = $_SERVER['HTTP_X_PORKBUN_SIGNATURE']; // "sha256=..."
$body      = file_get_contents('php://input');
$expected  = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $endpointSecret);
if (!hash_equals($expected, $signature) || abs(time() - (int)$timestamp) > 300) {
    http_response_code(400); exit;
}
// signature OK — now json_decode($body) and process
```

```javascript
import crypto from 'node:crypto';
const ts  = req.header('X-Porkbun-Webhook-Timestamp');
const sig = req.header('X-Porkbun-Signature');
const expected = 'sha256=' + crypto.createHmac('sha256', endpointSecret)
  .update(`${ts}.${rawBody}`).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))
  && Math.abs(Date.now()/1000 - Number(ts)) < 300;
```

**Delivery log & manual resend.** Every attempt is recorded — list recent deliveries with `GET /webhook/deliveries` (filter by `endpointId` or `status`, newest first) and fetch one with its full payload via `GET /webhook/delivery/{id}`. History is retained ~30 days. To replay a delivery (e.g. after fixing a bug on your side), call `POST /webhook/resend` with the delivery `id`; it re-queues a fresh attempt reusing the **original event id**, so a consumer that dedupes on `X-Porkbun-Webhook-Id` treats it as the same event. The target endpoint must still exist and be ACTIVE.

**Responding & retries.** Return any `2xx` status to acknowledge. Non-2xx responses, timeouts, or connection errors are retried with exponential backoff (~1m, 5m, 30m, 2h, 6h) up to 6 attempts. An endpoint that racks up 20 consecutive failures is automatically disabled and the account owner emailed; re-enable it (which resets the failure counter) via `POST /webhook/update` with `status: "ACTIVE"`. Use `POST /webhook/test` to send a `webhook.test` event and confirm your verifier works end-to-end.

## Guarantees

Explicit contract points for autonomous clients. These are stable within the `v3` major version.

- **Operation timing** — nearly every operation is synchronous and takes effect before the response returns. The one long-running operation is an inbound domain transfer (days): poll `GET /domain/getTransfer/{domain}` or subscribe to the `domain.transfer.completed` webhook rather than blocking.
- **Idempotency** — `Idempotency-Key` on any POST makes retries safe: the response is stored for **24 hours** and replayed for an identical body; a same-key/different-body request returns `409 IDEMPOTENCY_KEY_MISMATCH`; an in-flight duplicate returns `409 IDEMPOTENCY_KEY_IN_USE`.
- **Webhook delivery** — at-least-once. Deduplicate on the `X-Porkbun-Webhook-Id` (UUIDv7) — an event may be delivered more than once, and **ordering is not guaranteed**. Failed deliveries retry ~1m/5m/30m/2h/6h (6 attempts); the delivery log is retained ~30 days; an endpoint with 20 consecutive failures is auto-disabled.
- **Error contract** — every error carries a stable `code` and (when known) a `next_action{type, hint, retryable, url?}`. Branch on `code` / `type` / `retryable`, never on `message` (messages may be reworded without a version bump; codes will not).
- **Rate limiting** — a `429` always includes a `Retry-After` header in whole seconds.
- **Backward compatibility** — the URL path stays `/api/json/v3/` for the life of v3. Minor bumps (tracked by `X-API-Version` and the Changelog below) are strictly additive — new endpoints, optional fields, error codes, or relaxed limits — and never remove or repurpose an existing field. Only a breaking change introduces a new major version and a new URL path.

## Changelog

The API URL path stays at `/api/json/v3/`. The version below (`major.minor`, also on the `X-API-Version` response header) tracks backward-compatible additions — new endpoints, new optional fields, new error codes, relaxed limits. Existing integrations are never broken by a minor bump; only a breaking change would introduce a new major version (and a new URL path).

### v3.15

- **Credential-free mock server.** `GET /mock` and `GET|POST /mock/<path>` return schema-accurate example responses for any endpoint with no key required (`?status=error` for the error shape).
- **Webhooks in the sandbox.** Sandbox operations emit signed webhook events, and `POST /sandbox/triggerWebhook` fires any event type on demand so you can test your handler end-to-end.
- **`Retry-After` on 429.** Every rate-limited response now sets a `Retry-After` header (whole seconds until the window resets) and returns HTTP 429 with `code: RATE_LIMIT_EXCEEDED`.
- **Agent metadata & explicit guarantees.** Every operation now carries an `x-porkbun-agent` extension (`safe`/`cost`/`destructive`/`reversible`/`requiresConfirmation`; `requiresConfirmation` is set for every billable or destructive operation — including a hosting deploy, which overwrites live public content); error `next_action` objects gained a `retryable` boolean; and a new **Guarantees** section documents idempotency retention, webhook delivery/ordering, the error contract, and the backward-compatibility policy.

### v3.14

- **Sandbox / test mode.** Use a `pk1_sb_` API key to run the whole API against an isolated sandbox (fake credit, no real registry/DNS/charges); responses carry `sandbox:true`. New `POST /sandbox/topup` and `POST /sandbox/reset`. Hosting/email return `SANDBOX_UNSUPPORTED` in sandbox. See “Sandbox / test mode” above.

### v3.13

- **Provision hosting by SKU.** `POST /hosting/create/{domain}` now takes a single `sku` (from `GET /hosting/plans`) instead of `product` + `plan` — one identifier to pass, and new hosting products/plans can be offered without a breaking request change.
- **`POST /hosting/makeDir/{domain}`** — create a directory (and missing parents) explicitly. Deploy already auto-creates the directories in a file’s `path`, so this is for standing up an empty directory.

### v3.12

- **Static site hosting via the API.** New **Hosting** endpoints provision and deploy Secure Static Hosting: `GET /hosting/plans` (discover provisionable plans + prices), `POST /hosting/create/{domain}` (15-day free trial on a domain’s first provision, auto-renews at the plan price; a re-provision after deprovision is charged to account credit — one free trial per domain), `GET /hosting/get/{domain}`, `POST /hosting/deploy/{domain}` (upload base64 files, ≤10 MB/request, static types only), `GET /hosting/files/{domain}`, `POST /hosting/deleteFile/{domain}`, and `POST /hosting/delete/{domain}` (deprovision). Provisioning switches the domain to Porkbun nameservers (gated behind `agreeToNameserverChange`) and requires `acknowledgedCost`. New codes: `COST_ACKNOWLEDGMENT_REQUIRED`, `NAMESERVER_CHANGE_REQUIRED`, `HOSTING_ALREADY_EXISTS`, `HOSTING_NOT_FOUND`, `HOSTING_NOT_READY`, `HOSTING_PROVISION_FAILED`, `FILE_TOO_LARGE`.

### v3.11

- **Address validation for contact edits.** A registrant change via `POST /domain/updateContacts` for a TLD that requires a validated address (.de/.nrw/.uk/.us/.ca/.nyc/.au/.eu/.in/.nz families) now runs Google Address Validation (per-account rate-limited + 24h cached) instead of being blocked. If the address needs correction the call returns `ADDRESS_VALIDATION_REQUIRED` with a `suggestedAddress`; re-submit with `addressValidationChoice` = `accept_suggestion` or `use_as_entered`. For .de, DENIC registry verification is then auto-attempted (address fingerprint / email); a proof-of-address document upload, if still required, is completed at porkbun.com. Only a .au registrant name/org change stays `REGISTRANT_CHANGE_NOT_SUPPORTED` (paid ownership trade).

### v3.10

- **Edit domain contacts.** New `GET /domain/getContacts/{domain}` returns the four contacts (registrant/admin/tech/billing), and `POST /domain/updateContacts/{domain}` edits them. Send a `contacts` object with any subset of roles (unspecified roles are left unchanged) or a single `contact` applied to all four. Mirrors the website: pushes to the registry on thick TLDs and a registrant change fires the same new-owner notice/verification email (no 60-day lock). Supports `dryRun`. A registrant name/org change on .au and any registrant change on address-validation TLDs (.de/.nrw) return `REGISTRANT_CHANGE_NOT_SUPPORTED` (do those at porkbun.com); admin/tech/billing edits still work.

### v3.9

- **Smoother agent key handoff (PKCE)** - `POST /apikey/request` now accepts an optional PKCE `codeChallenge` (RFC 7636, S256). When supplied, the account holder just approves in the browser (nothing to copy) and `POST /apikey/retrieve` returns BOTH the public and secret keys once to the caller presenting the matching `codeVerifier` - the key is minted lazily at retrieve time, so the secret is never shown in the browser or persisted. Omit `codeChallenge` for the unchanged legacy flow (public key only; secret shown in the browser). Adds response field `deliveryMode` and codes `CODE_VERIFIER_REQUIRED`, `INVALID_CODE_VERIFIER`, `SECRET_ALREADY_CLAIMED`.

### v3.8

- **URL forwarding — full web-dashboard parity.** `POST /domain/addUrlForward` now accepts `masked` forwards and an optional `redirectType` (`301`/`302`/`307`/`masked`) to select the exact redirect code — this is how you create a 307 temporary redirect or a masked forward via the API. `redirectType` takes precedence over `type` and defaults to 302 for temporary. `GET /domain/getUrlForwarding` now returns `redirectType` (the exact stored code) alongside `type`, so 302 and 307 are distinguishable. Backward compatible — `type`-only requests are unchanged.

### v3.7

- **Clearer auth errors** — a request with a valid API key but a missing or misnamed secret now returns `MISSING_SECRETAPIKEY` with an explicit message (the field is `secretapikey`, not `secretkey`) instead of the generic `INVALID_API_KEYS_002`. A genuinely wrong secret still returns the deliberately-vague `INVALID_API_KEYS_002`, and auth errors now carry `next_action` hints.

### v3.6

- **Validate-only writes** — `dryRun: true` now works beyond the billable endpoints: DNS record writes (`/dns/create`, `/dns/edit`, `/dns/editByNameType`, `/dns/delete`, `/dns/deleteByNameType`) and nameserver updates (`/domain/updateNs`) validate and return `wouldSucceed` without mutating anything. Rehearse a destructive change before applying it.

### v3.5

- **Actionable errors** — most error responses now include a `next_action` object (`{type, hint, url?}`) telling an agent how to recover (e.g. re-quote the price, enable API access, add funds, register on the website). `type` is a small stable vocabulary; branch on it instead of string-matching messages.

### v3.4

- **TLD registration requirements as JSON Schema** — `GET /domain/getRegistrationRequirements/{tld}` returns whether a TLD is API-registerable, the `/domain/create` body as a JSON Schema, and (for TLDs with registry eligibility rules like .us/.ca) a second schema enumerating the required fields and allowed values. Lets an agent validate a registration before attempting it.

### v3.3

- **Outbound webhooks** — register HTTPS endpoints that Porkbun POSTs signed JSON to when lifecycle events occur (`domain.registered`, `domain.renewed`, `domain.transfer.completed`, `domain.expiring`, `dns.record.created|updated|deleted`). Manage them under the **Webhooks** endpoints below or at `porkbun.com/account/api`. Each delivery is signed with HMAC-SHA256 — see the **Webhooks** section above.

### v3.2

- **Dry run** — `dryRun: true` on register/renew/transfer previews availability, cost, balance, and `wouldSucceed` without charging or creating anything.
- **LLM-readable docs** — full API reference as flat Markdown at `/llms-full.txt`, plus per-topic pages indexed at `/llms`, generated from this spec; refreshed `/llms.txt` index; non-JavaScript fallback on the docs page so agents can read it.

### v3.1

- **Idempotency** — `Idempotency-Key` request header on POST endpoints; retries within 24h replay the original response instead of re-charging.
- **Request IDs** — `X-Request-Id` response header and `requestId` body field on every response.
- **Version signalling** — `X-API-Version` response header.
- **Domain transfers** — `transfer`, `getTransfer`, and `listTransfers` endpoints for inbound transfers via API.
- **Single-domain lookup** — `GET /domain/get/{domain}` plus new filters on `listAll` (`domain`, `nameContains`, `tlds`, `expiringWithinDays`, `autoRenew`, `apiAccess`, `sortName`, `sortDirection`).
- **Marketplace filtering** — server-side `query`, `tlds`, `sldLengthMin`/`Max`, and sort parameters on `marketplace/getAll`.
- **Account endpoints** — `GET /account/balance` and `GET /account/apiSettings`.
- **Spend controls** — per-account monthly spend limit, low-balance alert, and auto top-up, enforced on registrations/renewals/transfers.
- **Per-key restrictions** — each API key can be scoped to specific source IPs (with CIDR support) and/or specific target domains (`IP_NOT_ALLOWED` / `DOMAIN_NOT_ALLOWED`).
- **DNS record types** — `HTTPS`, `SVCB`, and `SSHFP` documented as supported types.
- **Machine-readable error codes** — every error response includes a `code` field.

---

# Endpoints

## POST /api/json/v3/ping

**Test credentials and get caller IP**

Returns the caller's public IP address. Optionally validates API credentials.

- **No credentials supplied** — returns IP only.
- **Valid credentials supplied** — returns IP with `credentialsValid: true`.
- **Invalid credentials supplied** — returns an error.

Useful for agents and clients to verify their API key is working before making other calls.

```bash
curl -X POST https://api.porkbun.com/api/json/v3/ping \
  -H 'Content-Type: application/json' \
  -d '[]'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `yourIp` | string | The caller's public IP address |
| `xForwardedFor` | string | Raw value of the X-Forwarded-For header |
| `credentialsValid` | boolean | Present and true when valid credentials were supplied |

## GET /api/json/v3/ping

**Test credentials and get caller IP**

Returns the caller's public IP address. Optionally validates API credentials.

- **No credentials supplied** — returns IP only.
- **Valid credentials supplied** — returns IP with `credentialsValid: true`.
- **Invalid credentials supplied** — returns an error.

Useful for agents and clients to verify their API key is working before making other calls.

| Parameter | In | Required | Description |
|---|---|---|---|
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

```bash
curl 'https://api.porkbun.com/api/json/v3/ping'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `yourIp` | string | The caller's public IP address |
| `xForwardedFor` | string | Raw value of the X-Forwarded-For header |
| `credentialsValid` | boolean | Present and true when valid credentials were supplied |

## POST /api/json/v3/ip

**Get caller IP address**

Returns the caller's public IP address. No credentials required. Use the `api-ipv4.porkbun.com` hostname if you need to force an IPv4 address.

```bash
curl -X POST https://api.porkbun.com/api/json/v3/ip \
  -H 'Content-Type: application/json' \
  -d '[]'
```

Response fields (IpResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `yourIp` | string | The caller's public IP address |
| `xForwardedFor` | string | Raw value of the X-Forwarded-For header |

## GET /api/json/v3/ip

**Get caller IP address**

Returns the caller's public IP address. No credentials required. Use the `api-ipv4.porkbun.com` hostname if you need to force an IPv4 address.

```bash
curl 'https://api.porkbun.com/api/json/v3/ip'
```

Response fields (IpResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `yourIp` | string | The caller's public IP address |
| `xForwardedFor` | string | Raw value of the X-Forwarded-For header |

## POST /api/json/v3/pricing/get

**Retrieve domain pricing (public)**

Retrieve default domain pricing information for all supported TLDs. Does not require authentication. Prices are in US dollars.

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `tlds` | string[] | no | Optional array of TLDs to filter results. If omitted, all supported TLDs are returned. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/pricing/get \
  -H 'Content-Type: application/json' \
  -d '{"tlds":[]}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `pricing` | object | Object keyed by TLD string |

## GET /api/json/v3/pricing/get

**Retrieve domain pricing (public)**

Retrieve default domain pricing information for all supported TLDs. Does not require authentication. Prices are in US dollars.

This GET form returns pricing for all TLDs. To filter by specific TLDs, use `POST /pricing/get` with a `tlds` array in the request body.

```bash
curl 'https://api.porkbun.com/api/json/v3/pricing/get'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `pricing` | object | Object keyed by TLD string |

## POST /api/json/v3/apikey/request

**Initiate an API key authorization request**

Initiate an API key authorization flow. No credentials are required. Returns a `requestToken` and `authUrl` that the account holder must visit (while logged in to Porkbun in that browser) to approve the request. The `authUrl` is valid for **10 minutes**; if it lapses, call this endpoint again for a fresh token. After approval, call `/apikey/retrieve` to get the public API key. By default (legacy flow) the secret API key is shown only once, in the user's browser, and must be pasted into the application manually. To let an agent receive the secret without a browser copy, supply a PKCE `codeChallenge` (see the parameter below): the key is then minted lazily and BOTH keys are returned once from `/apikey/retrieve` to the caller presenting the matching `codeVerifier`.

**Rate limit:** 20 requests per IP per 3600 seconds.

SANDBOX: pass `sandbox: true` to skip approval and get a sandbox key pair back immediately (both `apikey` and `secretapikey` in the response, plus `sandbox: true`).

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | no | Human-readable name for the application/integration requesting access. Optional but strongly recommended: it is shown to the account holder on the approval screen so they know what they're granting access to. If omitted, the approval page shows "No application name was provided." |
| `codeChallenge` | string | no | Optional PKCE (RFC 7636) binding. base64url(SHA-256(codeVerifier)), 43 unpadded chars. When present, the request uses lazy-mint delivery: after approval the account holder copies nothing, and /apikey/retrieve returns BOTH the public and secret keys once to the caller that presents the matching codeVerifier. Omit for the legacy flow (secret shown only in the browser). |
| `codeChallengeMethod` | string | no | PKCE challenge method. Only S256 is supported. |
| `sandbox` | boolean | no | If true, skip the approval flow and immediately return a throwaway SANDBOX key pair (public `pk1_sb_`, secret `sk1_sb_`) for a fresh test account seeded with $1000 fake credit. Use it as apikey/secretapikey against the same base URL to run the whole API in the isolated sandbox — no real registry actions, DNS changes, or charges. Rate-limited (20/IP/hour). |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/apikey/request \
  -H 'Content-Type: application/json' \
  -d '[]'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `requestToken` | string | Token used to poll `/apikey/retrieve` to check approval status. |
| `authUrl` | string | URL the account holder must visit to approve the request |
| `expiration` | string | ISO datetime when this request expires (10 minutes from creation) |
| `deliveryMode` | string | Which delivery flow this request uses. 'pkce' when a codeChallenge was supplied (the secret is returned once via /apikey/retrieve to the verifier holder); 'legacy' otherwise (the secret is shown only in the browser). |
| `message` | string | Human-readable instructions |

## POST /api/json/v3/apikey/retrieve

**Poll for API key approval**

Poll to check whether the account holder has approved an API key authorization request. Returns `status: PENDING` while awaiting approval. On approval, returns the public API key. For a **PKCE** request (one created with a codeChallenge), pass the matching codeVerifier here to receive BOTH keys once (secretapikey is included in that single response and never again); the secret must be claimed within 10 minutes of approval or the request expires (`REQUEST_EXPIRED`) and a new one is needed. For a **legacy** request the secret API key is never transmitted via this endpoint — it is displayed in the user's browser and must be pasted into the application.

**Rate limit:** 120 requests per IP per 3600 seconds.

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `requestToken` | string | yes | The token returned by /apikey/request. Must be a 64-character lowercase hex string. |
| `codeVerifier` | string | no | PKCE verifier (RFC 7636). Required only when the request was created with a codeChallenge. The high-entropy secret whose base64url(SHA-256(codeVerifier)) equals that challenge. When it matches, this response includes the secret key (secretapikey) once. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/apikey/retrieve \
  -H 'Content-Type: application/json' \
  -d '[]'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `apikey` | string | The approved public API key. Present only when status is SUCCESS. |
| `secretapikey` | string | The secret API key. Returned ONLY for a PKCE request, exactly once, on the retrieve that presents a valid codeVerifier. Never returned for legacy requests, and never again after the first successful claim (later calls return apikey only, with code SECRET_ALREADY_CLAIMED). Store it immediately. |
| `message` | string |  |
| `code` | string | Machine-readable error code. Present when status is ERROR. |

## POST /api/json/v3/domain/checkDomain/{domain}

**Check domain availability**

Check if a domain is available for registration and retrieve current pricing. Includes registration, renewal, and transfer prices.

**Rate limit:** Configurable per API key. Default is 1 check per 10 seconds per account. Rate limit usage is returned in the `limits` field of the response.

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

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/checkDomain/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields (CheckDomainResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `response` | object |  |
| `limits` | object | Current rate limit usage for this account |
| `ttlRemaining` | integer | Seconds remaining until the rate limit window resets |

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

**Register a domain**

Register a domain using account credit. Requirements:
- Account email and phone must be verified
- Account must have sufficient credit
- `agreeToTerms` must be `'yes'` or `'1'`
- `cost` must equal the current price for the domain's minimum registration duration (in pennies)
- Account must have placed at least one previous domain registration
- Premium domains cannot be registered via API

Registrations are always for the registry-minimum duration (usually 1 year).

**WHOIS privacy.** WHOIS privacy is automatically enabled on new registrations (when the TLD supports it). Pass the optional `whoisPrivacy` field to override this on a per-registration basis, or change the account-level default under Account Security Settings on porkbun.com/account.

**Rate limits (both apply):**
- Attempt limit (default: 1 attempt per 10 seconds per account)
- Success limit (default: 50 successful registrations per 86400 seconds per account)

Both limits are configurable per API key and their current values are returned in the `limits` field of the response.

## Dry run

Add `dryRun: true` to validate everything and preview the cost WITHOUT registering or charging — nothing is created. Example response:

```json
{
  "status": "SUCCESS",
  "dryRun": true,
  "wouldSucceed": true,
  "operation": "registration",
  "domain": "example.com",
  "tld": "com",
  "available": "available",
  "premium": false,
  "duration": 1,
  "cost": 973,
  "costDisplay": "$9.73",
  "balance": 5000,
  "sufficientFunds": true,
  "message": "Dry run: this registration would succeed and cost $9.73. No order was created and no charge was made.",
  "requestId": "019e04fa-258d-7d11-aa86-4d5795c3fe8f"
}
```

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

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `cost` | integer | yes | The registration cost in pennies (USD cents). Must exactly equal the total price for the domain at its minimum registration duration. Obtain this from /domain/checkDomain first. |
| `agreeToTerms` | string | yes | Must be 'yes' or '1' to confirm agreement to the Domain Name Registration Agreement, Product Terms of Service, Privacy Policy, and automatic renewal terms. |
| `whoisPrivacy` | boolean | no | Optional. Override WHOIS privacy for this registration. When omitted, the account-level default is used (set under Account Security Settings on porkbun.com/account — defaults to enabled). Pass `true` to force-enable privacy or `false` to register with public contact info. Strings `"on"`/`"off"`, `"true"`/`"false"`, `"yes"`/`"no"`, `"1"`/`"0"` are also accepted. Has no effect on TLDs that don't support WHOIS privacy (privacy stays off regardless). |
| `dryRun` | boolean | no | Optional. When true, runs all pre-flight validation (availability, pricing, cost match, eligibility, funds, spend limit) and returns a preview with `dryRun: true` and `wouldSucceed` WITHOUT creating an order or charging. Nothing is registered and the rate-limit budget is not consumed. Use it to safely confirm an operation before committing. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/create/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","cost":0,"agreeToTerms":"yes"}'
```

Response fields (CreateDomainResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string | The registered domain name |
| `cost` | integer | The total amount charged in pennies |
| `orderId` | integer | Internal Porkbun order ID |
| `limits` | object | Current rate limit state for both attempt and success limits |
| `balance` | integer | Remaining account credit balance in pennies after the charge |
| `ttlRemaining` | integer | Seconds until the success rate limit window resets |
| `requestId` | string | Per-request UUID (also in the X-Request-Id header). Present on every API response. |

Response fields (DryRunPreviewResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `dryRun` | boolean | Always true on a dry-run preview — distinguishes it from a real success response. |
| `wouldSucceed` | boolean | True if the operation would complete given current funds and spend-limit state. (Hard validation failures — unavailable, bad price, ineligible — return a normal error response instead of a preview.) |
| `operation` | string |  |
| `domain` | string |  |
| `tld` | string |  |
| `available` | string | Availability as reported by the registry check (e.g. `available` / `unavailable`). |
| `premium` | boolean | Whether the domain is a premium/aftermarket name. |
| `duration` | integer | Term in years that would be purchased. |
| `cost` | integer | Total cost in pennies that would be charged. |
| `costDisplay` | string | Human-readable cost. |
| `balance` | integer | Current account credit balance in pennies. |
| `sufficientFunds` | boolean | Whether the balance covers the cost. |
| `monthlySpendLimit` | integer | The account's monthly API spend cap in pennies. Present only if a cap is configured. |
| `monthlySpendSoFar` | integer | API spend so far this calendar month in pennies. Present only if a cap is configured. |
| `withinMonthlySpendLimit` | boolean | Whether this cost stays within the monthly cap. Present only if a cap is configured. |
| `message` | string | Human-readable summary of the preview outcome. |
| `requestId` | string | Per-request UUID (also in the X-Request-Id header). Present on every API response. |

## POST /api/json/v3/domain/renew/{domain}

**Renew a domain**

Renew a domain using account credit. Requirements:
- Domain must be in your account and active
- Domain must be opted in to API access
- Account email and phone must be verified
- Account must have sufficient credit
- `cost` must equal the current renewal price for the domain's minimum renewal duration (in pennies)
- Domain must have been registered more than 30 days ago (checked against the domain's creation date)
- Domain must not have been successfully renewed within the last 30 days
- Premium renewals are not currently supported via API

Renewals are always for the registry-minimum duration (usually 1 year). Use `/domain/checkDomain/{domain}` with `priceType=renewal` to get the current price before renewing.

**Rate limits (both apply):**
- Attempt limit (default: 1 attempt per 10 seconds per account)
- Success limit (default: 50 successful renewals per 86400 seconds per account)

Both limits are configurable per API key and their current values are returned in the `limits` field of the response.

## Dry run

Add `dryRun: true` to validate everything and preview the cost WITHOUT renewing or charging — nothing is created. Example response:

```json
{
  "status": "SUCCESS",
  "dryRun": true,
  "wouldSucceed": true,
  "operation": "renewal",
  "domain": "example.com",
  "tld": "com",
  "available": "unavailable",
  "premium": false,
  "duration": 1,
  "cost": 1099,
  "costDisplay": "$10.99",
  "balance": 5000,
  "sufficientFunds": true,
  "message": "Dry run: this renewal would succeed and cost $10.99. No order was created and no charge was made.",
  "requestId": "019e04fa-3c11-7a02-9bd2-1f7c0e4a8b55"
}
```

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

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `cost` | integer | yes | The renewal cost in pennies (USD cents). Must exactly equal the total price for the domain at its minimum renewal duration. Obtain this from /domain/checkDomain first. |
| `dryRun` | boolean | no | Optional. When true, runs all pre-flight validation and returns a preview with `dryRun: true` and `wouldSucceed` WITHOUT renewing or charging. Nothing changes and the rate-limit budget is not consumed. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/renew/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","cost":0}'
```

Response fields (RenewDomainResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string | The renewed domain name |
| `expirationDate` | string | The new expiration date returned by the registry |
| `cost` | integer | The total amount charged in pennies |
| `orderId` | integer | Internal Porkbun order ID |
| `limits` | object | Current rate limit state for both attempt and success limits |
| `balance` | integer | Remaining account credit balance in pennies after the charge |
| `ttlRemaining` | integer | Seconds until the success rate limit window resets |
| `requestId` | string | Per-request UUID (also in the X-Request-Id header). Present on every API response. |

Response fields (DryRunPreviewResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `dryRun` | boolean | Always true on a dry-run preview — distinguishes it from a real success response. |
| `wouldSucceed` | boolean | True if the operation would complete given current funds and spend-limit state. (Hard validation failures — unavailable, bad price, ineligible — return a normal error response instead of a preview.) |
| `operation` | string |  |
| `domain` | string |  |
| `tld` | string |  |
| `available` | string | Availability as reported by the registry check (e.g. `available` / `unavailable`). |
| `premium` | boolean | Whether the domain is a premium/aftermarket name. |
| `duration` | integer | Term in years that would be purchased. |
| `cost` | integer | Total cost in pennies that would be charged. |
| `costDisplay` | string | Human-readable cost. |
| `balance` | integer | Current account credit balance in pennies. |
| `sufficientFunds` | boolean | Whether the balance covers the cost. |
| `monthlySpendLimit` | integer | The account's monthly API spend cap in pennies. Present only if a cap is configured. |
| `monthlySpendSoFar` | integer | API spend so far this calendar month in pennies. Present only if a cap is configured. |
| `withinMonthlySpendLimit` | boolean | Whether this cost stays within the monthly cap. Present only if a cap is configured. |
| `message` | string | Human-readable summary of the preview outcome. |
| `requestId` | string | Per-request UUID (also in the X-Request-Id header). Present on every API response. |

## POST /api/json/v3/domain/transfer/{domain}

**Initiate a domain transfer**

Initiates an inbound domain transfer to Porkbun using account credit. The transfer is processed asynchronously and typically takes 5–7 days to complete.

**Requirements:**
- Account email and phone must be verified.
- Sufficient account credit to cover the transfer cost.
- Domain must not already be in your account.
- No other active transfer for the same domain.
- `.uk` and manage-only TLDs are not supported via API.
- Premium domain transfers are not supported via API.

## Dry run

Add `dryRun: true` to validate everything and preview the cost WITHOUT initiating the transfer or charging — nothing is created. Example response:

```json
{
  "status": "SUCCESS",
  "dryRun": true,
  "wouldSucceed": true,
  "operation": "transfer",
  "domain": "example.com",
  "tld": "com",
  "available": "unavailable",
  "premium": false,
  "duration": 1,
  "cost": 999,
  "costDisplay": "$9.99",
  "balance": 5000,
  "sufficientFunds": true,
  "message": "Dry run: this transfer would succeed and cost $9.99. No order was created and no charge was made.",
  "requestId": "019e04fa-5f22-7c93-8a41-2e9d0b3f6c77"
}
```

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes | The domain name to transfer (e.g. `example.com`). |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `authCode` | string | yes | The EPP auth code for the domain. |
| `cost` | integer | yes | The transfer cost in cents as returned by the pricing API. Must match exactly. |
| `dryRun` | boolean | no | Optional. When true, runs all pre-flight validation and returns a preview with `dryRun: true` and `wouldSucceed` WITHOUT initiating the transfer or charging. Nothing changes and the rate-limit budget is not consumed. |

```bash
curl 'https://api.porkbun.com/api/json/v3/domain/transfer/example.com' \
  -H 'Content-Type: application/json' \
  -d '{
    "apikey": "pk1_...",
    "secretapikey": "sk1_...",
    "authCode": "abc123",
    "cost": 899
  }'
```

Response fields (TransferDomainResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `orderId` | integer |  |
| `transferId` | integer |  |
| `message` | string |  |
| `balance` | integer | Remaining account credit balance in cents. |
| `ttlRemaining` | integer |  |
| `limits` | object |  |
| `requestId` | string | Per-request UUID (also in the X-Request-Id header). Present on every API response. |

Response fields (DryRunPreviewResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `dryRun` | boolean | Always true on a dry-run preview — distinguishes it from a real success response. |
| `wouldSucceed` | boolean | True if the operation would complete given current funds and spend-limit state. (Hard validation failures — unavailable, bad price, ineligible — return a normal error response instead of a preview.) |
| `operation` | string |  |
| `domain` | string |  |
| `tld` | string |  |
| `available` | string | Availability as reported by the registry check (e.g. `available` / `unavailable`). |
| `premium` | boolean | Whether the domain is a premium/aftermarket name. |
| `duration` | integer | Term in years that would be purchased. |
| `cost` | integer | Total cost in pennies that would be charged. |
| `costDisplay` | string | Human-readable cost. |
| `balance` | integer | Current account credit balance in pennies. |
| `sufficientFunds` | boolean | Whether the balance covers the cost. |
| `monthlySpendLimit` | integer | The account's monthly API spend cap in pennies. Present only if a cap is configured. |
| `monthlySpendSoFar` | integer | API spend so far this calendar month in pennies. Present only if a cap is configured. |
| `withinMonthlySpendLimit` | boolean | Whether this cost stays within the monthly cap. Present only if a cap is configured. |
| `message` | string | Human-readable summary of the preview outcome. |
| `requestId` | string | Per-request UUID (also in the X-Request-Id header). Present on every API response. |

## GET /api/json/v3/domain/getTransfer/{domain}

**Get transfer status**

Returns the most recent transfer record for the specified domain in your account. Authenticate using `X-API-Key` and `X-Secret-API-Key` headers, or `Authorization: Bearer <token>`.

| Parameter | In | Required | Description |
|---|---|---|---|
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |
| `domain` | path | yes | The domain name (e.g. `example.com`). |

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

Response fields (GetTransferResponse):

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

## GET /api/json/v3/domain/listTransfers

**List active transfers**

Returns all active inbound domain transfers for your account (excludes completed and canceled transfers). Authenticate using `X-API-Key` and `X-Secret-API-Key` headers, or `Authorization: Bearer <token>`.

| Parameter | In | Required | Description |
|---|---|---|---|
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

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

Response fields (ListTransfersResponse):

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

## POST /api/json/v3/domain/listAll

**List all domains**

Retrieve domains in the authenticated account. Results are returned in chunks of up to 1000 domains. Use `start` to paginate.

**Filtering:** all filter parameters are optional. Combine them freely.

- `domain` — exact match (returns 0 or 1)
- `nameContains` — substring search
- `tlds` — limit to these TLDs
- `expiringWithinDays` — only domains expiring within N days
- `autoRenew` — `yes` / `no`
- `apiAccess` — `yes` / `no` (filter to domains the API key can operate on)
- `sortName` — `domain` / `tld` / `create_date` / `expire_date`
- `sortDirection` — `asc` / `desc`

Supports both GET (with header auth) and POST (with body or header auth). For multi-value `tlds` on GET, use bracket syntax: `?tlds[]=com&tlds[]=io`.

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `start` | integer | no | Zero-based offset for pagination (default: 0). Returns up to 1000 domains per call. |
| `includeLabels` | string | no | Return label metadata for each domain. Defaults to no. |
| `domain` | string | no | Exact domain name match. Returns 0 or 1 result. Useful as an alternative to `/domain/get/{domain}`. |
| `nameContains` | string | no | Substring match against the full domain name. Case-insensitive. |
| `expiringWithinDays` | integer | no | Filter to domains expiring within this many days (relative to now). |
| `tlds` | string[] | no | Limit to these TLDs (without leading dot). |
| `autoRenew` | string | no | Filter to domains with auto-renew on or off. |
| `apiAccess` | string | no | Filter to domains opted in to API access (yes) or not (no). Useful for finding domains an API key can actually operate on. |
| `sortName` | string | no | Field to sort by. Default: `expire_date` ascending. |
| `sortDirection` | string | no | Sort direction. Default: asc. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/listAll \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","start":0,"includeLabels":"yes"}'
```

Response fields (DomainListAllResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `count` | integer | Number of domains returned in this page. |
| `domains` | object[] |  |

## GET /api/json/v3/domain/listAll

**List all domains**

Retrieve domains in the authenticated account. Results are returned in chunks of up to 1000 domains. Use `start` to paginate.

**Filtering:** all filter parameters are optional. Combine them freely.

- `domain` — exact match (returns 0 or 1)
- `nameContains` — substring search
- `tlds` — limit to these TLDs
- `expiringWithinDays` — only domains expiring within N days
- `autoRenew` — `yes` / `no`
- `apiAccess` — `yes` / `no` (filter to domains the API key can operate on)
- `sortName` — `domain` / `tld` / `create_date` / `expire_date`
- `sortDirection` — `asc` / `desc`

Supports both GET (with header auth) and POST (with body or header auth). For multi-value `tlds` on GET, use bracket syntax: `?tlds[]=com&tlds[]=io`.

| Parameter | In | Required | Description |
|---|---|---|---|
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |
| `start` | query | no | Zero-based offset for pagination. Returns up to 1000 domains per call. |
| `includeLabels` | query | no | Return label metadata for each domain. Defaults to no. |
| `domain` | query | no | Exact domain name match. Returns 0 or 1 result. |
| `nameContains` | query | no | Substring match against the full domain name. Case-insensitive. |
| `expiringWithinDays` | query | no | Filter to domains expiring within this many days. |
| `tlds` | query | no | Limit to these TLDs (no leading dot). Use bracket form: `?tlds[]=com&tlds[]=io`. |
| `autoRenew` | query | no | Filter to domains with auto-renew on or off. |
| `apiAccess` | query | no | Filter to domains opted in to API access. |
| `sortName` | query | no | Field to sort by. Default: `expire_date` ascending. |
| `sortDirection` | query | no | Sort direction. Default: asc. |

```bash
curl 'https://api.porkbun.com/api/json/v3/domain/listAll?tlds[]=com&expiringWithinDays=30&autoRenew=no' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields (DomainListAllResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `count` | integer | Number of domains returned in this page. |
| `domains` | object[] |  |

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

**Get a single domain**

Get the metadata for a single domain in the authenticated account. Returns the same per-domain shape as `listAll` items but as a single object. Returns HTTP 404 with code `DOMAIN_NOT_FOUND` if the domain isn't in the account.

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes | Fully qualified domain name in the authenticated account. |
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |
| `includeLabels` | query | no | Return label metadata. Defaults to no. |

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

Response fields:

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

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

**Update auto-renew setting**

Update the auto-renew setting for one or more domains. The domain can be passed in the URL path or in the `domains` array in the request body (or both). Both are combined and deduplicated.

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | no | Optional single domain in URL. Omit or use `/domain/updateAutoRenew/` (without trailing domain) when using the `domains` body array instead. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `status` | string | yes | Auto-renew status to set |
| `domains` | string[] | no | Array of additional domain names to update. Combined with the domain in the URL path if provided. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/updateAutoRenew/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","status":"on","domains":[]}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `results` | object | Object keyed by domain name |

## POST /api/json/v3/domain/getNs/{domain}

**Get nameservers**

Retrieve the authoritative nameservers listed at the registry for the domain. Supports both GET (with header auth) and POST (with body or header auth).

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

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/getNs/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `ns` | string[] |  |

## GET /api/json/v3/domain/getNs/{domain}

**Get nameservers**

Retrieve the authoritative nameservers listed at the registry for the domain. Supports both GET (with header auth) and POST (with body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Authorization` | header | no | Bearer token auth: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

```bash
curl 'https://api.porkbun.com/api/json/v3/domain/getNs/{domain}' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `ns` | string[] |  |

## POST /api/json/v3/domain/updateNs/{domain}

**Update nameservers**

Update the nameservers for the domain at the registry.

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

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `ns` | string[] | yes | Ordered array of nameserver hostnames |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/updateNs/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","ns":["ns1.example.com","ns2.example.com"]}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `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/domain/getGlue/{domain}

**Get glue records**

Retrieve all glue records (host objects) registered under the domain. Supports both GET (with header auth) and POST (with body or header auth).

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

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/getGlue/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `hosts` | array[] | Array of [hostname, ipAddresses] tuples. Each element is a two-item array: index 0 is the full hostname (string), index 1 is an object with `v4` (array of IPv4 strings) and `v6` (array of IPv6 strings). Example: `["ns1.example.com", {"v4": ["1.2.3.4"], "v6": []}]` |

## GET /api/json/v3/domain/getGlue/{domain}

**Get glue records**

Retrieve all glue records (host objects) registered under the domain. Supports both GET (with header auth) and POST (with body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Authorization` | header | no | Bearer token auth: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

```bash
curl 'https://api.porkbun.com/api/json/v3/domain/getGlue/{domain}' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `hosts` | array[] | Array of [hostname, ipAddresses] tuples. Each element is a two-item array: index 0 is the full hostname (string), index 1 is an object with `v4` (array of IPv4 strings) and `v6` (array of IPv6 strings). Example: `["ns1.example.com", {"v4": ["1.2.3.4"], "v6": []}]` |

## POST /api/json/v3/domain/createGlue/{domain}/{subdomain}

**Create glue record**

Create a glue record (host object) for a nameserver hostname under the domain. Use this when you want to host a nameserver at a subdomain of the domain itself (e.g. ns1.example.com).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `subdomain` | path | yes | The subdomain portion only (e.g. 'ns1' for ns1.example.com) |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `ips` | string[] | yes | Array of IP addresses (IPv4 and/or IPv6) to associate with the host record |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/createGlue/{domain}/{subdomain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","ips":["1.2.3.4","2001:db8::1"]}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `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/domain/updateGlue/{domain}/{subdomain}

**Update glue record**

Update the IP addresses of a glue record. All existing IP addresses are replaced with the supplied list.

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

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `ips` | string[] | yes | Array of IP addresses (IPv4 and/or IPv6) to associate with the host record |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/updateGlue/{domain}/{subdomain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","ips":["1.2.3.4","2001:db8::1"]}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `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/domain/deleteGlue/{domain}/{subdomain}

**Delete glue record**

Delete a glue record (host object) for a subdomain.

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

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/deleteGlue/{domain}/{subdomain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `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/domain/getUrlForwarding/{domain}

**List URL forwards**

Retrieve all active URL forwards for a domain. Supports both GET (with header auth) and POST (with body or header auth).

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

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/getUrlForwarding/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields (GetUrlForwardingResponse):

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

## GET /api/json/v3/domain/getUrlForwarding/{domain}

**List URL forwards**

Retrieve all active URL forwards for a domain. Supports both GET (with header auth) and POST (with body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Authorization` | header | no | Bearer token auth: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

```bash
curl 'https://api.porkbun.com/api/json/v3/domain/getUrlForwarding/{domain}' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields (GetUrlForwardingResponse):

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

## POST /api/json/v3/domain/addUrlForward/{domain}

**Add URL forward**

Add a URL forward for a domain or subdomain.

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

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `subdomain` | string | no | Subdomain to forward (optional, leave blank or omit for root domain). Alphanumeric and hyphens only. |
| `location` | string | yes | Destination URL to forward to |
| `type` | string | yes | Redirect kind. 'permanent' = HTTP 301; 'temporary' = HTTP 302 (default); 'masked' = loads the destination in a frame (URL masking). For a precise code — including a 307 temporary redirect — use `redirectType`. |
| `redirectType` | string | no | Optional. The exact redirect type; takes precedence over `type` when supplied. 301 = permanent, 302 or 307 = temporary (this is how you request 307), masked = URL masking. Omit to derive from `type` (temporary->302, permanent->301). |
| `includePath` | string | yes | Whether to append the request URI path to the forwarding destination |
| `wildcard` | string | yes | Whether to also forward all subdomains of the forwarded subdomain |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/addUrlForward/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","subdomain":"www","location":"https:\/\/destination.example.com","type":"temporary","includePath":"yes","wildcard":"yes"}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `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/domain/deleteUrlForward/{domain}/{id}

**Delete URL forward**

Delete a specific URL forward by ID.

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `id` | path | yes | URL forward record ID |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/deleteUrlForward/{domain}/{id} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `message` | string | Human-readable message. Present on ERROR, sometimes on SUCCESS. |
| `code` | string | Machine-readable error code. Present when status is ERROR. |

## GET /api/json/v3/domain/getContacts/{domain}

**Get domain contacts**

Return the domain's four contacts (registrant, admin, tech, billing) with their current field values. Also available via POST.

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

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

Response fields:

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

## POST /api/json/v3/domain/updateContacts/{domain}

**Update domain contacts**

Edit the domain's contacts. Send a `contacts` object keyed by role (`registrant`, `admin`, `tech`, `billing`) containing ANY subset of roles — unspecified roles keep their current values — or a single `contact` object to apply to all four. Behaves like the website: on thick TLDs the change is pushed to the registry, and a **registrant** change (name/organization/email) triggers the same material-change record and new-owner notice/verification email (no 60-day transfer lock is imposed). Per-TLD extension data (e.g. .us nexus, .ca legalType) is preserved. Supports `dryRun`.

**Address-validated TLDs.** On a **registrant** change for a TLD that requires a validated address (`.de`, `.nrw`, `.uk`/`.co.uk`/…, `.us`, `.ca`, `.nyc`, `.au`, `.eu`, `.in`/`.co.in`/…, `.nz`/`.co.nz`/…), the API runs Google Address Validation (per-account rate-limited + 24h cached). If the address needs correction the call returns `ADDRESS_VALIDATION_REQUIRED` with a `suggestedAddress` and `addressValidationStatus`; re-submit with `addressValidationChoice` = `accept_suggestion` (save the standardized address) or `use_as_entered` (keep yours — stays blocked if a real correction was offered). For `.de`, DENIC registry verification is then attempted automatically (address-fingerprint / email); if it still needs a proof-of-address document upload, that is completed at porkbun.com. **Still not supported via API:** a registrant name/organization change on a `.au` domain is an auDA paid ownership trade and returns `REGISTRANT_CHANGE_NOT_SUPPORTED` (do it at porkbun.com). Admin/tech/billing edits are unaffected everywhere.

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

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `contacts` | object | no | Any subset of roles to change; unspecified roles are left as-is. |
| `contact` |  | no | A single contact applied to all four roles. Use this OR `contacts`, not both. |
| `dryRun` | boolean | no | Validate and report wouldSucceed without applying the change. |
| `addressValidationChoice` | string | no | Only for a registrant change on an address-validated TLD after an ADDRESS_VALIDATION_REQUIRED response. 'accept_suggestion' saves the standardized suggestedAddress; 'use_as_entered' keeps the submitted address (rejected if a real correction was offered). |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/domain/updateContacts/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","contacts":{"tech":{"firstName":"Ada","lastName":"Lovelace","organization":"Analytical Engines","address1":"1 Countess Rd","city":"London","state":"","postalCode":"NW5 1AA","country":"GB","phone":"2071234567","phoneCountryCode":"44","email":"ada@example.com"}}}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `message` | string |  |
| `registrantChanged` | boolean |  |

## POST /api/json/v3/dns/retrieve/{domain}

**Retrieve all DNS records**

Retrieve all editable DNS records for a domain. SOA records and Porkbun default NS records are excluded. Supports both GET (with header auth) and POST (with body or header auth).

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

```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/retrieve/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields (DnsRecordsResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `cloudflare` | string | Whether Cloudflare proxy is enabled for this domain |
| `records` | object[] |  |

## GET /api/json/v3/dns/retrieve/{domain}

**Retrieve all DNS records**

Retrieve all editable DNS records for a domain. SOA records and Porkbun default NS records are excluded. Supports both GET (with header auth) and POST (with body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Authorization` | header | no | Bearer token auth: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

```bash
curl 'https://api.porkbun.com/api/json/v3/dns/retrieve/{domain}' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields (DnsRecordsResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `cloudflare` | string | Whether Cloudflare proxy is enabled for this domain |
| `records` | object[] |  |

## POST /api/json/v3/dns/retrieve/{domain}/{id}

**Retrieve DNS record by ID**

Retrieve a specific DNS record by its numeric ID. Supports both GET (with header auth) and POST (with body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `id` | path | yes | Numeric DNS record ID |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/retrieve/{domain}/{id} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields (DnsRecordsResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `cloudflare` | string | Whether Cloudflare proxy is enabled for this domain |
| `records` | object[] |  |

## GET /api/json/v3/dns/retrieve/{domain}/{id}

**Retrieve DNS record by ID**

Retrieve a specific DNS record by its numeric ID. Supports both GET (with header auth) and POST (with body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `id` | path | yes | Numeric DNS record ID |
| `Authorization` | header | no | Bearer token auth: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

```bash
curl 'https://api.porkbun.com/api/json/v3/dns/retrieve/{domain}/{id}' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields (DnsRecordsResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `cloudflare` | string | Whether Cloudflare proxy is enabled for this domain |
| `records` | object[] |  |

## POST /api/json/v3/dns/retrieveByNameType/{domain}/{type}/{subdomain}

**Retrieve DNS records by name and type**

Retrieve all DNS records for a domain that match a specific subdomain and record type. Omit `subdomain` (or leave the path segment empty) to query the root domain. Supports both GET (with header auth) and POST (with body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `type` | path | yes | DNS record type (A, AAAA, CNAME, MX, TXT, etc.) |
| `subdomain` | path | no | Subdomain portion only. Omit or leave empty for root domain records. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/retrieveByNameType/{domain}/{type}/{subdomain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields (DnsRecordsResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `cloudflare` | string | Whether Cloudflare proxy is enabled for this domain |
| `records` | object[] |  |

## GET /api/json/v3/dns/retrieveByNameType/{domain}/{type}/{subdomain}

**Retrieve DNS records by name and type**

Retrieve all DNS records for a domain that match a specific subdomain and record type. Omit `subdomain` (or leave the path segment empty) to query the root domain. Supports both GET (with header auth) and POST (with body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `type` | path | yes | DNS record type (A, AAAA, CNAME, MX, TXT, etc.) |
| `subdomain` | path | no | Subdomain portion only. Omit or leave empty for root domain records. |
| `Authorization` | header | no | Bearer token auth: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

```bash
curl 'https://api.porkbun.com/api/json/v3/dns/retrieveByNameType/{domain}/{type}/{subdomain}' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields (DnsRecordsResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `cloudflare` | string | Whether Cloudflare proxy is enabled for this domain |
| `records` | object[] |  |

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

**Create DNS record**

Create a new DNS record for a domain. The record ID is returned in the response.

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

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | no | Subdomain for the record (e.g. 'www', '*' for wildcard, blank for root). Do not include the domain name itself. |
| `type` | string | yes | DNS record type |
| `content` | string | yes | The record value |
| `ttl` | integer | no | Time to live in seconds. Minimum is determined by account settings (typically 600). Defaults to the account minimum if omitted or 0. |
| `prio` | integer | no | Priority for MX and SRV records. Defaults to 0 if omitted. |
| `notes` | string | no | Optional notes to store with the record (not served in DNS) |
| `dryRun` | boolean | no | If true, validate only — checks ownership/type/permissions and returns wouldSucceed without creating the record. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/create/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","name":"www","type":"A","content":"1.2.3.4","ttl":600,"prio":10}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `id` | string | The numeric ID of the newly created record |

## POST /api/json/v3/dns/edit/{domain}/{id}

**Edit DNS record by ID**

Edit a specific DNS record by its numeric ID. SOA and default Porkbun NS records cannot be edited.

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `id` | path | yes | Numeric DNS record ID |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | no | Subdomain for the record. Do not include the domain name itself. |
| `type` | string | yes | DNS record type |
| `content` | string | yes | The record value |
| `ttl` | integer | no | Time to live in seconds (optional) |
| `prio` | integer | no | Priority for MX/SRV records (optional) |
| `notes` | string | no | Notes (optional). Pass empty string to clear notes; omit or pass null to leave unchanged. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/edit/{domain}/{id} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","type":"A","content":"5.6.7.8","ttl":0,"prio":0}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `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/dns/editByNameType/{domain}/{type}/{subdomain}

**Edit DNS records by name and type**

Replace the content of all records matching the given subdomain and type. SOA and NS records cannot be edited with this method (use edit by ID instead).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `type` | path | yes |  |
| `subdomain` | path | no | Subdomain portion only. Omit or leave empty for root domain records. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `content` | string | yes | New record value to set on all matching records |
| `ttl` | integer | no | Time to live in seconds (optional) |
| `prio` | integer | no | Priority (optional) |
| `notes` | string | no | Notes to store with the record (not served in DNS). Pass an empty string to clear existing notes; pass null or omit this field to leave notes unchanged. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/editByNameType/{domain}/{type}/{subdomain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","content":"5.6.7.8","ttl":0,"prio":0}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `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/dns/delete/{domain}/{id}

**Delete DNS record by ID**

Delete a specific DNS record. SOA and default Porkbun NS records cannot be deleted.

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `id` | path | yes | Numeric DNS record ID |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/delete/{domain}/{id} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `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/dns/deleteByNameType/{domain}/{type}/{subdomain}

**Delete DNS records by name and type**

Delete all DNS records matching the given subdomain and type. SOA and NS records cannot be deleted with this method (use delete by ID instead).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `type` | path | yes |  |
| `subdomain` | path | no | Subdomain portion only. Omit or leave empty for root domain records. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/deleteByNameType/{domain}/{type}/{subdomain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `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/dns/getDnssecRecords/{domain}

**Get DNSSEC records**

Retrieve DNSSEC records associated with the domain at the registry. Supports both GET (with header auth) and POST (with body or header auth).

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

```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/getDnssecRecords/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `records` | object | Object keyed by key tag value. Each value is an object containing the DNSSEC data fields. |

## GET /api/json/v3/dns/getDnssecRecords/{domain}

**Get DNSSEC records**

Retrieve DNSSEC records associated with the domain at the registry. Supports both GET (with header auth) and POST (with body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Authorization` | header | no | Bearer token auth: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

```bash
curl 'https://api.porkbun.com/api/json/v3/dns/getDnssecRecords/{domain}' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `records` | object | Object keyed by key tag value. Each value is an object containing the DNSSEC data fields. |

## POST /api/json/v3/dns/createDnssecRecord/{domain}

**Create DNSSEC record**

Create a DNSSEC DS or key record at the registry. DNSSEC requirements vary by registry — `keyTag`, `alg`, `digestType`, and `digest` are the minimum required fields. Key data fields are optional and will be omitted if not accepted by the registry.

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

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `keyTag` | string | yes | DNSSEC key tag |
| `alg` | string | yes | DS Data algorithm number (e.g. 13 for ECDSA P-256 SHA-256) |
| `digestType` | string | yes | Digest type number (e.g. 2 for SHA-256) |
| `digest` | string | yes | Hex-encoded digest value |
| `maxSigLife` | string | no | Maximum signature lifetime in seconds (optional, registry-specific) |
| `keyDataFlags` | string | no | Key data flags (optional, used when submitting full key data) |
| `keyDataProtocol` | string | no | Key data protocol (optional) |
| `keyDataAlgo` | string | no | Key data algorithm (optional) |
| `keyDataPubKey` | string | no | Key data public key in base64 (optional) |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/createDnssecRecord/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","keyTag":"12345","alg":"13","digestType":"2","digest":"ABCD1234..."}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `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/dns/deleteDnssecRecord/{domain}/{keytag}

**Delete DNSSEC record**

Delete a DNSSEC record from the registry by key tag. Note: most registries delete all records matching the key data, not only the record with the specified key tag.

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `keytag` | path | yes | The DNSSEC key tag value |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/dns/deleteDnssecRecord/{domain}/{keytag} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `message` | string | Human-readable message. Present on ERROR, sometimes on SUCCESS. |
| `code` | string | Machine-readable error code. Present when status is ERROR. |

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

**List provisionable hosting plans**

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.

```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 Secure Static Hosting**

Provision Secure Static Hosting 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 (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.

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

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

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

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

**Deploy files to hosting**

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.

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

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

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

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

Delete a file (or empty directory) at `path` in the domain's hosting space.

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

Request body fields:

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

Response fields:

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

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

**Deprovision hosting**

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

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

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `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/ssl/retrieve/{domain}

**Retrieve SSL bundle**

Retrieve the Let's Encrypt SSL certificate bundle for a domain. The certificate must already be issued (status HAVECERT). Token-based access is not supported for this endpoint. Supports both GET (with header auth) and POST (with body or header auth).

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

```bash
curl -X POST https://api.porkbun.com/api/json/v3/ssl/retrieve/{domain} \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `certificatechain` | string | The full PEM-encoded certificate chain (certificate + intermediates) |
| `privatekey` | string | The PEM-encoded private key |
| `publickey` | string | The PEM-encoded public key |

## GET /api/json/v3/ssl/retrieve/{domain}

**Retrieve SSL bundle**

Retrieve the Let's Encrypt SSL certificate bundle for a domain. The certificate must already be issued (status HAVECERT). Token-based access is not supported for this endpoint. Supports both GET (with header auth) and POST (with body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Authorization` | header | no | Bearer token auth: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

```bash
curl 'https://api.porkbun.com/api/json/v3/ssl/retrieve/{domain}' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `certificatechain` | string | The full PEM-encoded certificate chain (certificate + intermediates) |
| `privatekey` | string | The PEM-encoded private key |
| `publickey` | string | The PEM-encoded public key |

## POST /api/json/v3/email/setPassword

**Set email hosting password**

Set the password for an email hosting account associated with a domain managed by your API key.

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `emailAddress` | string | yes | The full email address (e.g. user@example.com) |
| `password` | string | yes | The new password. Must pass Porkbun password validation rules. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/email/setPassword \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","emailAddress":"user@example.com"}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `message` | string | Human-readable message. Present on ERROR, sometimes on SUCCESS. |
| `code` | string | Machine-readable error code. Present when status is ERROR. |

## GET /api/json/v3/marketplace/getAll

**List marketplace domains**

GET form of `/marketplace/getAll` for read-friendly filtering and URL-shareable searches. Authenticate via `X-API-Key` and `X-Secret-API-Key` headers, or `Authorization: Bearer <token>`. All filter params are optional and mirror the POST body. For multi-value `tlds`, use bracket syntax: `?tlds[]=com&tlds[]=io`.

See the POST documentation for the full filtering semantics (unfiltered pagination vs filtered mode, `+include` / `-exclude` query prefixes, sort options).

| Parameter | In | Required | Description |
|---|---|---|---|
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |
| `query` | query | no | SLD substring search. Multi-word; prefix a term with `-` to exclude. Example: `ai -test`. |
| `tlds` | query | no | Filter to listings under these TLDs (without leading dot). Use bracket form: `?tlds[]=com&tlds[]=io`. |
| `sldLengthMin` | query | no | Minimum SLD character length. |
| `sldLengthMax` | query | no | Maximum SLD character length. |
| `sortName` | query | no | Field to sort filtered results by. |
| `sortDirection` | query | no | Sort direction. |
| `start` | query | no | Pagination offset (unfiltered mode only). |
| `limit` | query | no | Page size (unfiltered mode only). Default 1000, max 5000. |

```bash
curl 'https://api.porkbun.com/api/json/v3/marketplace/getAll?query=ai&tlds[]=com&tlds[]=io&sldLengthMax=6&sortName=price&sortDirection=asc' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `count` | integer |  |
| `filtered` | boolean |  |
| `domains` | object[] |  |

## POST /api/json/v3/marketplace/getAll

**List marketplace domains**

Retrieve domains listed on the Porkbun marketplace. Two modes:

- **Unfiltered (default):** paginated raw listing, up to 5000 entries per call via `start` / `limit`.
- **Filtered:** when any of `query`, `tlds`, `sldLengthMin`, `sldLengthMax`, or `sortName` is provided, results are filtered server-side. Filtered mode returns up to 1000 matching listings (matches the web UI's marketplace search).

`query` supports `+include` and `-exclude` prefixes per word against the SLD (e.g. `+ai -test` matches SLDs containing 'ai' but not 'test'). Token-based access is not supported.

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `start` | integer | no | Pagination offset (unfiltered mode only). Default 0. |
| `limit` | integer | no | Number of domains to return (unfiltered mode only). Default 1000, max 5000. |
| `query` | string | no | Search string. Each space-separated term filters by SLD substring. Prefix a term with `-` to exclude it. |
| `tlds` | string[] | no | Filter to listings under these TLDs (without the leading dot). |
| `sldLengthMin` | integer | no | Filter to listings whose SLD has at least this many characters. |
| `sldLengthMax` | integer | no | Filter to listings whose SLD has at most this many characters. |
| `sortName` | string | no | Field to sort filtered results by. Default `sld_length` ascending when `query` is set, otherwise `create_date` descending. |
| `sortDirection` | string | no | Sort direction. Defaults vary by `sortName`. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/marketplace/getAll \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","start":0,"limit":1000}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `count` | integer | Number of domains returned in this response |
| `filtered` | boolean | True when one or more filter parameters were applied (query, tlds, sldLengthMin/Max, sortName). |
| `domains` | object[] |  |

## POST /api/json/v3/auth/login

**Login with username and password**

**Partner-only endpoint** (requires `auth:login` access on the API key). Authenticate a Porkbun account with username and password and receive a short-lived token (5 minutes). Supports TOTP and email-based 2FA. Returns HTTP 403 with a `2FA` field when a second factor is required.

| Parameter | In | Required | Description |
|---|---|---|---|
| `Sig` | header | yes | Base64-encoded SHA-256 digest of the trimmed request body, signed with the private key associated with the API key. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `username` | string | yes |  |
| `password` | string | yes |  |
| `twoFactorCode` | string | no | TOTP or email 2FA code, if required |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `token` | string | Short-lived session token (5 minutes) |
| `expiration` | string |  |

## POST /api/json/v3/account/invite

**Create an account registration invite**

Generates a one-time invite token and URL that you send to a prospective user. When the user visits the URL they go through Porkbun's normal account creation flow — including CAPTCHA, address collection, and TOS acceptance — in the browser. The invite expires in 48 hours. Each token can only be used once.

Optionally supply an `email` to pre-fill the email field on the registration form.

Optionally supply a `returnUrl` (must be HTTPS) to redirect the user back to your platform after they complete registration.

After sending the invite URL, poll `/account/inviteStatus` to check whether the account was created.

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `email` | string | no | Email address to pre-fill on the registration form (optional) |
| `returnUrl` | string | no | HTTPS URL to redirect the user to after successful registration (optional). Use this to send users back to your platform after they complete account creation. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/account/invite \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","email":"newuser@example.com"}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `inviteToken` | string | Opaque token — pass to `/account/inviteStatus` to track completion |
| `inviteUrl` | string | URL to send to the prospective user. Opens Porkbun's standard registration page. |
| `expires` | string | UTC datetime when the invite token expires (48 hours from creation) |

## GET /api/json/v3/account/inviteStatus

**Check account invite status**

Returns the current status of a registration invite created by your API key.

- `PENDING` — the invite URL has not yet been used
- `ACCEPTED` — the user completed registration; `newAccountId` contains their account ID
- `EXPIRED` — the invite was not used within 48 hours or was canceled

You can only query invites created by your own API key. Pass credentials via `X-API-Key` and `X-Secret-API-Key` headers.

| Parameter | In | Required | Description |
|---|---|---|---|
| `token` | query | yes | The `inviteToken` returned by `/account/invite` |
| `X-API-Key` | header | yes | Your API key |
| `X-Secret-API-Key` | header | yes | Your secret API key |

```bash
curl 'https://api.porkbun.com/api/json/v3/account/inviteStatus?token=a3f8c2...' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `inviteStatus` | string | Current state of the invite |
| `newAccountId` | integer | ID of the newly created account. Present only when `inviteStatus` is `ACCEPTED`. |

## GET /api/json/v3/account/balance

**Get account balance**

Returns the available account credit balance. Authenticate using `X-API-Key` and `X-Secret-API-Key` headers, or `Authorization: Bearer <token>`.

| Parameter | In | Required | Description |
|---|---|---|---|
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

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

Response fields (BalanceResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `balance` | integer | Available account credit balance in cents. |
| `display` | string | Human-readable balance string (e.g. `$12.34`). |

## GET /api/json/v3/account/apiSettings

**Get API spend settings**

Returns the account's API spend control settings and current month's spend total. All amounts are in cents. Authenticate using `X-API-Key` and `X-Secret-API-Key` headers, or `Authorization: Bearer <token>`.

| Parameter | In | Required | Description |
|---|---|---|---|
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

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

Response fields (ApiSettingsResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `settings` | object |  |
| `monthlySpend` | integer | Total API spend in the current calendar month, in cents. |

## GET /api/json/v3/webhook/eventTypes

**List subscribable event types**

Return the catalog of event types a webhook endpoint can subscribe to. Read-only; supports GET (header auth) or POST (body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

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

Response fields (WebhookEventTypesResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `eventTypes` | string[] |  |

## GET /api/json/v3/webhook/list

**List webhook endpoints**

List all webhook endpoints registered on the authenticated account, including each endpoint's signing secret and delivery health. Read-only; supports GET (header auth) or POST (body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

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

Response fields (WebhookListResponse):

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

## GET /api/json/v3/webhook/get/{id}

**Get a webhook endpoint**

Fetch a single webhook endpoint by id, including its signing secret and delivery health. Read-only; supports GET (header auth) or POST (body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `id` | path | yes |  |
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

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

Response fields (WebhookEndpointResponse):

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

## POST /api/json/v3/webhook/create

**Create a webhook endpoint**

Register an HTTPS endpoint to receive signed event payloads. The response includes the generated `secret` — store it securely; it is the HMAC key used to verify the `X-Porkbun-Signature` header. Omit `events` (or pass `["*"]`) to subscribe to all event types. Maximum 20 endpoints per account.

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | string | yes | HTTPS URL to deliver events to. |
| `events` | string[] | no | Event types to subscribe to. Omit, or pass ["*"], for all events. Prefix wildcards like "dns.*" are allowed. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/webhook/create \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","url":"https://example.com/porkbun/webhook","events":["*"]}'
```

Response fields (WebhookEndpointResponse):

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

## POST /api/json/v3/webhook/update

**Update a webhook endpoint**

Update an endpoint's URL, event subscriptions, and/or status. Only the supplied fields change. Set `status` to `DISABLED` to pause deliveries or `ACTIVE` to resume (resuming also resets the consecutive-failure counter).

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | integer | yes | Endpoint id to update. |
| `url` | string | no | New HTTPS URL (optional). |
| `events` | string[] | no | Replacement event subscription list (optional). |
| `status` | string | no | Set ACTIVE to resume (also clears the failure counter) or DISABLED to pause (optional). |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/webhook/update \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","id":42,"status":"DISABLED"}'
```

Response fields (WebhookEndpointResponse):

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

## POST /api/json/v3/webhook/rotateSecret

**Rotate the signing secret**

Generate a new signing secret for an endpoint and return the endpoint with the new secret. Deliveries are signed with the new secret immediately, so update your verifier as part of the same operation.

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | integer | yes | Endpoint id. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/webhook/rotateSecret \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","id":42}'
```

Response fields (WebhookEndpointResponse):

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

## POST /api/json/v3/webhook/test

**Send a test event**

Enqueue a `webhook.test` event to the endpoint so you can confirm reachability and that your signature verification works. The endpoint must be ACTIVE. Delivery is asynchronous (usually within a minute).

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | integer | yes | Endpoint id. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/webhook/test \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","id":42}'
```

Response fields (WebhookTestResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `eventId` | string | UUID of the queued webhook.test event. |
| `message` | string |  |

## POST /api/json/v3/webhook/delete

**Delete a webhook endpoint**

Delete a webhook endpoint by id. Deliveries stop immediately.

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | integer | yes | Endpoint id. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/webhook/delete \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","id":42}'
```

Response fields:

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

## GET /api/json/v3/webhook/deliveries

**List webhook deliveries**

List recent delivery attempts across the account (newest first), optionally filtered by endpoint or status. The bulky payload is omitted — use GET /webhook/delivery/{id} for it. History is retained about 30 days. Read-only; supports GET (header auth) or POST (body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `endpointId` | query | no | Only deliveries for this endpoint. |
| `status` | query | no | Filter by delivery status. |
| `start` | query | no | Pagination offset (default 0). |
| `limit` | query | no | Page size, 1-200 (default 50). |
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

```bash
curl 'https://api.porkbun.com/api/json/v3/webhook/deliveries?status=FAILED&limit=50' \
  -H 'X-API-Key: pk1_...' \
  -H 'X-Secret-API-Key: sk1_...'
```

Response fields (WebhookDeliveryListResponse):

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `deliveries` | object[] | Newest first. The bulky `payload` field is omitted here. |
| `total` | integer | Total matching deliveries (for pagination). |
| `start` | integer | Offset of this page. |
| `limit` | integer | Page size used. |
| `message` | string |  |

## GET /api/json/v3/webhook/delivery/{id}

**Get a webhook delivery**

Fetch a single delivery including the full event payload that was (or will be) sent and its delivery status. Read-only; supports GET (header auth) or POST (body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `id` | path | yes |  |
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

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

Response fields (WebhookDeliveryResponse):

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

## POST /api/json/v3/webhook/resend

**Resend a delivery**

Re-queue a past delivery to its endpoint. Clones it into a fresh attempt reusing the ORIGINAL event id (so consumers that dedupe on X-Porkbun-Webhook-Id treat it as the same event). The endpoint must still exist and be ACTIVE. The original delivery row is left intact as history.

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | integer | yes | Endpoint id. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/webhook/resend \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","id":9001}'
```

Response fields (WebhookResendResponse):

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

## GET /api/json/v3/domain/getRegistrationRequirements/{tld}

**Get TLD registration requirements (JSON Schema)**

Machine-readable registration requirements for a TLD. Returns whether the TLD is registerable via the API (`apiRegisterable`), the `/domain/create` request body as a JSON Schema, the fixed registration term, WHOIS-privacy/validated-address/registrant-only flags, and — for TLDs with registry eligibility rules (e.g. .us nexus, .ca legal type) — a second JSON Schema (`registryRequirements`) enumerating those fields with allowed values and labels. Call this before /domain/create to know upfront whether and how a TLD can be registered. Read-only; GET (header auth) or POST (body or header auth).

| Parameter | In | Required | Description |
|---|---|---|---|
| `tld` | path | yes | TLD without a leading dot, e.g. `com`, `us`, `ca`. |
| `Authorization` | header | no | Bearer token: `Authorization: Bearer <token>` |
| `X-API-Key` | header | no | API key header auth (use with X-Secret-API-Key) |
| `X-Secret-API-Key` | header | no | Secret API key header auth (use with X-API-Key) |

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

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `tld` | string |  |
| `apiRegisterable` | boolean | Whether this TLD can be registered via the API. False for TLDs with registry eligibility requirements the API cannot submit (register those on the website). |
| `registrationDurationYears` | integer | Fixed registration term the API uses for this TLD. |
| `maxRegistrationYears` | integer | Maximum years the registry allows, or null if unspecified. |
| `whoisPrivacySupported` | boolean |  |
| `requiresValidatedAddress` | boolean |  |
| `registrantOnly` | boolean | TLD uses only the registrant contact (no admin/tech/billing). |
| `requestSchema` | object | JSON Schema (Draft 2020-12) for the /domain/create request body this TLD accepts (cost, agreeToTerms, whoisPrivacy, credentials), including the fixed registration term. |
| `registryRequirements` | object | JSON Schema of extra registry/eligibility fields the TLD requires (e.g. .us purpose+category, .ca legal type) with enums and human labels, plus an x-policyNote. Null when the TLD has no structured extra data. These fields are documented for eligibility; they are not accepted by /domain/create today. |
| `notApiRegisterableReason` | string | Present only when apiRegisterable is false. |

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

**Create a hosting directory**

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

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

Request body fields:

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

Response fields:

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

## POST /api/json/v3/sandbox/topup

**Sandbox: add fake credit**

Sandbox only (requires a `pk1_sb_` key). Grants fake account credit so paid operations can keep being exercised after funds run out. Optional `amount` in US cents (default 100000 = $1000, capped 1,000,000).

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `amount` | integer | no | Fake credit to add in US cents (default 100000; max 1000000). |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `granted` | integer |  |
| `balance` | integer |  |
| `display` | string |  |
| `sandbox` | boolean |  |

## POST /api/json/v3/sandbox/reset

**Sandbox: reset to a clean slate**

Sandbox only (requires a `pk1_sb_` key). Wipes the sandbox account's simulated state (domains, DNS, orders, credit) and re-grants $1000 fake credit.

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `message` | string |  |
| `domainsCleared` | integer |  |
| `balance` | integer |  |
| `sandbox` | boolean |  |

## POST /api/json/v3/sandbox/triggerWebhook

**Sandbox: fire a sample webhook event**

Sandbox only (requires a `pk1_sb_` key). Enqueues a signed sample webhook event to your registered endpoints so you can test your handler and signature verification for any event type on demand — including cron-driven events like `domain.expiring` that don't result from a single API call. Register an endpoint first with `POST /webhook/create`.

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `eventType` | string | yes | Event type to emit. |
| `domain` | string | no | Domain used in the sample payload (default example.com). |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `event` | string |  |
| `data` | object |  |
| `endpointsRegistered` | integer |  |
| `message` | string |  |
| `sandbox` | boolean |  |

## GET /api/json/v3/mock

**Mock server: list mockable endpoints**

Credential-free. Returns a directory of every endpoint that can be mocked, each with a ready-to-call mock URL.

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `mock` | boolean |  |
| `count` | integer |  |
| `endpoints` | object[] |  |

## GET /api/json/v3/mock/{path}

**Mock server: example response for an endpoint**

Credential-free. Mirror any real endpoint path after `/mock` (e.g. `/mock/domain/listAll`, `/mock/dns/create/example.com`) to get a schema-accurate example success response. Append `?status=error` for the error-response shape. Touches no datastore; signalled by the `X-Porkbun-Mock: true` header.

| Parameter | In | Required | Description |
|---|---|---|---|
| `path` | path | yes | The real endpoint path to mock, e.g. `domain/listAll`. |
| `status` | query | no | Set to `error` to return the error-response shape. |

---

## 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://github.com/oborseth/Porkbun-MCP (`npx -y @porkbunllc/mcp-server`)
- Create API keys: https://porkbun.com/account/api
