# 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 (some "work" modes, claude.ai in a browser) can't install a local server and should use the credential-free docs endpoints (`/llms.txt`, `/llms-full.txt`, `/api/json/v3/spec`).

## 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
```

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 (some "work" modes, claude.ai in a browser) — you can't install a local server

Hand these steps to a human, or just read the docs directly — the doc endpoints 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

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\"}"
```

The response includes `requestToken`, `authUrl`, `deliveryMode: "pkce"`, and `expiration`. **The `authUrl` is valid for only 10 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 10-min window lapsed (before **or** after approval — the secret must be claimed within 10 min of approval) | 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. A leaked scoped key can then only touch those domains, from that network.
- **Spend cap:** set a monthly API spend limit and a low-balance alert, so an automation bug can't run up the bill.

## 4. Operate safely

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

## 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 only 10 minutes. 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://github.com/oborseth/Porkbun-MCP
- Create & scope API keys: https://porkbun.com/account/api
