# Porkbun API — agent setup

You are an AI coding agent setting up the Porkbun API for this project. Do these steps yourself. The only thing you need a human for is approving API access in their browser (step 2).

> **Heads-up:** how you install depends on your environment — **Step 1 routes by capability.** Shell-capable agents (Claude Code, Cursor, Cline) install it directly; GUI clients (Claude Desktop) need a human to edit config; hosted or web-sandboxed agents (ChatGPT, claude.ai, some "work" modes) can't install a local server, but their human can add Porkbun's **hosted connector** in the app's settings (path C).

## 1. Install the Porkbun MCP server — pick the path for your environment

### A. You can run shell commands and edit files (Claude Code, Cursor agent, Cline) — do it yourself

**Claude Code** — one command (keys land in your private local config, not committed):

```bash
claude mcp add porkbun \
  --env PORKBUN_API_KEY=pk1_... \
  --env PORKBUN_SECRET_API_KEY=sk1_... \
  -- npx -y @porkbunllc/mcp-server
```

**No keys at all?** Claude Code can use Porkbun's hosted server instead, with a browser sign-in in place of API keys: `claude mcp add --transport http --scope user porkbun https://mcp.porkbun.com/mcp`, then have the human run `/mcp`, select `porkbun`, and authenticate in the browser (the Porkbun page's **Allow Claude Code** button). Skip steps 2 and 3 in that case (the connection is its own key). Use the local server below when you want pinned versions, a sandbox key, or keys scoped by IP.

The `--env` flags go **before** the `--`; everything after `--` is the command that starts the server. Needs Node 18+. If the first run times out while `npx` downloads the package, retry with `MCP_TIMEOUT=60000 claude`. No keys yet? Do step 2 first, then run this. Verify with `claude mcp list` (look for `porkbun ✔ Connected`).

