| pingA | Verify the Porkbun API connection and credentials. Returns the caller's public IP, whether the API key is valid, account (the username of the Porkbun account the key belongs to), and scopes: what the key may do (["full"], or a list such as read, dns, domains, email, hosting, purchase, funding, account). A call outside the key's scopes fails with INSUFFICIENT_SCOPE and changes nothing; tell the user rather than retrying, since only they can widen the key's permissions. Use this as a first sanity check before making other calls, and to confirm you are in the account the user means (a domain in another account comes back as DOMAIN_NOT_FOUND, which also names the account). |
| check_domainA | Check whether a single domain is available for registration and what it costs. Returns availability (avail: yes|no), registration price, renewal price, transfer price, and (for premium domains) extended pricing details. Pricing is in USD. An internationalized name (IDN) also returns idnLanguages, the languages its registry accepts; register_domain needs one of them as idn_language. For a domain already in the user's account the result has inYourAccount: true: the top-level price is still the registration price, and the renewal price is additional.renewal.price (use that, or a renew_domain dry run, before renewing). For a domain registered to someone else it adds aftermarketSearch: the name may be for sale, so call search_aftermarket before telling the user it is unavailable. Use this BEFORE register_domain to confirm cost — Porkbun rejects registrations whose cost_cents doesn't match the current quote. Checking more than one name? Use check_domains instead — it takes up to 25 in a single call and draws on a separate, more generous budget (200 domains/minute against 10 checks/10s here), because Porkbun chunks checks per registry and a batch costs less than the same names one at a time. |
| check_domainsA | Check up to 25 domains for availability and price in ONE call. Prefer this over calling check_domain in a loop whenever you have more than one name: Porkbun chunks availability checks per registry, so a batch is materially less work than the same names individually, and it draws on a separate budget of 200 domains per 60 seconds (counted per domain, not per call) rather than the single check's 10 per 10 seconds. Read all three result lists — they mean different things. domains is keyed by domain name and each value has the same shape as check_domain's response. invalid holds entries that are not checkable at all (not a domain, unsupported TLD); one bad entry does NOT fail the call, so the other names still come back answered. unresolved holds domains the registry did not answer for — these are neither available nor taken, and reporting them as unavailable would be wrong. Retry those. Duplicates are removed before the budget is charged. More than 25 names returns BULK_CHECK_TOO_MANY and checks nothing, rather than silently truncating, so split longer lists yourself. Synchronous, no polling — 25 mixed TLDs answers in roughly three seconds. One exception to watch: a few registries accept only a few domains per command and .de accepts exactly one, so a batch heavy in those TLDs returns BULK_CHECK_TOO_SLOW without checking anything. Group .de names into their own small calls. RATE_LIMIT_EXCEEDED with reason: SUSTAINED_BULK_CHECKING means the account has been checking at scanning volume (hundreds of names an hour, few registrations) and is held to 50 names per 5 minutes for a day. Do not retry in a loop: check only names the user might actually buy, wait retryAfter, or tell the user support can raise the limit for a legitimate high-volume use. |
| get_registration_requirementsA | Get a TLD's registration requirements as JSON Schema. Returns whether the TLD is registerable via the API (apiRegisterable), the register_domain request body as a JSON Schema (with the fixed term, cost, agreeToTerms), WHOIS-privacy / validated-address / registrant-only flags, and — for TLDs with registry eligibility rules (e.g. .us nexus, .ca legal type) — a second schema (registryRequirements) listing those fields with allowed values and labels. Call this BEFORE register_domain to confirm a TLD can be registered and to build a valid payload, instead of discovering requirements from a failed registration. |
| get_pricingA | Get current Porkbun pricing: registration, renewal and transfer prices per TLD in USD. No authentication required. Pass tlds whenever you know which TLDs matter (e.g. ["com", "io"]): without it the response lists every TLD Porkbun sells, around 900 of them. Case, a leading dot and IDN form are handled. A TLD Porkbun does not sell comes back in unsupported instead of being priced, and if none of them are sold the call fails with INVALID_TLD. Standard pricing only: premium names have their own per-domain price, reported by check_domain. |
| list_marketplaceA | Browse domains for sale on Porkbun Marketplace: names other Porkbun customers listed themselves, not new registrations. This is ONE aftermarket source; to find whether a taken name is for sale anywhere (Afternic, Sedo, Atom, NameBright, DAX and Porkbun Marketplace together), use search_aftermarket. Returns each listing's domain, TLD, SLD length, price (in USD), and listing date. Filters (all optional, server-side, mirroring the porkbun.com/marketplace UI): query: SLD substring match. Multi-word queries: prefix a word with - to exclude it (e.g. "ai -test" matches SLDs containing 'ai' but not 'test').
tlds: limit to a list of TLDs (without the leading dot).
sld_length_min, sld_length_max: SLD character length bounds.
sort_name: domain | tld | price | sld_length.
sort_direction: asc | desc.
When any filter is set, server returns up to 1000 matching listings. With no filters, supports raw pagination via start / limit (max 5000). |
| list_domainsA | List domains in the authenticated Porkbun account. Returns one page (up to 1000) with metadata: expire date, auto-renew, security lock, WHOIS privacy, API access opt-in, and notLocal flag. Filters (all optional): domain: exact match. Returns 0 or 1.
name_contains: substring search on domain name.
tlds: limit to these TLDs (no leading dot).
expiring_within_days: only domains expiring within N days. Useful for renewal automation.
auto_renew: 'yes' or 'no'.
api_access: 'yes' or 'no'. Filter to domains an API key can actually operate on — eliminates API_ACCESS_DISABLED errors downstream.
sort_name: 'domain' | 'tld' | 'create_date' | 'expire_date'. Default expire_date.
sort_direction: 'asc' | 'desc'. Default asc.
For a single domain by name, use get_domain instead — cleaner shape and 404-on-not-found semantics. |
| get_domainA | Get the metadata for a single domain in the authenticated account: status, TLD, create date, expire date, security lock, WHOIS privacy, auto-renew, API access opt-in, and (optionally) labels. Returns an error with code DOMAIN_NOT_FOUND if the domain isn't in the account; the error's account field names the account the key belongs to, so a domain in a different account is easy to tell from a missing one. |
| get_balanceA | Get the available account credit balance for the authenticated Porkbun account. Returns the balance in cents (integer) and a human-readable display string (e.g. $12.34). Call it before offering to buy anything: a purchase is paid from this prepaid balance, not charged to a card, so this number decides whether a registration, renewal or transfer goes through as-is. If it is short and the account has a saved card, top_up_account_credit adds credit in one call (ask the user first, with the amount); get_auto_topup reports whether a card is on file. Without a card, or if you do not charge cards on a user's behalf, the user adds credit with buy account credit at https://porkbun.com/account/credit. |
| get_auto_topupA | Read the account's auto top-up configuration: whether it is on, the balance threshold that triggers it, the amount added, whether a payment method is actually on file, and effectiveAmount — what top_up_account_credit would charge right now. If paymentMethodOnFile is false the settings are inert: nothing can be charged and auto top-up will never fire, and a card can only be saved on porkbun.com, never through the API. |
| top_up_with_usdcA | Add account credit by paying USDC on Base, through a Coinbase checkout. Use it to add credit when a purchase failed with INSUFFICIENT_FUNDS and there is no saved card (or the user would rather pay in crypto); to pay for just that one purchase, retry it with pay_with_usdc: true instead. Nothing is charged by this call; it returns two ways to pay: x402Url: if you have a crypto wallet tool that can pay x402 URLs (for example Coinbase's Agentic Wallet or Wallet MCP, "make an x402 request"), pay it with that, with the user's OK and the dollar amount. It is a gasless USDC transfer on Base.
payUrl: a Coinbase page for the user to pay themselves.
Credit lands when the payment confirms (usually within a minute), less Coinbase's fee of about 1% (estimatedCredit_cents in the response); check with get_usdc_topup_status and then retry the purchase. amount_cents is integer US cents, $1 to $500. To cover an INSUFFICIENT_FUNDS shortfall exactly (a $2.50 domain, say), use that error's usdcAmountToCover, which allows for the fee. Payments are on-chain and cannot be reversed. Not available on sandbox keys (use sandbox_topup). Fails with CRYPTO_NOT_AVAILABLE for accounts that cannot use crypto (bank-transfer-only accounts); they add credit on porkbun.com instead. Credit added over the API counts toward the account's monthly top-up limit ($100 a month unless the account holder set their own; TOPUP_LIMIT_EXCEEDED), and unpaid checkouts from the last day count as if paid. Guide: https://porkbun.com/llms/guides/pay-with-usdc-x402
|
| top_up_with_card_mppA | Get a payment link that adds account credit when paid with the user's CARD over MPP (Machine Payments Protocol), using a Stripe Shared Payment Token from their agent wallet (Stripe's Link Agent Wallet). Use it to add credit when a purchase failed with INSUFFICIENT_FUNDS and no card is saved at Porkbun; to pay for just that one purchase, retry it with pay_with_card: true instead, which only charges if the purchase succeeds. Nothing is charged by this call. Pay the returned payUrl with an MPP-capable wallet tool, for example Stripe's Link CLI or its MCP server (link-cli mpp pay <payUrl> -X POST --context "..."). Newer Link CLI versions stop after creating the spend request: once the user approves in Link, run link-cli mpp pay <payUrl> --spend-request-id <lsrq_id> -X POST to complete it. Link rejects a --context shorter than 100 characters, and the payment fails without it: write one or two full sentences the user will read when approving, saying what it is for and the dollar amount (e.g. "Adding $20.00 of prepaid credit to my Porkbun account so my AI assistant can register the domains I asked for."). Word it freshly for each payment; Link can refuse a repeat of an identical request. The link answers 402 with the terms, the wallet asks the user to approve the amount, and the credit is added immediately (the response shows the new balance). Tell the user the dollar amount first. The link is single-use, valid 15 minutes. amount_cents is integer US cents, $5 to $500; same limits as card top-ups. Not available on sandbox keys (use sandbox_topup). If you cannot pay MPP links, top_up_with_usdc or the user adding credit at porkbun.com are the alternatives. |
| send_phone_verification_codeA | Text a verification code to the phone number on the user's Porkbun account, to clear VERIFICATION_REQUIRED (purchases and top-ups need a verified phone and email, and new accounts are not asked to verify the phone at signup). Then ask the user for the code they received and call confirm_phone_verification. The number on the account is used; it cannot be chosen or changed here. If the text does not arrive, call again with channel: "call" to have the code read out by phone. A second request within 10 minutes returns codeAlreadySent rather than texting again. Some countries can only verify on the website (PHONE_VERIFY_UNAVAILABLE); up to 5 sends a day. Guide: https://porkbun.com/llms/guides/verify-the-account |
| confirm_phone_verificationA | Verify the account's phone number with the code the user received from send_phone_verification_code. On success, retry what failed with VERIFICATION_REQUIRED; emailVerified: false in the response means the email still needs verifying: send_email_verification_code, then confirm_email_verification with the code the user reads out (or they click the link in that email). PHONE_CODE_INVALID means a wrong or expired code: check it with the user, or send a new one. |
| send_email_verification_codeA | Email a verification code to the email address on the user's Porkbun account, to clear VERIFICATION_REQUIRED (purchases and top-ups need a verified email and phone). It is the usual verification email: a link plus a code. Then ask the user for the code in that email (or read it, if you have access to that inbox) and call confirm_email_verification; the user clicking the link works too. The address on the account is used (or a new address waiting for verification); it cannot be chosen or changed here, and the code is never returned to you: it only proves the address because it arrives there. A second request within 10 minutes returns codeAlreadySent; the earlier code stays valid for 3 days. Guide: https://porkbun.com/llms/guides/verify-the-account |
| confirm_email_verificationA | Verify the account's email address with the code from the verification email (send_email_verification_code, or any verification email from the last 3 days). On success, retry what failed with VERIFICATION_REQUIRED; phoneVerified: false means the phone still needs send_phone_verification_code. EMAIL_CODE_INVALID means a wrong or expired code, or one from an older email: check it against the newest email, or send a new one. |
| get_usdc_topup_statusA | Check a USDC top-up from top_up_with_usdc: state is ACTIVE (not paid yet), PROCESSING (paid, being credited), COMPLETED (credited: credited true), EXPIRED or FAILED. Also returns the current balance, so once COMPLETED you can retry the purchase that needed it. |
| list_invoicesA | List the account's invoices (one per order), newest first, with each one's date, state (PAID, PARTIALLY_REFUNDED, REFUNDED or UNPAID), net total in cents and the domains on it. total_cents is what the customer was left paying: refunds are netted out. Each invoice has url, its page on porkbun.com, which is the easiest thing to hand a user who wants to see or print one. Use get_invoice for the lines and get_invoice_pdf for the file. |
| get_invoiceA | Get one invoice as data: who it is billed to, how it was paid (card brand and last four only), each line with its product, term, resulting domain expiry and status, and the gross, refunded and net totals in cents. The same figures as the PDF. url and pdfUrl open it on porkbun.com (the user must be signed in); downloadUrl downloads the PDF without signing in for 15 minutes, which is the link to hand a user who wants the file. Invoice IDs come from list_invoices; an ID not on this account returns INVOICE_NOT_FOUND. |
| get_invoice_pdfA | Get an invoice PDF: the same document as Download PDF on porkbun.com. The file comes back as an embedded PDF resource (for clients that can show or save files), alongside filename, sizeBytes and downloadUrl, a link that downloads it without signing in for 15 minutes (downloadExpires). To give the user the file, hand them downloadUrl; do not try to reproduce the file's bytes yourself. get_invoice also returns a downloadUrl if you only need the link. |
| configure_auto_topupA | Set the top-up amount, and/or turn auto top-up on or off. amount_cents (integer US cents) is what a top-up adds, and it stands on its own: top_up_account_credit charges this same figure on demand, so setting it without enabling automation is a normal thing to do. Capped at $500 (50000) with a $5 floor when set through the API; a larger figure has to be set by the account holder at https://porkbun.com/account/api and is honoured as-is.
enabled: true (with threshold_cents) additionally makes it fire by itself: an order that drops the balance below threshold_cents charges the saved payment method for the amount and adds it — the way to stop an unattended workflow dead-ending on INSUFFICIENT_FUNDS. enabled: false stops it firing on a threshold but KEEPS the amount on file.
Confirm the numbers with the user before calling: this authorises charges to their card. No card is added or changed here, and none can be added over the API. Supports dry_run. |
| top_up_account_creditA | Charges the user's saved payment method (amount_cents is integer cents: 803 charges $8.03; tell the user the dollar figure first) and adds the money to their Porkbun account credit immediately. Use it when a purchase failed with INSUFFICIENT_FUNDS and the user wants to continue now — enabling auto top-up does not help in that moment, because it only fires on the next order. Ask the user before calling, with the figure. This spends real money off a card, not credit they already bought. Omit amount_cents and it charges what the account has configured (or $50 if it never has) — that is the right default, and amount_source in the response says which was used. Pass amount_cents only when the user wants a specific one-off figure, e.g. enough to cover a particular purchase; it does NOT change their saved setting, so prefer it over calling configure_auto_topup for a single charge. Fails with NO_PAYMENT_METHOD when nothing is saved to charge (the user has to save a card or buy credit on porkbun.com; the API cannot add one), CARD_DECLINED when the card refuses, and TOPUP_LIMIT_EXCEEDED when the month's dollars or the frequency run out — the account's monthly spend limit caps top-up dollars as well as domain spend, an account with no limit set gets $100/month, card, auto, MPP and USDC top-ups all count toward it, and there are 5/day and 20/month count caps. get_auto_topup reports monthlyCeiling, ceilingSource and toppedUpThisMonth, so check there before promising a user a top-up will go through. Every successful charge emails the account holder. A sandbox key grants simulated credit and charges nothing. Supports dry_run, which previews and charges nothing. |
| get_api_settingsA | Get the account's API spend controls and the limits actually enforced, in cents. spendLimit caps everything bought over the API in a calendar month (registrations, renewals, transfers, closeouts, paid hosting): limit, source (account = set by the account holder, default = none set so $100 applies, none = no cap), spent and remaining. topupLimit caps credit added over the API (card, auto top-up, MPP, USDC together): limit, source, added. settings is what the account holder configured. Check remaining before an expensive operation: a purchase over it fails with MONTHLY_SPEND_LIMIT_EXCEEDED, and only the account holder can raise the limit, on the website. |
| get_nameserversA | Get the current nameservers configured for a domain in the authenticated account. Returns an array of nameserver hostnames, read live from the registry. Read-only complement to update_nameservers. Treat the result as an unordered set — registries return nameservers in whatever order they like, so this will often not match the order passed to update_nameservers. Order is meaningless in DNS; never report a reordering as a change. |
| list_dns_recordsA | List all DNS records for a domain in the authenticated account. Returns each record's id, type (A, AAAA, CNAME, MX, TXT, etc.), name (subdomain or empty for apex), content, ttl, and priority (where applicable). The id field is required when editing or deleting a specific record. |
| search_closeoutsA | Search expired-domain closeouts: names that did not sell at auction and are now offered at a fixed price that descends on a schedule. No bidding — the first buyer at the current price takes the name. Filter by keyword, TLD, exact name length, age range and price range; sort by domain, end time, price, revenue, visitors, inbound links or registration_date. Every row carries age and registrationDate, and both are sortable — sort_name=registrationDate with sort_direction=asc gives the OLDEST registrations first, which is the single most requested thing here and is not possible on the website. Note sort_direction=asc on revenue, visitors or inboundLinks lists the domains with no recorded figure first (they come back null); use desc on those. price is the closeout price ALONE. Do not quote a user a total from these results: the binding amount adds the renewal or transfer year and comes from get_closeout. Page with start/limit until start >= totalAvailable. |
| search_aftermarketA | Find registered domains that are FOR SALE on the aftermarket: Afternic, Sedo, Atom, NameBright and DAX listings and auctions, plus Porkbun Marketplace, merged to the lowest price per name. Use it when check_domain says a name is taken (aftermarketSearch in its result), or to browse names for sale by keyword. query is a keyword (coffee) or a full domain (coffee.com); for a full domain, exactMatch holds its listing if it has one, and related listings follow. Read saleType before quoting a price: buy_now means price is the price; make_offer means price is only the seller's minimum offer; auction means price is the current bid. Prices are integer cents and are the current listed price; the binding price is set at checkout. You cannot buy these through the API. Tell the user the price and what it means, and give them the listing's buyUrl to buy (or bid) on porkbun.com; the name arrives by transfer from the seller, usually within a few days. A cold search can take a few seconds; 20 searches a minute per account. |
| get_closeoutA | Get one closeout plus totalPrice — the binding amount, which is the closeout price plus the registration year that comes with it. Always call this before buy_closeout: totalPrice is what you must pass as cost_cents, and it cannot be derived from search results because a name already at Porkbun is renewed while anything else is transferred in, and those price differently. available: false means somebody already claimed it. Quote the user totalPrice, never the search price. |
| buy_closeoutA | Spends account credit, or pays directly by card (pay_with_card) or in USDC (pay_with_usdc). Buys a closeout at its current price and claims the name. Confirm the total with the user first. cost_cents must equal totalPrice from get_closeout exactly — any other value is refused, so you cannot accidentally charge a price the user did not agree to. Use dry_run with cost 0 to quote without charging. The domain is reserved, not delivered. The provider releases it over the following days, so do not tell the user it is in their account: poll list_domains or watch the domain.registered webhook. Every post-charge failure refunds automatically and reports refunded:true. Losing the race to another buyer (CLOSEOUT_UNAVAILABLE) is not worth retrying on the same name — closeouts are first-come at a fixed price. CLOSEOUT_NOT_ELIGIBLE means support has blocked this account from auctions and closeouts over past-due invoices or an auction terms violation — do not retry, tell the user to contact support. There is no account-age or order-history requirement: eligibility is the same as registering a domain, plus verified email and phone. How a purchase is paid. From prepaid account credit by default; a card saved at Porkbun is never charged by a purchase itself. Or directly, for this one purchase: pay_with_card: true (the user's card from their Stripe Link agent wallet, held and charged only if the purchase succeeds) or pay_with_usdc: true (USDC on Base over x402). If the credit is short, the call fails with INSUFFICIENT_FUNDS carrying cost, balance, shortfall, topUpAvailable and usdcAmountToCover (dry_run: true reports the same without charging). Ways forward, simplest first, always telling the user the dollar amount and getting their OK: (1) if they use Stripe Link, retry this exact call with pay_with_card: true; (2) if you can pay x402 (a wallet tool, or this server has PORKBUN_X402_PRIVATE_KEY), retry with pay_with_usdc: true; (3) when topUpAvailable is true a card is saved: top_up_account_credit (amount_cents at least topUpAmountToCover, or omit it for their configured amount), then retry; (4) add credit with top_up_with_card_mpp (Link) or top_up_with_usdc (for usdcAmountToCover, which allows for Coinbase's ~1% fee), then retry; (5) the user adds credit at https://porkbun.com/account/credit. If a purchase fails with VERIFICATION_REQUIRED, the phone (send_phone_verification_code then confirm_phone_verification) and the email (send_email_verification_code then confirm_email_verification) can both be verified without leaving the conversation, the user reading out each code. Suggest configure_auto_topup if they want credit refilled automatically. Money parameters are integer cents: state amounts to the user in dollars (cost_cents: 1108 is $11.08). If you do not move money on a user's behalf, hand it back instead: tell them the shortfall and that they can add it with buy account credit at https://porkbun.com/account/credit, then retry once they have. Buying for a new or unfunded account: the whole path, including what only the human can do, is at https://porkbun.com/llms/guides/buy-a-domain-from-an-agent. Paying directly in USDC (no account credit needed): set pay_with_usdc: true. If this server has a wallet configured (PORKBUN_X402_PRIVATE_KEY, local installs only), the payment is signed and the purchase completes in this one call. Otherwise the result is status: PAYMENT_REQUIRED with a checkoutId, an x402Url and a payUrl, and nothing is charged: pay the x402Url with a wallet tool that pays x402 URLs (for example Coinbase's awal x402 pay <x402Url> --scheme auth-capture), or have the user pay the payUrl page, then call this tool again with the same arguments plus usdc_checkout_id. Tell the user the dollar amount first. If the purchase fails after payment, the payment is NOT returned to the wallet: it stays on the Porkbun account as credit (the error says keptAsCredit: true with the new balance), so retry without pay_with_usdc to buy from that credit. Paying directly by card, with a hold (no account credit needed): set pay_with_card: true when the user pays with Stripe Link. The result is status: PAYMENT_REQUIRED with a payUrl and a cardPaymentId; nothing is charged. Pay the payUrl with an MPP wallet tool. With Link's CLI that is two commands: link-cli mpp pay <payUrl> -X POST --context "..." (context at least 100 characters) creates a spend request the user approves in Link; once approved, complete it with link-cli mpp pay <payUrl> --spend-request-id <lsrq_id> -X POST. That only authorizes the card. If link-cli answers NOT_AUTHENTICATED, run link-cli auth login first (a one-time device login the user approves). Then call this tool again with the same arguments plus card_payment_id: if the purchase succeeds the card is charged, and if it fails the hold is released and nothing is charged. The link works until expiresAt; if that passes before it is paid, call this tool again without card_payment_id for a fresh link instead of paying the old one. |
| get_transfer_setupA | Report where a held inbound transfer is and what it is waiting on: whether it is held at PENDINGDNS, whether its DNS zone exists, how many records it holds, what the domain currently delegates to, and the next step. Use this to resume a no-downtime transfer instead of tracking that state yourself. |
| prepare_transferA | Create the Porkbun DNS zone for a domain whose inbound transfer is held, so records can be added before the domain moves. Step 2 of the no-downtime sequence (transfer_domain with hold_for_dns_setup, prepare_transfer, import_dns_records, start_transfer). Returns the Porkbun nameservers. The zone is created deliberately, not as a side effect of the first record write, which is why this call exists. |
| start_transferA | Release a held inbound transfer to the registry. Final step of the no-downtime sequence, and the only thing that releases a hold — nothing does it on a timer, so a held transfer waits indefinitely until you call this. Refuses with TRANSFER_ZONE_EMPTY if the zone has no records, which is the outage the hold exists to prevent; only pass force when the domain genuinely needs no DNS at Porkbun. |
| cancel_transferA | Cancels a paid inbound transfer and refunds the order. Confirm with the user first. The order is deliberate: mark cancelled locally, withdraw at the registry, verify the registry accepted the withdrawal, then refund. If the registry state cannot be confirmed it restores the transfer and returns TRANSFER_STATE_UNCONFIRMED rather than refunding something that may still be live — escalate to support in that case rather than retrying. The response reports withdrawnAtRegistry, registryResultCode, refunded and refundAmount. |
| update_transfer_auth_codeA | Replace the authorization code on an inbound transfer that stalled because the code was wrong, and re-queue it — instead of cancelling, refunding and resubmitting. The new code is validated against the registry before being stored, so a bad one is rejected here rather than failing again days later. Only repairable transfers qualify; others return TRANSFER_NOT_REPAIRABLE. Note that a losing registrar's refusal also surfaces as a bad-auth-code status, so check for a denial notice before assuming the code is the problem. |
| scan_dns_recordsA | Discover the DNS records a domain currently publishes by querying its live authoritative nameservers. Writes nothing. Use this before moving a domain to Porkbun: a registrar transfer carries only the delegation, never the zone contents, so the old registrar's records become permanently unreadable the moment it stops answering for the domain — and a domain whose records were never recreated goes dark right then (website down, mail bouncing). Scan first, review, then call import_dns_records, and only then change nameservers. The scan probes a wide list of well-known names (apex, common subdomains, MX, DKIM selectors, provider verification hosts) but cannot enumerate a zone — DNS has no listing operation — so treat it as thorough, not exhaustive, and tell the user that. If the losing registrar has its own API, reading the zone from there is authoritative: do that with the user's own credentials (they must never be sent to Porkbun) and pass the records to import_dns_records instead. Limited to 20 calls per hour per account, since each call is roughly 90 DNS lookups. |
| import_dns_recordsA | Create many DNS records on a Porkbun domain in one call — the companion to scan_dns_records for keeping a transferred domain working. Pass records to import an exact list (preferred when you read the zone from the old registrar's API), or omit it entirely to import whatever a live scan of the domain discovers. Idempotent: a record that already exists is reported in skipped, not failed, so this is safe to re-run and safe as a converge step. Individual failures are listed in failures while everything else still imports. NS and SOA entries are ignored on purpose — they describe the delegation, not the zone. Important: imported records have no effect until the domain actually points at Porkbun's nameservers, so check get_nameservers afterwards and use update_nameservers when the user is ready to cut over — when the domain is delegated elsewhere the response also says so in warnings. Maximum 500 records per call. |
| list_dnssec_recordsA | List the DNSSEC DS records currently submitted to the registry for a domain. Returns key tag, algorithm, digest type, and digest. Use this to verify DNSSEC chain-of-trust setup. Empty array = DNSSEC not configured. |
| list_url_forwardsA | List all URL forwarding rules configured for a domain. Each entry includes its id (used by delete_url_forward), the source subdomain, the destination URL, the redirect type (permanent/temporary/masked), the exact redirectType code (301/302/307/masked — distinguishes 302 from 307), and whether the request path and wildcards are forwarded. |
| list_glue_recordsA | List glue records for a domain. Glue records associate a host (e.g. ns1.example.com) with one or more IP addresses at the registry, used when running your own nameservers on the same domain they serve. Returns the host, IPv4 addresses, and IPv6 addresses for each glue record. |
| list_transfersA | List all in-progress and recent inbound domain transfers for the authenticated account. Returns each transfer's domain, status (NEW, PENDINGAUTH, PENDINGSUBMIT, PENDINGTRANSFER, DONE, CANCELED, etc.), and create date. Use this to monitor transfers initiated by transfer_domain. |
| get_transfer_statusA | Get the status of a specific inbound transfer for a domain. Useful for polling after transfer_domain to know when the transfer completes (typical window: 5-7 days). Returns the same status values as list_transfers, plus a human-readable description. |
| get_ssl_bundleA | Retrieve the free Porkbun-issued SSL certificate bundle for a domain. Returns the certificate chain, private key, and public key (PEM-encoded strings). Porkbun automatically provisions Let's Encrypt certificates for all registered domains using Porkbun nameservers. Use this to install TLS on a server you control. |
| get_contactsA | Get the four contacts (registrant, admin, tech, billing) for a domain in the authenticated account, with their current field values (name, organization, address, phone, email). Read-only. |
| register_domainA | Spends account credit, or pays directly by card (pay_with_card) or in USDC (pay_with_usdc). Registers a new domain on the authenticated Porkbun account. The cost_cents parameter must exactly match the current registration price returned by check_domain (in cents) — Porkbun rejects mismatched quotes. Workflow: call check_domain first to get availability + price, confirm the spend with the user, then call this. The order is idempotency-safe: retries within 24 hours via the same Idempotency-Key return the original response without re-charging. Premium domains, .uk, and a handful of registry-specific TLDs cannot be registered via API and must be done on the website. The account's email and phone number must be verified. A single API registration cannot exceed $100 (ORDER_TOO_LARGE); above that the user has to register on the website. Every registration includes a free trial email mailbox: after registering, setup_mailbox gives it an address at no cost (don't create_mailbox for the first one). Internationalized names (IDNs, e.g. café.com or xn--caf-dma.com) need idn_language: check_domain lists the accepted ones as idnLanguages; without it the call returns IDN_LANGUAGE_REQUIRED with that list and nothing is charged. How a purchase is paid. From prepaid account credit by default; a card saved at Porkbun is never charged by a purchase itself. Or directly, for this one purchase: pay_with_card: true (the user's card from their Stripe Link agent wallet, held and charged only if the purchase succeeds) or pay_with_usdc: true (USDC on Base over x402). If the credit is short, the call fails with INSUFFICIENT_FUNDS carrying cost, balance, shortfall, topUpAvailable and usdcAmountToCover (dry_run: true reports the same without charging). Ways forward, simplest first, always telling the user the dollar amount and getting their OK: (1) if they use Stripe Link, retry this exact call with pay_with_card: true; (2) if you can pay x402 (a wallet tool, or this server has PORKBUN_X402_PRIVATE_KEY), retry with pay_with_usdc: true; (3) when topUpAvailable is true a card is saved: top_up_account_credit (amount_cents at least topUpAmountToCover, or omit it for their configured amount), then retry; (4) add credit with top_up_with_card_mpp (Link) or top_up_with_usdc (for usdcAmountToCover, which allows for Coinbase's ~1% fee), then retry; (5) the user adds credit at https://porkbun.com/account/credit. If a purchase fails with VERIFICATION_REQUIRED, the phone (send_phone_verification_code then confirm_phone_verification) and the email (send_email_verification_code then confirm_email_verification) can both be verified without leaving the conversation, the user reading out each code. Suggest configure_auto_topup if they want credit refilled automatically. Money parameters are integer cents: state amounts to the user in dollars (cost_cents: 1108 is $11.08). If you do not move money on a user's behalf, hand it back instead: tell them the shortfall and that they can add it with buy account credit at https://porkbun.com/account/credit, then retry once they have. Buying for a new or unfunded account: the whole path, including what only the human can do, is at https://porkbun.com/llms/guides/buy-a-domain-from-an-agent. Paying directly in USDC (no account credit needed): set pay_with_usdc: true. If this server has a wallet configured (PORKBUN_X402_PRIVATE_KEY, local installs only), the payment is signed and the purchase completes in this one call. Otherwise the result is status: PAYMENT_REQUIRED with a checkoutId, an x402Url and a payUrl, and nothing is charged: pay the x402Url with a wallet tool that pays x402 URLs (for example Coinbase's awal x402 pay <x402Url> --scheme auth-capture), or have the user pay the payUrl page, then call this tool again with the same arguments plus usdc_checkout_id. Tell the user the dollar amount first. If the purchase fails after payment, the payment is NOT returned to the wallet: it stays on the Porkbun account as credit (the error says keptAsCredit: true with the new balance), so retry without pay_with_usdc to buy from that credit. Paying directly by card, with a hold (no account credit needed): set pay_with_card: true when the user pays with Stripe Link. The result is status: PAYMENT_REQUIRED with a payUrl and a cardPaymentId; nothing is charged. Pay the payUrl with an MPP wallet tool. With Link's CLI that is two commands: link-cli mpp pay <payUrl> -X POST --context "..." (context at least 100 characters) creates a spend request the user approves in Link; once approved, complete it with link-cli mpp pay <payUrl> --spend-request-id <lsrq_id> -X POST. That only authorizes the card. If link-cli answers NOT_AUTHENTICATED, run link-cli auth login first (a one-time device login the user approves). Then call this tool again with the same arguments plus card_payment_id: if the purchase succeeds the card is charged, and if it fails the hold is released and nothing is charged. The link works until expiresAt; if that passes before it is paid, call this tool again without card_payment_id for a fresh link instead of paying the old one. |
| renew_domainA | Spends account credit, or pays directly by card (pay_with_card) or in USDC (pay_with_usdc). Renews an existing domain in the authenticated account. The cost_cents parameter must exactly match the current renewal price returned by check_domain (in cents). The domain must be opted in to API access (per-domain or global toggle in account settings), and only the user can turn that on: check apiAccess in get_domain or list_domains before starting, and if it is off, tell the user rather than finding out from API_ACCESS_DISABLED. Domains registered within the last 30 days, or already renewed within the last 30 days, cannot be renewed yet — the API returns RENEWAL_TOO_SOON with renewableAt, the date it becomes renewable; nothing is charged, so tell the user that date rather than retrying. Premium domain renewals are not supported via API. Idempotency-safe: retries within 24 hours don't double-charge. How a purchase is paid. From prepaid account credit by default; a card saved at Porkbun is never charged by a purchase itself. Or directly, for this one purchase: pay_with_card: true (the user's card from their Stripe Link agent wallet, held and charged only if the purchase succeeds) or pay_with_usdc: true (USDC on Base over x402). If the credit is short, the call fails with INSUFFICIENT_FUNDS carrying cost, balance, shortfall, topUpAvailable and usdcAmountToCover (dry_run: true reports the same without charging). Ways forward, simplest first, always telling the user the dollar amount and getting their OK: (1) if they use Stripe Link, retry this exact call with pay_with_card: true; (2) if you can pay x402 (a wallet tool, or this server has PORKBUN_X402_PRIVATE_KEY), retry with pay_with_usdc: true; (3) when topUpAvailable is true a card is saved: top_up_account_credit (amount_cents at least topUpAmountToCover, or omit it for their configured amount), then retry; (4) add credit with top_up_with_card_mpp (Link) or top_up_with_usdc (for usdcAmountToCover, which allows for Coinbase's ~1% fee), then retry; (5) the user adds credit at https://porkbun.com/account/credit. If a purchase fails with VERIFICATION_REQUIRED, the phone (send_phone_verification_code then confirm_phone_verification) and the email (send_email_verification_code then confirm_email_verification) can both be verified without leaving the conversation, the user reading out each code. Suggest configure_auto_topup if they want credit refilled automatically. Money parameters are integer cents: state amounts to the user in dollars (cost_cents: 1108 is $11.08). If you do not move money on a user's behalf, hand it back instead: tell them the shortfall and that they can add it with buy account credit at https://porkbun.com/account/credit, then retry once they have. Buying for a new or unfunded account: the whole path, including what only the human can do, is at https://porkbun.com/llms/guides/buy-a-domain-from-an-agent. Paying directly in USDC (no account credit needed): set pay_with_usdc: true. If this server has a wallet configured (PORKBUN_X402_PRIVATE_KEY, local installs only), the payment is signed and the purchase completes in this one call. Otherwise the result is status: PAYMENT_REQUIRED with a checkoutId, an x402Url and a payUrl, and nothing is charged: pay the x402Url with a wallet tool that pays x402 URLs (for example Coinbase's awal x402 pay <x402Url> --scheme auth-capture), or have the user pay the payUrl page, then call this tool again with the same arguments plus usdc_checkout_id. Tell the user the dollar amount first. If the purchase fails after payment, the payment is NOT returned to the wallet: it stays on the Porkbun account as credit (the error says keptAsCredit: true with the new balance), so retry without pay_with_usdc to buy from that credit. Paying directly by card, with a hold (no account credit needed): set pay_with_card: true when the user pays with Stripe Link. The result is status: PAYMENT_REQUIRED with a payUrl and a cardPaymentId; nothing is charged. Pay the payUrl with an MPP wallet tool. With Link's CLI that is two commands: link-cli mpp pay <payUrl> -X POST --context "..." (context at least 100 characters) creates a spend request the user approves in Link; once approved, complete it with link-cli mpp pay <payUrl> --spend-request-id <lsrq_id> -X POST. That only authorizes the card. If link-cli answers NOT_AUTHENTICATED, run link-cli auth login first (a one-time device login the user approves). Then call this tool again with the same arguments plus card_payment_id: if the purchase succeeds the card is charged, and if it fails the hold is released and nothing is charged. The link works until expiresAt; if that passes before it is paid, call this tool again without card_payment_id for a fresh link instead of paying the old one. |
| transfer_domainA | Spends account credit, or pays directly by card (pay_with_card) or in USDC (pay_with_usdc). Initiates a transfer of an external domain into Porkbun. Requires the auth/EPP code from the losing registrar, and cost_cents must match the current transfer price from check_domain. Poll with get_transfer_status. Most transfers finish well inside the five-day worst case — two thirds within 24 hours — so do not promise the user a week. .uk and a few TLDs do not support inbound API transfers. Idempotency-safe. Set hold_for_dns_setup unless the user has no DNS to preserve. A transfer carries only the delegation, so a domain that moves before its records exist at Porkbun goes dark. Holding charges the transfer but parks it until you release it: hold → prepare_transfer → import_dns_records → start_transfer. Nothing releases a held transfer on a timer. How a purchase is paid. From prepaid account credit by default; a card saved at Porkbun is never charged by a purchase itself. Or directly, for this one purchase: pay_with_card: true (the user's card from their Stripe Link agent wallet, held and charged only if the purchase succeeds) or pay_with_usdc: true (USDC on Base over x402). If the credit is short, the call fails with INSUFFICIENT_FUNDS carrying cost, balance, shortfall, topUpAvailable and usdcAmountToCover (dry_run: true reports the same without charging). Ways forward, simplest first, always telling the user the dollar amount and getting their OK: (1) if they use Stripe Link, retry this exact call with pay_with_card: true; (2) if you can pay x402 (a wallet tool, or this server has PORKBUN_X402_PRIVATE_KEY), retry with pay_with_usdc: true; (3) when topUpAvailable is true a card is saved: top_up_account_credit (amount_cents at least topUpAmountToCover, or omit it for their configured amount), then retry; (4) add credit with top_up_with_card_mpp (Link) or top_up_with_usdc (for usdcAmountToCover, which allows for Coinbase's ~1% fee), then retry; (5) the user adds credit at https://porkbun.com/account/credit. If a purchase fails with VERIFICATION_REQUIRED, the phone (send_phone_verification_code then confirm_phone_verification) and the email (send_email_verification_code then confirm_email_verification) can both be verified without leaving the conversation, the user reading out each code. Suggest configure_auto_topup if they want credit refilled automatically. Money parameters are integer cents: state amounts to the user in dollars (cost_cents: 1108 is $11.08). If you do not move money on a user's behalf, hand it back instead: tell them the shortfall and that they can add it with buy account credit at https://porkbun.com/account/credit, then retry once they have. Buying for a new or unfunded account: the whole path, including what only the human can do, is at https://porkbun.com/llms/guides/buy-a-domain-from-an-agent. Paying directly in USDC (no account credit needed): set pay_with_usdc: true. If this server has a wallet configured (PORKBUN_X402_PRIVATE_KEY, local installs only), the payment is signed and the purchase completes in this one call. Otherwise the result is status: PAYMENT_REQUIRED with a checkoutId, an x402Url and a payUrl, and nothing is charged: pay the x402Url with a wallet tool that pays x402 URLs (for example Coinbase's awal x402 pay <x402Url> --scheme auth-capture), or have the user pay the payUrl page, then call this tool again with the same arguments plus usdc_checkout_id. Tell the user the dollar amount first. If the purchase fails after payment, the payment is NOT returned to the wallet: it stays on the Porkbun account as credit (the error says keptAsCredit: true with the new balance), so retry without pay_with_usdc to buy from that credit. Paying directly by card, with a hold (no account credit needed): set pay_with_card: true when the user pays with Stripe Link. The result is status: PAYMENT_REQUIRED with a payUrl and a cardPaymentId; nothing is charged. Pay the payUrl with an MPP wallet tool. With Link's CLI that is two commands: link-cli mpp pay <payUrl> -X POST --context "..." (context at least 100 characters) creates a spend request the user approves in Link; once approved, complete it with link-cli mpp pay <payUrl> --spend-request-id <lsrq_id> -X POST. That only authorizes the card. If link-cli answers NOT_AUTHENTICATED, run link-cli auth login first (a one-time device login the user approves). Then call this tool again with the same arguments plus card_payment_id: if the purchase succeeds the card is charged, and if it fails the hold is released and nothing is charged. The link works until expiresAt; if that passes before it is paid, call this tool again without card_payment_id for a fresh link instead of paying the old one. |
| update_auto_renewA | Turn auto-renewal on or off for a domain in the authenticated account. When auto-renew is on, Porkbun automatically charges your account credit at expiration. When off, you must manually renew or the domain expires. Idempotent. |
| update_nameserversA | Replace the nameservers for a domain in the authenticated account. This is a full replacement, not an append — the supplied list becomes the complete set of nameservers. Most TLDs require 2-13 entries. Setting custom nameservers disables Porkbun's free DNS hosting for the domain. Idempotent: applying the same NS list twice is a no-op. The list is applied as a set — the registry may return it in a different order, so do not expect get_nameservers to echo your ordering back, and do not treat a reordering as drift. |
| update_contactsA | Edit a domain's contacts. Provide contacts keyed by role with ANY subset of registrant/admin/tech/billing (unspecified roles keep their current values), or a single contact applied to all four. Mirrors the website: pushes to the registry on thick TLDs, and a registrant change (name/organization/email) fires the same new-owner notice/verification email — no 60-day transfer lock. Supports dry_run. Note: a registrant name/organization change on a .au domain, or any registrant change on an address-validation TLD (.de/.nrw), is rejected with REGISTRANT_CHANGE_NOT_SUPPORTED — do those at porkbun.com; admin/tech/billing edits still work. |
| list_hosting_plansA | List the hosting plans provisionable via the API, with price (cents — pass as acknowledged_cost_cents to create_hosting), interval, trial length, and features. Use this to discover plans + costs before create_hosting rather than hardcoding them. Currently Secure Static Hosting; more products are added over time. |
| create_hostingA | Provision hosting for a domain in the account — Secure Static Hosting OR Cloud for WordPress (a managed WordPress site), chosen by sku. The domain's FIRST provision starts a 15-day FREE trial that auto-renews at the plan price ($3/mo or $30/yr) when it ends; a re-provision after deprovision is charged to account credit (one free trial per domain). Provisioning switches the domain to Porkbun nameservers if it isn't already — set agree_to_nameserver_change: true to allow that. You MUST echo the price in acknowledged_cost_cents (300 monthly / 3000 yearly) so the human is told about the auto-renew/charge. Use dry_run to preview. Provisioning can be async: status may be PENDING — poll get_hosting until ACTIVE before deploying. For a WordPress plan, the file tools (deploy_site/list_hosting_files/…) do NOT apply — manage the site through WordPress instead, using create_wp_credentials to get REST API credentials. A paid re-provision is a purchase like any other: How a purchase is paid. From prepaid account credit by default; a card saved at Porkbun is never charged by a purchase itself. Or directly, for this one purchase: pay_with_card: true (the user's card from their Stripe Link agent wallet, held and charged only if the purchase succeeds) or pay_with_usdc: true (USDC on Base over x402). If the credit is short, the call fails with INSUFFICIENT_FUNDS carrying cost, balance, shortfall, topUpAvailable and usdcAmountToCover (dry_run: true reports the same without charging). Ways forward, simplest first, always telling the user the dollar amount and getting their OK: (1) if they use Stripe Link, retry this exact call with pay_with_card: true; (2) if you can pay x402 (a wallet tool, or this server has PORKBUN_X402_PRIVATE_KEY), retry with pay_with_usdc: true; (3) when topUpAvailable is true a card is saved: top_up_account_credit (amount_cents at least topUpAmountToCover, or omit it for their configured amount), then retry; (4) add credit with top_up_with_card_mpp (Link) or top_up_with_usdc (for usdcAmountToCover, which allows for Coinbase's ~1% fee), then retry; (5) the user adds credit at https://porkbun.com/account/credit. If a purchase fails with VERIFICATION_REQUIRED, the phone (send_phone_verification_code then confirm_phone_verification) and the email (send_email_verification_code then confirm_email_verification) can both be verified without leaving the conversation, the user reading out each code. Suggest configure_auto_topup if they want credit refilled automatically. Money parameters are integer cents: state amounts to the user in dollars (cost_cents: 1108 is $11.08). If you do not move money on a user's behalf, hand it back instead: tell them the shortfall and that they can add it with buy account credit at https://porkbun.com/account/credit, then retry once they have. Buying for a new or unfunded account: the whole path, including what only the human can do, is at https://porkbun.com/llms/guides/buy-a-domain-from-an-agent. Paying directly in USDC (no account credit needed): set pay_with_usdc: true. If this server has a wallet configured (PORKBUN_X402_PRIVATE_KEY, local installs only), the payment is signed and the purchase completes in this one call. Otherwise the result is status: PAYMENT_REQUIRED with a checkoutId, an x402Url and a payUrl, and nothing is charged: pay the x402Url with a wallet tool that pays x402 URLs (for example Coinbase's awal x402 pay <x402Url> --scheme auth-capture), or have the user pay the payUrl page, then call this tool again with the same arguments plus usdc_checkout_id. Tell the user the dollar amount first. If the purchase fails after payment, the payment is NOT returned to the wallet: it stays on the Porkbun account as credit (the error says keptAsCredit: true with the new balance), so retry without pay_with_usdc to buy from that credit. Paying directly by card, with a hold (no account credit needed): set pay_with_card: true when the user pays with Stripe Link. The result is status: PAYMENT_REQUIRED with a payUrl and a cardPaymentId; nothing is charged. Pay the payUrl with an MPP wallet tool. With Link's CLI that is two commands: link-cli mpp pay <payUrl> -X POST --context "..." (context at least 100 characters) creates a spend request the user approves in Link; once approved, complete it with link-cli mpp pay <payUrl> --spend-request-id <lsrq_id> -X POST. That only authorizes the card. If link-cli answers NOT_AUTHENTICATED, run link-cli auth login first (a one-time device login the user approves). Then call this tool again with the same arguments plus card_payment_id: if the purchase succeeds the card is charged, and if it fails the hold is released and nothing is charged. The link works until expiresAt; if that passes before it is paid, call this tool again without card_payment_id for a fresh link instead of paying the old one. |
| get_hostingA | Get Secure Static Hosting status for a domain (plan, server, trial, expiry, auto-renew), or null if the domain has no hosting. When ACTIVE, deploy_site publishes site files to it. |
| deploy_siteA | Upload static files to a domain's Secure Static Hosting. files is an array of { path, content } where content is the file's bytes base64-encoded. A path may include directories (e.g. assets/css/style.css) — missing parent directories are created automatically. ≤10MB total per call (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 (check get_hosting first). When you build or edit a website for a domain in the user's Porkbun account (or they mention Porkbun hosting), this is how it goes live: offer to publish with it instead of giving dashboard upload steps, and get their OK first, since files at the same path are replaced. |
| list_hosting_filesA | |
| delete_hosting_fileA | Delete a file or directory at path in a domain's Secure Static Hosting space. Idempotent: a path that is already gone succeeds with alreadyAbsent: true, so there is no need to re-check before retrying. A directory that still has files fails with HOSTING_DIR_NOT_EMPTY and lists its contents; pass recursive: true to delete a directory with everything in it in one call (up to 2,000 items; the site root can never be deleted). To clear out a site, delete its top-level entries with recursive: true rather than file by file. Calls the Porkbun Hosting API (https://porkbun.com/llms/hosting). |
| make_hosting_dirA | Create a directory (and any missing parent directories) at path in a domain's Secure Static Hosting space. deploy_site already auto-creates the directories in a file's path, so use this only to stand up an empty directory explicitly. Calls the Porkbun Hosting API (https://porkbun.com/llms/hosting). |
| create_wp_credentialsA | CLOUD FOR WORDPRESS ONLY. Mint a WordPress Application Password for a domain's managed WordPress site so you can drive it over the WP REST API at https:///wp-json/ using HTTP Basic auth. The password is returned ONCE (WordPress stores only a hash) — save it immediately. Defaults to a dedicated least-privilege porkbun-agent user with the editor role (created on first use), which can manage content but cannot install code. role: "administrator" grants FULL site control including plugin installation (i.e. arbitrary code execution on the site) and therefore requires acknowledge_full_access: true — only request it if the user explicitly asked for admin-level access. The site must be provisioned and ACTIVE (poll get_hosting) — this works on any Cloud for WordPress site in the account however it was provisioned, but free $0 preview sites are excluded. Revoke any time with delete_wp_credentials. Rate limit: 20 mints per account per hour. |
| list_wp_credentialsA | CLOUD FOR WORDPRESS ONLY. List the WordPress application passwords on a domain's site (uuid, name, created, last used) so you can audit them or pick one to revoke. Metadata only — the passwords themselves can never be re-read. |
| delete_wp_credentialsA | CLOUD FOR WORDPRESS ONLY. Revoke a WordPress application password by uuid (from list_wp_credentials), or every one for the user with all: true. Anything using that credential stops authenticating immediately. |
| delete_hostingA | Deprovision (cancel) Secure Static Hosting for a domain; teardown is scheduled by Porkbun. Note: the domain has already used its one free trial, so provisioning it again later will be charged (no second free trial). |
| request_api_keyA | Start creating a new LIVE Porkbun API key that the account holder approves in their browser (no credentials needed to call this). Returns authUrl: give it to the user to open, sign in and approve. Then call retrieve_api_key with the requestToken and codeVerifier from this result to receive the new key pair, once. Ask for only the permissions the work needs with scopes (any of read, dns, domains, email, hosting, purchase, funding, account; read is always included): for example ["dns"] for DNS work, or everything except purchase and funding to manage without buying. Leave scopes out only if the key must buy. The user sees the requested permissions on the approval page and may grant less, never more, so check ping on the new key. The link is valid for 30 minutes. For a throwaway test key with fake credit, use create_sandbox_key instead. |
| retrieve_api_keyA | Collect the API key the user approved after request_api_key. Returns the public key (apikey, pk1_...) and the secret key (secretapikey, sk1_...) exactly once; store them securely right away (for example as PORKBUN_API_KEY / PORKBUN_SECRET_API_KEY in the MCP server's config, never in a committed file). Until the user approves, the answer says the request is still pending: wait and try again. This server keeps using the credentials it was started with; to switch to the new key, update its configuration and restart or reconnect it. |
| create_sandbox_keyA | Instantly create a free SANDBOX API key — NO credentials or approval needed (works before you have any keys). Returns a pk1_sb_ / sk1_sb_ pair for a throwaway test account seeded with $1000 fake credit. Set the returned keys as PORKBUN_API_KEY / PORKBUN_SECRET_API_KEY (or pass them to any tool) and the whole API runs against an isolated sandbox: no real registry actions, DNS changes, certificates, or charges. Ideal for rehearsing register → DNS → renew end-to-end before using a live key. Optional name labels the key. |
| sandbox_topupA | SANDBOX ONLY. Grant fake account credit to the sandbox account so paid operations (register/renew/transfer) can keep being exercised after funds run out. Requires a sandbox API key (pk1_sb_…). Optional amount_cents (integer US cents) (default 100000 = $1000; capped 1,000,000). Returns the new balance. With a live key this endpoint is not available. |
| sandbox_resetA | SANDBOX ONLY. Wipe the sandbox account's simulated state (domains, DNS, orders, credit) and re-grant $1000 fake credit — a clean slate between test runs. Requires a sandbox API key (pk1_sb_…). With a live key this endpoint is not available. |
| sandbox_trigger_webhookA | SANDBOX ONLY. Fire a sample signed webhook event to your registered endpoints so you can test your handler and HMAC 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 the webhook tools. Requires a sandbox API key (pk1_sb_…). |
| mock_callA | Get a schema-accurate EXAMPLE response for any API endpoint with NO credentials — nothing to set up. Mirrors the real path under /mock (e.g. path domain/listAll or dns/create/example.com). Touches no datastore and returns the exact shape the live API would. Set error: true to see the error-response shape instead. Use it to learn/verify response shapes before wiring up real or sandbox keys. Omit path (or pass empty) to list every mockable endpoint. |
| create_dns_recordA | Create a DNS record on a domain in the authenticated account. Returns the new record's id so it can be referenced by update_dns_record and delete_dns_record. For the name field: omit or pass empty string for the apex/root, otherwise pass the subdomain prefix only (e.g. www, not www.example.com). For MX and SRV records, set prio (priority). Free, doesn't spend account credit. If a record with this exact name, type and content already exists the call fails with DUPLICATE_RECORD and returns that record's id in existingId — use it rather than retrying or creating a variant. If a CNAME would share a name with another record type (forbidden by RFC 1034) the call fails with RECORD_CONFLICT and lists the blocking rows in conflictingRecords; delete those or pick another name. Check warnings in the response and pass it on. Porkbun keeps a DNS zone for every domain in the account whether or not the domain is delegated to Porkbun's nameservers, so this write succeeds either way. If the domain points somewhere else, the response is still SUCCESS but carries a warnings string naming the nameservers that actually answer — the record exists and nothing resolves differently. Do not report plain success in that case: either make the change at the provider that runs those nameservers, or offer update_nameservers to point the domain at Porkbun. |
| preflight_domainA | Ask whether a change is about to break a domain, BEFORE making it. Read-only, free, and the right thing to call before changing nameservers, transferring a domain out, or enabling DNSSEC. Every check comes from a real incident, so these are the failures where a zone looks fine and stops working anyway. Read blockers first — those will break something. warnings will not break outright but are usually what the user notices next. safe is true only when both are empty. Each check names the rule it comes from and carries a next_action, so you can explain the finding rather than just report it. The two an agent should treat as hard stops: dnssec-active — DS records are published at the registry, so new nameservers will serve answers that do not match them and validating resolvers will refuse the WHOLE zone. The domain goes dark rather than degrading. DNSSEC has to be removed and the DS TTL waited out BEFORE the nameservers change. Never move delegation past this one.
spf-duplicate / spf-lookups — RFC 7208 permerrors that make SPF fail for every sender, so the domain's mail is already being degraded whether or not the user has noticed.
Check nameservers-ours before you read anything else: it says whether this zone is what the world resolves at all. A domain delegated to another provider still accepts every DNS write here and none of them take effect, and the mixed case (some of the delegated nameservers are Porkbun's, some are not) means resolvers disagree query to query. The one worth explaining because nobody expects it: wildcard-shadowed. Under RFC 4592 a wildcard answers only names that do NOT exist in the zone, so a name holding just an MX or TXT record stops inheriting the wildcard's address and stops resolving — with nothing in the zone looking wrong. |
| list_dns_restore_pointsA | List the saved versions of a domain's DNS zone, newest first. DNS is the one layer with no undo built in, and this is it — if a record was deleted or edited by mistake you do not need to know what it used to say, you can look. Restore points are captured automatically before the first write to a zone in each hour (so a run of edits costs one point, not one per record), before any bulk import or zone wipe, and before any restore. Read two fields carefully. recordCount is the size of the zone AS IT WAS at that moment, not now — a drop between consecutive points is where records were lost. matchesLive marks the point the zone currently sits on, which is where you are, not where you want to go. Up to 50 are returned. Restore points only cover the zone as Porkbun held it; a domain on someone else's nameservers has nothing here. |
| diff_dns_restore_pointA | Compare a DNS restore point with the live zone before changing anything. Always do this before restore_dns_zone — it is free and it is the difference between fixing a zone and finding out afterwards. missing is in the restore point but not live: these are the records a restore would ADD BACK. extra is live but not in the restore point: a restore LEAVES THESE ALONE unless you pass prune. inSync is true when both lists are empty.
Records are matched on name, type, content and priority rather than id, because an id means nothing across a delete and re-create. SOA and NS are excluded — the zone's own delegation is not what anyone means by their DNS records. For masked records (parking, ALIAS, HTTPS) the value shown is the one that was configured, not the internal host it resolves to. |
| restore_dns_zoneA | Put a domain's DNS zone back to a saved restore point. Call diff_dns_restore_point first and show the user what will change. By default this only ADDS BACK what is missing. It does not remove records added since — pass prune: true for that, and only after reading the extra list, because 'restore my records' usually means 'put back what I lost', not 'delete everything I have done since'. The restore is itself reversible. The zone's state from immediately before is saved and returned as previousStateSavedAs; restore that id to undo. Use dry_run: true to rehearse — it reports exactly what would change and changes nothing. Read failed in the response. Parking records and other masked types (ALIAS, HTTPS) are managed by another part of the platform and cannot be recreated this way; they appear in failed rather than being counted, so restored is always a true number. If a parking record is what is missing, the domain needs re-parking on the website instead. SOA and NS are never touched. A restore only matters if this zone is the one being served. If the domain is delegated to another provider, the response carries a warnings string saying so: the records went back, and nothing the user can see has changed. |
| update_dns_recordA | Update an existing DNS record by its numeric record_id (obtained from list_dns_records). All fields except record_id and domain are optional — pass only the ones you want to change. Idempotent: applying the same update twice is a no-op. Check warnings in the response and pass it on. Porkbun keeps a DNS zone for every domain in the account whether or not the domain is delegated to Porkbun's nameservers, so this write succeeds either way. If the domain points somewhere else, the response is still SUCCESS but carries a warnings string naming the nameservers that actually answer — the record exists and nothing resolves differently. Do not report plain success in that case: either make the change at the provider that runs those nameservers, or offer update_nameservers to point the domain at Porkbun. |
| delete_dns_recordA | Delete a single DNS record by its numeric record_id (obtained from list_dns_records). Idempotent: deleting an already-deleted record returns success. Free. Check warnings in the response and pass it on. Porkbun keeps a DNS zone for every domain in the account whether or not the domain is delegated to Porkbun's nameservers, so this write succeeds either way. If the domain points somewhere else, the response is still SUCCESS but carries a warnings string naming the nameservers that actually answer — the record exists and nothing resolves differently. Do not report plain success in that case: either make the change at the provider that runs those nameservers, or offer update_nameservers to point the domain at Porkbun. |
| create_dnssec_recordA | Submit a DNSSEC DS record to the registry for a domain. Use when you sign DNS yourself (custom nameservers running BIND/Knot/PowerDNS/etc.) and need to publish the chain-of-trust at the parent zone. Required: keyTag, algorithm, digestType, digest. Optional key-data fields for registries that require full DNSKEY (rare). Algorithms and digest types are validated per registry, not globally: registries are retiring the values deprecated by RFC 9904/9905/9906 on their own schedules, so a value one registry has already dropped may still be accepted by another. A refused value returns DNSSEC_ALGORITHM_DEPRECATED naming a replacement, and retrying the same values will not help. Use alg 8 (RSA/SHA-256) or 13 (ECDSA/SHA-256) with digestType 2 (SHA-256) to be accepted everywhere. A SUCCESS response may carry a warnings array when a value still works but is being retired: the record WAS created. Deprecation never affects list_dnssec_records or delete_dnssec_record. |
| delete_dnssec_recordA | Remove a DNSSEC DS record from the registry for a domain, identified by key tag. Use when retiring a key. Idempotent: deleting a non-existent key tag returns success. |
| create_url_forwardA | Add a URL forwarding rule for a domain. Forwards a subdomain (or apex if subdomain is empty/omitted) to an arbitrary destination URL. Useful for redirects without setting up a web server. Free. |
| delete_url_forwardA | Delete a URL forwarding rule by its id (obtained from list_url_forwards). Idempotent. |
| list_email_forwardsA | List the email forwards on a domain: each address at the domain and the forwardTo mailbox it is delivered to (one address can forward to several). Also returns limits (used / max, 20 by default) and dnsConfigured, whether the domain's apex MX records in the Porkbun zone point at Porkbun forwarding. If the domain uses other nameservers, forwarding only works once those point MX at Porkbun too. |
| create_email_forwardA | Forward an address at the domain (e.g. info@example.com) to another mailbox. Free, up to the domain's limit. No catch-all or wildcard forwards, and an address that is an email hosting mailbox cannot also be a forward. Setting up forwarding puts Porkbun's MX records (and SPF include) at the domain apex. If that would delete MX records for another mail service or rewrite the domain's SPF record, the call returns DNS_CHANGE_CONFIRMATION_REQUIRED with dnsChanges listing exactly what would change, and nothing is changed: tell the user (deleting another provider's MX stops that mail service receiving), and only with their OK call again with confirm_dns_changes: true. Use dry_run: true to preview, including whether confirmation will be needed. |
| delete_email_forwardA | Delete one email forward, named by its address and forward_to (from list_email_forwards; an address can forward to several mailboxes, so both are needed). When it was the domain's last forward and there are no email hosting mailboxes, Porkbun's forwarding MX and SPF records are removed too (dnsRecordsRemoved: true), so mail to the domain stops being accepted. |
| list_mailboxesA | List a domain's email hosting mailboxes (real inboxes at the domain, read over IMAP/POP or webmail), with status, address, whether each is the free trial, and expires. A mailbox with status: PENDINGSETUP and address: null is already paid for, or is the free trial mailbox every domain registration includes: setup_mailbox gives it an address for free. Each mailbox also shows autoRenews and its renewalPrice (renew_mailbox adds a year now). Also returns the price of buying another mailbox and the mail client settings. |
| setup_mailboxA | Give a waiting mailbox (already paid for, or the free trial that comes with a domain registration) its address and password. Free. If none is waiting the result is NO_PENDING_MAILBOX: buy one with create_mailbox (with the user's OK on the price). Leave password out and Porkbun generates a strong one and returns it ONCE in password: give it to the user straight away, it is not shown again (it can be changed later with set_email_password). If you pass one, it needs 12-72 characters with an upper case letter, a lower case letter, a number and a special character. A mailbox needs Porkbun's MX records and SPF include at the domain apex (the same records as email forwarding), and the call puts them in place. If that would delete MX records for another mail service or rewrite the domain's SPF record, it returns DNS_CHANGE_CONFIRMATION_REQUIRED with dnsChanges and changes (and charges) nothing: tell the user what will change, and only with their OK call again with confirm_dns_changes: true. The answer includes clientSettings (IMAP/SMTP hosts and ports) for setting up a mail app. DKIM and DMARC are set up on the website. Needs a verified account email and phone. |
| create_mailboxA | Spends account credit, or pays directly by card (pay_with_card) or in USDC (pay_with_usdc). Buy one email hosting mailbox ($36.00 a year, renewing automatically; the current price is price in list_mailboxes) and set it up in the same call. Check list_mailboxes first: if a mailbox is already waiting (the free trial every registration includes), setup_mailbox is free. Tell the user the price and that it renews yearly before buying. cost_cents must equal the price; dry_run: true with cost_cents: 0 quotes it and checks everything without charging. If the charge succeeds but setup does not, the result says setupComplete: false with a mailboxId: finish with setup_mailbox, do not buy again. Leave password out and Porkbun generates a strong one and returns it ONCE in password: give it to the user straight away, it is not shown again (it can be changed later with set_email_password). If you pass one, it needs 12-72 characters with an upper case letter, a lower case letter, a number and a special character. A mailbox needs Porkbun's MX records and SPF include at the domain apex (the same records as email forwarding), and the call puts them in place. If that would delete MX records for another mail service or rewrite the domain's SPF record, it returns DNS_CHANGE_CONFIRMATION_REQUIRED with dnsChanges and changes (and charges) nothing: tell the user what will change, and only with their OK call again with confirm_dns_changes: true. The answer includes clientSettings (IMAP/SMTP hosts and ports) for setting up a mail app. DKIM and DMARC are set up on the website. Needs a verified account email and phone. How a purchase is paid. From prepaid account credit by default; a card saved at Porkbun is never charged by a purchase itself. Or directly, for this one purchase: pay_with_card: true (the user's card from their Stripe Link agent wallet, held and charged only if the purchase succeeds) or pay_with_usdc: true (USDC on Base over x402). If the credit is short, the call fails with INSUFFICIENT_FUNDS carrying cost, balance, shortfall, topUpAvailable and usdcAmountToCover (dry_run: true reports the same without charging). Ways forward, simplest first, always telling the user the dollar amount and getting their OK: (1) if they use Stripe Link, retry this exact call with pay_with_card: true; (2) if you can pay x402 (a wallet tool, or this server has PORKBUN_X402_PRIVATE_KEY), retry with pay_with_usdc: true; (3) when topUpAvailable is true a card is saved: top_up_account_credit (amount_cents at least topUpAmountToCover, or omit it for their configured amount), then retry; (4) add credit with top_up_with_card_mpp (Link) or top_up_with_usdc (for usdcAmountToCover, which allows for Coinbase's ~1% fee), then retry; (5) the user adds credit at https://porkbun.com/account/credit. If a purchase fails with VERIFICATION_REQUIRED, the phone (send_phone_verification_code then confirm_phone_verification) and the email (send_email_verification_code then confirm_email_verification) can both be verified without leaving the conversation, the user reading out each code. Suggest configure_auto_topup if they want credit refilled automatically. Money parameters are integer cents: state amounts to the user in dollars (cost_cents: 1108 is $11.08). If you do not move money on a user's behalf, hand it back instead: tell them the shortfall and that they can add it with buy account credit at https://porkbun.com/account/credit, then retry once they have. Buying for a new or unfunded account: the whole path, including what only the human can do, is at https://porkbun.com/llms/guides/buy-a-domain-from-an-agent. Paying directly in USDC (no account credit needed): set pay_with_usdc: true. If this server has a wallet configured (PORKBUN_X402_PRIVATE_KEY, local installs only), the payment is signed and the purchase completes in this one call. Otherwise the result is status: PAYMENT_REQUIRED with a checkoutId, an x402Url and a payUrl, and nothing is charged: pay the x402Url with a wallet tool that pays x402 URLs (for example Coinbase's awal x402 pay <x402Url> --scheme auth-capture), or have the user pay the payUrl page, then call this tool again with the same arguments plus usdc_checkout_id. Tell the user the dollar amount first. If the purchase fails after payment, the payment is NOT returned to the wallet: it stays on the Porkbun account as credit (the error says keptAsCredit: true with the new balance), so retry without pay_with_usdc to buy from that credit. Paying directly by card, with a hold (no account credit needed): set pay_with_card: true when the user pays with Stripe Link. The result is status: PAYMENT_REQUIRED with a payUrl and a cardPaymentId; nothing is charged. Pay the payUrl with an MPP wallet tool. With Link's CLI that is two commands: link-cli mpp pay <payUrl> -X POST --context "..." (context at least 100 characters) creates a spend request the user approves in Link; once approved, complete it with link-cli mpp pay <payUrl> --spend-request-id <lsrq_id> -X POST. That only authorizes the card. If link-cli answers NOT_AUTHENTICATED, run link-cli auth login first (a one-time device login the user approves). Then call this tool again with the same arguments plus card_payment_id: if the purchase succeeds the card is charged, and if it fails the hold is released and nothing is charged. The link works until expiresAt; if that passes before it is paid, call this tool again without card_payment_id for a fresh link instead of paying the old one. |
| renew_mailboxA | Spends account credit, or pays directly by card (pay_with_card) or in USDC (pay_with_usdc). Add a year to an email hosting mailbox now, at its renewalPrice from list_mailboxes ($36.00, or a legacy price for mailboxes that still qualify). Set-up mailboxes already renew on their own when due (autoRenews: true), so use this when the user wants to pay early or keep a trial mailbox. Name the mailbox with mailbox_id or address. Tell the user the price first; cost_cents must match, and dry_run: true with cost_cents: 0 quotes it and shows the new expiry (expiresAfter). How a purchase is paid. From prepaid account credit by default; a card saved at Porkbun is never charged by a purchase itself. Or directly, for this one purchase: pay_with_card: true (the user's card from their Stripe Link agent wallet, held and charged only if the purchase succeeds) or pay_with_usdc: true (USDC on Base over x402). If the credit is short, the call fails with INSUFFICIENT_FUNDS carrying cost, balance, shortfall, topUpAvailable and usdcAmountToCover (dry_run: true reports the same without charging). Ways forward, simplest first, always telling the user the dollar amount and getting their OK: (1) if they use Stripe Link, retry this exact call with pay_with_card: true; (2) if you can pay x402 (a wallet tool, or this server has PORKBUN_X402_PRIVATE_KEY), retry with pay_with_usdc: true; (3) when topUpAvailable is true a card is saved: top_up_account_credit (amount_cents at least topUpAmountToCover, or omit it for their configured amount), then retry; (4) add credit with top_up_with_card_mpp (Link) or top_up_with_usdc (for usdcAmountToCover, which allows for Coinbase's ~1% fee), then retry; (5) the user adds credit at https://porkbun.com/account/credit. If a purchase fails with VERIFICATION_REQUIRED, the phone (send_phone_verification_code then confirm_phone_verification) and the email (send_email_verification_code then confirm_email_verification) can both be verified without leaving the conversation, the user reading out each code. Suggest configure_auto_topup if they want credit refilled automatically. Money parameters are integer cents: state amounts to the user in dollars (cost_cents: 1108 is $11.08). If you do not move money on a user's behalf, hand it back instead: tell them the shortfall and that they can add it with buy account credit at https://porkbun.com/account/credit, then retry once they have. Buying for a new or unfunded account: the whole path, including what only the human can do, is at https://porkbun.com/llms/guides/buy-a-domain-from-an-agent. Paying directly in USDC (no account credit needed): set pay_with_usdc: true. If this server has a wallet configured (PORKBUN_X402_PRIVATE_KEY, local installs only), the payment is signed and the purchase completes in this one call. Otherwise the result is status: PAYMENT_REQUIRED with a checkoutId, an x402Url and a payUrl, and nothing is charged: pay the x402Url with a wallet tool that pays x402 URLs (for example Coinbase's awal x402 pay <x402Url> --scheme auth-capture), or have the user pay the payUrl page, then call this tool again with the same arguments plus usdc_checkout_id. Tell the user the dollar amount first. If the purchase fails after payment, the payment is NOT returned to the wallet: it stays on the Porkbun account as credit (the error says keptAsCredit: true with the new balance), so retry without pay_with_usdc to buy from that credit. Paying directly by card, with a hold (no account credit needed): set pay_with_card: true when the user pays with Stripe Link. The result is status: PAYMENT_REQUIRED with a payUrl and a cardPaymentId; nothing is charged. Pay the payUrl with an MPP wallet tool. With Link's CLI that is two commands: link-cli mpp pay <payUrl> -X POST --context "..." (context at least 100 characters) creates a spend request the user approves in Link; once approved, complete it with link-cli mpp pay <payUrl> --spend-request-id <lsrq_id> -X POST. That only authorizes the card. If link-cli answers NOT_AUTHENTICATED, run link-cli auth login first (a one-time device login the user approves). Then call this tool again with the same arguments plus card_payment_id: if the purchase succeeds the card is charged, and if it fails the hold is released and nothing is charged. The link works until expiresAt; if that passes before it is paid, call this tool again without card_payment_id for a fresh link instead of paying the old one. |
| set_email_passwordA | Change the password of an email hosting mailbox (an active one, from list_mailboxes). The new password needs 12-72 characters with an upper case letter, a lower case letter, a number and a special character. Only change a password the user asked to change, and give the new one to them. |
| create_glue_recordA | Create a glue record for a host on a domain. Used when running your own nameservers on the same domain they serve (e.g. ns1.example.com serving example.com). The subdomain is just the host part (e.g. ns1), not the full FQDN. Provide IPs as an array of IPv4 and/or IPv6 addresses. Idempotent. |
| update_glue_recordA | Update the IP addresses associated with an existing glue record. Replaces the full IP list — pass all IPs you want set, not just additions. Idempotent. |
| delete_glue_recordA | Delete a glue record by host on a domain. Idempotent: deleting a non-existent glue record returns success. |
| list_doc_topicsA | List the available Porkbun API documentation topics. Returns the docs index (Markdown) — every per-topic page (e.g. dns, domain, webhooks, ssl, pricing) with a one-line description and endpoint count, plus links to the full reference and the OpenAPI spec. Use this first to discover what docs exist, then read_doc to read one. Grounds an agent in Porkbun's own docs without leaving the conversation. |
| read_docA | Read a Porkbun documentation page as Markdown. Pass a topic from list_doc_topics (e.g. dns, domain, webhooks, ssl, pricing, account, marketplace). Special values: overview (the llms.txt orientation), full (the entire flat reference — every endpoint), or index (the topic list). Returns the page's full Markdown, including endpoint signatures, parameters, and curl examples. |
| search_docsA | Keyword-search the full Porkbun API reference and return the most relevant sections (endpoints/topics) as Markdown. Use for 'how do I…' questions — e.g. 'verify a webhook signature', 'register a domain with dry run', 'set a TXT record'. Returns the best-matching sections with their headings; follow up with read_doc for a full topic page. Searches Porkbun's own docs only. |
| get_webhook_event_typesA | List the event types you can subscribe a webhook endpoint to. Returns event-type strings like domain.registered, domain.renewed, domain.transfer.completed, domain.expiring, dns.record.created|updated|deleted, cloudflare.connect.completed|failed and account.verification.code_sent|completed. What each event sends: https://porkbun.com/llms/webhooks. Use these values (or * for all, or a prefix wildcard like dns.*) when calling create_webhook. |
| list_webhooksA | List the webhook endpoints registered on the authenticated account. Each endpoint includes its id, URL, subscribed events, status (ACTIVE|DISABLED), consecutive failure count, last success/failure timestamps, last error, and signing secret. Porkbun POSTs a signed JSON payload to each endpoint when subscribed events occur; deliveries are signed with the endpoint's secret via HMAC-SHA256 over {timestamp}.{rawBody} and sent in the X-Porkbun-Signature header. |
| get_webhookA | Fetch a single webhook endpoint by its numeric id, including its signing secret and delivery health (consecutive failures, last success/failure). |
| list_webhook_deliveriesA | List recent webhook delivery attempts (newest first), across all endpoints or filtered to one. Each row reports event type, event id, status (PENDING|PROCESSING|DELIVERED|FAILED), attempt count, HTTP status, and last error. Delivery history is retained ~30 days. Use this to audit what was sent and to find a delivery id to resend. The payload is omitted here — use get_webhook_delivery for the full signed payload. |
| get_webhook_deliveryA | Fetch a single webhook delivery by id, including the full JSON payload that was (or will be) sent and its delivery status. Get delivery ids from list_webhook_deliveries. |
| create_webhookA | Register a webhook endpoint. Porkbun will POST a signed JSON payload to url whenever a subscribed event occurs. Returns the new endpoint including its secret — store it securely; it's used to verify the X-Porkbun-Signature header (HMAC-SHA256 over {timestamp}.{rawBody}). url must be a publicly reachable HTTPS endpoint: https:// on port 443, no credentials embedded in the URL, and a hostname that resolves to a public internet address. Private, loopback (including tricks like 127.0.0.1.sslip.io), link-local, CGNAT and reserved addresses are rejected with INVALID_WEBHOOK_URL — do not try to point this at localhost or an internal host. For local development use a public HTTPS tunnel (ngrok, Cloudflare Tunnel) or a sandbox key with sandbox_trigger_webhook. A hostname that does not resolve yet is accepted so you can register before the receiver is deployed, but the rules are re-checked before every delivery. Omit events (or pass ['*']) to subscribe to all event types; you can also pass prefix wildcards like dns.*. |
| update_webhookA | Update a webhook endpoint. Only the supplied fields change. Set status to DISABLED to pause deliveries or ACTIVE to resume (resuming also clears the consecutive-failure counter). A replacement url must satisfy the same public-HTTPS rules as create_webhook. Idempotent. Calls the Porkbun Webhooks API (https://porkbun.com/llms/webhooks). |
| rotate_webhook_secretA | Generate a new signing secret for a webhook 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. |
| test_webhookA | Send a webhook.test event to an endpoint to confirm it's reachable and that signature verification works. The endpoint must be ACTIVE. Delivery happens asynchronously (usually within a minute); check the endpoint's last_success_date via get_webhook afterward. |
| resend_webhookA | Re-queue a past webhook delivery to its endpoint. Clones the delivery into a fresh attempt, reusing the ORIGINAL event id — so a consumer that dedupes on X-Porkbun-Webhook-Id treats the resend as the same event. The endpoint must still exist and be ACTIVE. Use after fixing a downstream bug to replay a delivery that previously FAILED. |
| delete_webhookA | Delete a webhook endpoint by id. Deliveries stop immediately. Idempotent in effect: deleting a non-existent endpoint returns an error you can safely ignore. |
| get_cloudflare_connectionA | Check whether this Porkbun account is connected to a Cloudflare account, and which one. THIS IS THE POLL TARGET for the connect flow. Connecting is a HUMAN action: Cloudflare's consent screen must be completed in a browser and the authorization is bound to the Porkbun web session that starts it, so it cannot be driven over the API. When connected is false the response includes a connectUrl — show that URL to the user, ask them to authorize, then call this tool again every few seconds until connected is true. Everything else in the Cloudflare flow is fully automatable once this is true. |
| list_cloudflare_inventoryA | List every domain in the account with its Cloudflare eligibility: state is eligible / warn / blocked / connected / inprogress, each with a human-readable reason (DNSSEC live, custom nameservers, uses Porkbun hosting, etc). Read this BEFORE queueing so you can tell the user what will move and what won't. Works even when the Cloudflare account isn't connected yet, so you can plan while the user authorizes. |
| connect_domains_to_cloudflareA | Queue domains to move into the customer's own Cloudflare account: Porkbun creates the zone there, copies over the DNS records it holds, and repoints the registry nameservers at Cloudflare. ASYNCHRONOUS — a successful call means QUEUED, never connected; the work runs in the background over the next few minutes, so poll get_cloudflare_queue afterwards. Results are PER DOMAIN: each name comes back under queued, skipped or alreadyQueued with its own reason. A non-empty skipped is a NORMAL outcome (DNSSEC live, custom nameservers, already connected), NOT a failed call — report the reasons rather than calling the whole thing an error. Eligibility is re-checked right before each domain is acted on, so a queued domain can still be skipped later. Requires the Cloudflare account to be connected first (see get_cloudflare_connection). Use dry_run to preview verdicts without queueing. Max 500 domains per call. |
| get_cloudflare_queueA | List every Cloudflare move this account has requested, with per-domain status and message. Use this to poll progress after connect_domains_to_cloudflare. Queue rows are never deleted, so this is also the audit trail of past moves. |
| get_cloudflare_domain_statusA | Get the Cloudflare move status for ONE domain, including the Cloudflare zone id once created and the nameservers Porkbun replaced (kept so the move can be undone). Returns NOT_QUEUED if the domain has never been queued. |
| retry_cloudflare_domainA | Re-queue a domain whose Cloudflare move failed or was skipped. Fails if it is already connected, already in progress, or no longer in the account — the message says which. Fix the underlying reason first (e.g. disable DNSSEC) or the retry will be skipped again. |
| rollback_cloudflare_domainA | UNDO a completed Cloudflare move: point the domain's nameservers back at Porkbun, restoring the DNS Porkbun still holds. This changes authoritative DNS for a live domain, so confirm with the user first. The Cloudflare zone is deliberately LEFT IN PLACE — deleting a zone inside the customer's own Cloudflare account is their call. Fails if Porkbun never moved the domain, it is already back on Porkbun nameservers, or it is being worked on right now. |
| disconnect_cloudflareA | Remove this account's stored Cloudflare connection. Domains already moved stay on Cloudflare and keep resolving — this only stops Porkbun making further changes on the customer's behalf. Reconnecting requires the human browser authorization again, so confirm before calling. |
| get_cloudflare_recordsA | List a domain's LIVE DNS records as Cloudflare holds them, each with its orange-cloud (proxied) state and whether it is proxiable at all. Once a domain has moved to Cloudflare THIS is the authoritative record set — list_dns_records reads the Porkbun zone, which is no longer answering queries for it. Returns ZONE_NOT_READY if the zone does not exist yet. |
| set_cloudflare_proxyA | Turn the Cloudflare proxy (orange cloud) on or off for a domain's DNS records. NOTE: connecting a domain deliberately imports every record DNS-only (grey cloud), because changing how traffic is served at the same time as changing who serves DNS makes failures hard to diagnose — so proxying is a separate, explicit step, best done AFTER confirming the site still works. Defaults to every proxiable record; pass records to target specific names ("@" = apex, bare labels like "www" are expanded). Only A/AAAA/CNAME can be proxied; anything else comes back under skipped with a reason rather than failing. Proxying hides the origin IP, so if MX points at a name you're proxying, mail to it breaks — that comes back in warnings. Reversible: call again with enabled=false. Use dry_run to preview. |
| preview_cloudflare_moveA | Show exactly which DNS records would be copied into Cloudflare for a domain, and which would be dropped, WITHOUT queueing anything. Use this to tell a user what a move will do to their DNS before committing. Records are always created DNS-only (grey cloud); proxying is a separate step via set_cloudflare_proxy. |
| get_cloudflare_zoneA | Get what CLOUDFLARE currently says about a domain's zone (status, paused, its nameservers, activation date) rather than what Porkbun's queue row remembers. These drift: if someone repoints the nameservers elsewhere after the move, the Porkbun row still says done while Cloudflare has stopped serving the domain. The response includes the live public nameservers and a nameserversDrifted boolean so you don't have to diff them yourself. |
| get_cloudflare_zone_settingsA | Read the Cloudflare zone settings that matter after a move: ssl mode, always_use_https, automatic_https_rewrites, min_tls_version, development_mode, cache_level. Warns when SSL mode is off or flexible, which means Cloudflare fetches the origin over plain HTTP while visitors still see a padlock. If this returns CLOUDFLARE_REAUTHORIZE_REQUIRED the customer's Cloudflare connection predates this permission — show them the connectUrl and ask them to reconnect (one click; domains already moved are unaffected). |
| set_cloudflare_zone_settingsA | Change Cloudflare zone settings for a moved domain. Only an allowlist is editable (ssl, always_use_https, automatic_https_rewrites, min_tls_version, development_mode, cache_level) — WAF/firewall/security controls are deliberately not exposed. Prefer ssl full over flexible: flexible makes Cloudflare fetch the origin over plain HTTP, a downgrade the visitor cannot see. CLOUDFLARE_REAUTHORIZE_REQUIRED means the stored authorization lacks this permission — surface the connectUrl and ask the customer to reconnect. Supports dry_run. |
| create_cloudflare_recordA | Create a DNS record in a domain's Cloudflare zone. IMPORTANT: for a domain that has been moved to Cloudflare this is the tool that changes what actually resolves — create_dns_record writes Porkbun's zone, which no longer answers for it. name accepts "@" for the apex or a bare label like "www". ttl: 1 means Cloudflare-automatic. MX requires priority. proxied (orange cloud) applies to A/AAAA/CNAME only. Record types Cloudflare models as structured objects (SRV, CAA, …) need a data object instead of content. Supports dry_run. |
| edit_cloudflare_recordA | Update a DNS record in a domain's Cloudflare zone. Partial: fields you omit keep their current value. Get record_id from get_cloudflare_records. The response includes both the new record and the previous one, so you can tell the user exactly what changed. Supports dry_run. |
| delete_cloudflare_recordA | Delete a DNS record from a domain's Cloudflare zone. This changes live DNS for a domain Cloudflare is authoritative for, so confirm with the user first — deleting the wrong record can take a site or its mail offline. Get record_id from get_cloudflare_records. The record is read before deletion, so the response reports exactly what was removed. Supports dry_run. |