`npx -y @porkbunllc/mcp-server` fetches the **latest** version on every run. For reproducible installs, pin a version — `@porkbunllc/mcp-server@x.y.z` (see the latest on [npm](https://www.npmjs.com/package/@porkbunllc/mcp-server)).

**Cursor / Cline / other clients** — write this to the client's MCP config file and make sure `npx` is on PATH:

```json
{
  "mcpServers": {
    "porkbun": {
      "command": "npx",
      "args": ["-y", "@porkbunllc/mcp-server"],
      "env": {
        "PORKBUN_API_KEY": "pk1_...",
        "PORKBUN_SECRET_API_KEY": "sk1_..."
      }
    }
  }
}
```

### B. GUI client you can't edit programmatically (Claude Desktop) — hand the human these steps

1. **Settings → Developer → Edit Config** (opens `claude_desktop_config.json`).
2. Paste the JSON block above (merge into any existing `mcpServers`).
3. Save, then **fully quit and reopen** the app (not just close the window).

Resume once the `porkbun` tools appear.

### C. Hosted / web-sandboxed agent (ChatGPT, claude.ai, some "work" modes) — you can't install a local server

Porkbun hosts the server at `https://mcp.porkbun.com/mcp`. Ask the human to add it as a custom connector (Claude: **Customize → Connectors → + → Add custom connector**, paste the URL, **Add**, then **Connect**; ChatGPT: turn on **Developer mode** under **Settings → Security and login**, then **Plugins → +** with the URL as a public endpoint) and click **Allow** on the Porkbun page that opens. In Claude they then switch it on per chat from **+ → Connectors**; in ChatGPT, from the tools menu. There are no API keys to handle, so **skip steps 2 and 3** — the connection is its own key, bounded by the account's per-domain API access and spend limit. Per-client steps: https://porkbun.com/llms/guides/connect-chatgpt-or-claude

Until it is connected you can still read the docs directly; they need no credentials: `/llms.txt`, `/llms-full.txt`, `/api/json/v3/spec`.

---

The server exposes the whole Porkbun API — domains, DNS, SSL, URL forwarding, webhooks — plus documentation tools. **The documentation tools (`search_docs`, `read_doc`, `list_doc_topics`) need no credentials**, so you can install the server and read the docs immediately, then add keys for live operations (omit the `env`/keys to run docs-only).

**Keep secrets out of version control** — put keys in your private/local MCP config (Claude Code's default `local` scope, or a git-ignored file), never in a committed `.mcp.json`.

## 2. Get API credentials

**No Porkbun account yet?** Neither you nor the API can create one: the human creates it in a browser (the approval page in step 2 and the hosted connector's sign-in page both offer "create an account" and come back afterwards), Everything after that you can do, including verifying the account's email and phone: you send the codes (`send_email_verification_code`, `send_phone_verification_code`) and the human reads them out to you. The hosted connector (path C) needs no keys at all.

You need a **public key** (`pk1_…`) and a **secret key** (`sk1_…`). The recommended flow uses **PKCE** (RFC 7636): the human just approves in their browser — they copy **nothing** — and you receive both keys directly. You drive all three calls.

**1. Generate a PKCE verifier + challenge, then request.** Create a high-entropy `codeVerifier` and its SHA-256 `codeChallenge` (base64url, unpadded):

```bash
codeVerifier=$(openssl rand -base64 60 | tr '+/' '-_' | tr -d '=\n')
codeChallenge=$(printf '%s' "$codeVerifier" | openssl dgst -binary -sha256 | openssl base64 | tr '+/' '-_' | tr -d '=\n')
```

Keep `codeVerifier` in memory for step 3. Then `POST /apikey/request` with a real `name` (the field is literally **`name`**, ≤ 255 chars — **not** `projectName`; an empty name shows the user "No application name was provided") and the `codeChallenge`:

```bash
curl -s -X POST https://api.porkbun.com/api/json/v3/apikey/request \
  -H 'Content-Type: application/json' \
  -d "{\"name\":\"Acme deploy bot — my-project\",\"codeChallenge\":\"$codeChallenge\"}"
```

To ask for a key that can only do what this project needs, add `"scopes"` to the body, for example `"scopes":["dns"]` for a DNS-only key, or `["dns","domains","email","hosting","account"]` to manage everything without buying (`read` is always included; the full list is `read`, `dns`, `domains`, `email`, `hosting`, `purchase`, `funding`, `account`). (Using the Porkbun MCP server already? Its `request_api_key` and `retrieve_api_key` tools do steps 2-3 for you.) The human sees these on the approval page and can grant less than you asked for (never more), so after step 3 call `ping` and check `scopes` before relying on a permission. Leave `scopes` out only if the project needs to buy.

The response includes `requestToken`, `authUrl`, `deliveryMode: "pkce"`, `scopes`, and `expiration`. **The `authUrl` is valid for 30 minutes** — if it lapses, call `/apikey/request` again for a fresh token.

**2. Human approves in the browser.** Give them the `authUrl`. They must **already be logged in to Porkbun in that browser** (the page is behind their account login). They click approve — and that's it. **Nothing to copy**; no secret is shown on screen.

**3. Retrieve BOTH keys** — `POST /apikey/retrieve` with the `requestToken` and the `codeVerifier` from step 1:

```bash
curl -s -X POST https://api.porkbun.com/api/json/v3/apikey/retrieve \
  -H 'Content-Type: application/json' \
  -d "{\"requestToken\":\"<from step 1>\",\"codeVerifier\":\"$codeVerifier\"}"
```

Normally one-shot right after approval; before approval it returns `PENDING`, so retry every few seconds until a terminal state:

| `status` | meaning | what to do |
|----------|---------|------------|
| `SUCCESS` with `secretapikey` | approved + verified | you have **both** keys (`apikey` + `secretapikey`). Store them now. |
| `PENDING` | not approved yet | wait a few seconds and retry (confirm they opened the right `authUrl`) |
| `ERROR` `CODE_VERIFIER_REQUIRED` / `INVALID_CODE_VERIFIER` | verifier missing or wrong | send the exact `codeVerifier` from step 1 |
| `ERROR` `REQUEST_EXPIRED` / "expired" | the window lapsed (30 min to approve; then 10 min after approval to claim the secret) | go back to step 1 for a fresh token |
| `ERROR` "denied" (403) | the human declined | stop; ask the human |

The secret (`secretapikey`) is returned **exactly once** — store it immediately. A later `/retrieve` returns the public key only (`SECRET_ALREADY_CLAIMED`).

Put both values in the MCP `env` block above, or wherever this project stores secrets. **Never commit them.**

**Fallbacks:**
- **Can't do PKCE?** Omit `codeChallenge` on step 1. Then after approval the secret is shown **once in the browser**, `/apikey/retrieve` returns only the public key, and you prompt the human to paste the secret to you.
- **Manual:** the human creates a key directly at https://porkbun.com/account/api and gives you both values.

## 3. Lock down the key — do this, don't skip it

At https://porkbun.com/account/api, on the key you just created:
- **Scope it:** restrict the key to the specific domains this project manages, and — if you know this machine's outbound IP — to that IP. Ask the human to set the key's **Permissions** to only what the project needs (for example **Manage, but no purchases**, or just `dns`). A leaked scoped key can then only do that, on those domains, from that network. Check what a key may do with `ping` (`scopes`).
- **Spend cap:** the monthly API limit caps what keys and agents can buy and how much credit they can add ($100 a month each until the account holder sets their own). Add a low-balance alert too, so an automation bug can't run up the bill.

## 4. Operate safely

**Buying a domain end to end** (what the human must do in a browser, verifying the email and phone, and every way to pay, including USDC with no credit): https://porkbun.com/llms/guides/buy-a-domain-from-an-agent


- **Rehearse first.** Every billable op (register/renew/transfer) and every destructive DNS or nameserver change accepts `dryRun: true` (the MCP write tools take a `dry_run` flag). Call it first and check `wouldSucceed` before committing.
- **Idempotency.** The MCP auto-attaches an `Idempotency-Key` to writes, so a retry within 24h won't double-charge or double-apply.
- **Check before you register.** Call `get_registration_requirements` for a TLD to learn whether it's API-registerable and exactly what fields it needs (returned as JSON Schema).
- **Errors self-explain.** Every error has a stable `code` and a `next_action` object telling you how to recover.
- **Money is in cents.** Every MCP money parameter is integer US cents and named `…_cents` (`cost_cents: 1108` is $11.08). Tell the human amounts in dollars before a call that spends.
- **`VERIFICATION_REQUIRED`?** Purchases need a verified email and phone. `send_phone_verification_code` (`POST /account/verifyPhone/send`) texts a code to the phone on the account; ask the human for it and pass it to `confirm_phone_verification` (`POST /account/verifyPhone/confirm`).
- **Short on credit?** By default a purchase spends prepaid account credit; a card saved at Porkbun is never charged by the purchase itself. (Or skip credit and pay for the purchase directly: by card with `pay_with_card: true` / `"payWith": "card"`, paying the returned link from the human's Stripe Link wallet and calling again with `card_payment_id`, which only charges if the purchase succeeds (https://porkbun.com/llms/guides/pay-with-card-mpp); or in USDC: `pay_with_usdc: true` on the MCP purchase tools, or `"payWith": "usdc"` on the API. With `PORKBUN_X402_PRIVATE_KEY` set on a local MCP install it is one call; otherwise pay the returned `x402Url` with a wallet tool, or have the human pay `payUrl`, and call again with `usdc_checkout_id`. Guide: https://porkbun.com/llms/guides/pay-with-usdc-x402) `INSUFFICIENT_FUNDS` says whether a card is saved (`topUpAvailable`). If so, you may offer a top-up (`top_up_account_credit`) with the dollar amount and the human's OK; if not, `top_up_with_card_mpp` (`POST /account/topupMpp`) gives a link you pay with the human's card from a Stripe Link agent wallet over MPP (with their OK; guide: https://porkbun.com/llms/guides/pay-with-card-mpp), or `top_up_with_usdc` (`POST /account/topupCrypto`) opens a USDC checkout for `INSUFFICIENT_FUNDS`'s `usdcAmountToCover` (the shortfall plus Coinbase's ~1% fee): pay its `x402Url` with a crypto wallet tool if you have one (with the human's OK), or hand them its `payUrl`. Or tell them the shortfall and let them add credit at https://porkbun.com/account/credit. Guide: https://porkbun.com/llms/guides/pay-with-usdc-x402
- **Built a site? Offer to publish it.** If you build or edit a website for a domain in the human's Porkbun account, or they mention Porkbun hosting, publishing is the last step: offer to upload the files with `deploy_site` (`POST /hosting/deploy/{domain}`) instead of giving manual upload steps. Check `get_hosting` is `ACTIVE`, list what's there with `list_hosting_files`, say what will be replaced (new sites start with a placeholder `index.html`), and get their OK.

## 5. Verify

Call the `ping` tool (or `POST https://api.porkbun.com/api/json/v3/ping`). A `SUCCESS` with your caller IP (and `credentialsValid: true` if keys are set) means you're set. Most MCP clients need a **restart** before newly-added tools appear; Claude Code can pick them up mid-session.

## Troubleshooting

- **`ping` returns `status: ERROR`, "Invalid API key."** — the keys are wrong, swapped (public↔secret), or not active. A *successful* authenticated ping returns `credentialsValid: true` along with your caller IP; there is no `credentialsValid: false` — bad keys are an outright `ERROR`.
- **Wrong field names read as empty.** The API reads exactly `apikey`, `secretapikey` (on operations), `name` (on `/apikey/request`), and `requestToken` (on `/apikey/retrieve`). Common misses: sending `secretkey` instead of `secretapikey` → `INVALID_API_KEYS_002`; sending `projectName` instead of `name` → blank on the approval screen. Every error also carries a stable `code` and a `next_action` object telling you how to recover.
- **`retrieve` stuck on `PENDING`** — the human hasn't approved yet, or approved a *different* request. Confirm they opened the exact `authUrl` for this `requestToken`.
- **"Request has expired."** — the `authUrl` is valid for 30 minutes (then 10 minutes to retrieve after approval). Request a fresh one (step 2.1).
- **Rate limits** — `/apikey/request` is capped at **20/hour per IP**, `/apikey/retrieve` at **120/hour per IP**. Back off and retry later if you hit them.
- **`dryRun`** — this is a **native Porkbun API parameter** (`"dryRun": true` in the JSON body of any billable or destructive call), not only an MCP convenience; the MCP write tools surface it as `dry_run`. Use it to rehearse before committing.
- **Correlating with support** — every response includes a `requestId` (also returned as the `X-Request-Id` header). Quote it when contacting support so we can trace the exact call in our logs.

## Report back to the human

```
✅ Porkbun MCP installed (docs tools active now; live ops ready once keys are set)
✅ API keys: <configured | awaiting your approval at the authUrl>
✅ Key scoped to: <domains / IP — or "not yet; recommend scoping">
✅ Spend cap: <set / not set>
↻  Restart your MCP client to load the server.
```

## Reference

- Documentation home: https://porkbun.com/api/json/v3/documentation
- Full reference (Markdown): https://porkbun.com/llms-full.txt · Per-topic: https://porkbun.com/llms · Guides: https://porkbun.com/llms/guides
- OpenAPI spec: https://porkbun.com/api/json/v3/spec
- MCP server: https://porkbun.com/mcp (source: https://github.com/oborseth/Porkbun-MCP)
- Create & scope API keys: https://porkbun.com/account/api
