# Porkbun API v3 — Closeouts

> Expired domains that did not sell at auction, offered at a fixed price with no bidding — first buyer at the current price takes the name.

The price descends on a schedule, so a listing is often gone within minutes; treat a search result as perishable and re-check the price before buying.

Search returns `age` and `registrationDate` on every row and both are sortable, which is the part the website cannot do — it paginates by price tier and does not show registration date at all.

Part of the Porkbun API v3.48. Topic index: https://porkbun.com/llms · Full reference: https://porkbun.com/llms-full.txt · Overview: https://porkbun.com/llms.txt · OpenAPI spec: https://porkbun.com/api/json/v3/spec

**Auth:** send `X-API-Key` / `X-Secret-API-Key` headers (preferred) or `apikey` / `secretapikey` in the JSON body. Create keys at https://porkbun.com/account/api

---

# Endpoints

## GET /api/json/v3/closeout/search

**Search expired-domain closeouts**

Search the closeout inventory. Closeouts are expired domains that did not sell at auction and are now offered at a fixed, descending price — there is no bidding, the first buyer at the current price takes the name.

Every filter is optional; with none you get the first page of the whole list plus `totalAvailable`. Page with `start`/`limit` until `start >= totalAvailable`.

**`age` and `registrationDate` are on every row and both are sortable.** On the website the inventory is paginated by price tier and registration date is not shown, so finding aged names means walking several tier pages and then doing a WHOIS lookup per candidate. `sortName=registrationDate&sortDirection=asc` returns the oldest registrations first in one call.

`price` is the closeout price alone. The binding total adds the renewal or transfer year you are also buying, and comes from `/closeout/get/{domain}` — it cannot be derived from search results, because a domain already at Porkbun is renewed while anything else is transferred in, and those are priced differently.

| Parameter | In | Required | Description |
|---|---|---|---|
| `query` | query | no | Keyword match on the domain name. |
| `tld` | query | no | Single TLD, with or without the leading dot. Omit to search all. |
| `nameLength` | query | no | Exact SLD character count. |
| `ageMin` | query | no | Minimum domain age in years. Pair with sortName=registrationDate to find aged names. |
| `ageMax` | query | no | Maximum domain age in years. |
| `priceMin` | query | no | Minimum closeout price, integer US cents. Converted to whole dollars for the provider, rounding outward so the range never excludes an item inside it. |
| `priceMax` | query | no | Maximum closeout price, integer US cents. |
| `sortName` | query | no | One of: domain, endTime, price, revenue, visitors, inboundLinks, registrationDate. Ascending on registrationDate means oldest registration first, which is how you find aged names. Ascending on revenue, visitors or inboundLinks puts the domains with no recorded figure first (they return null) — use desc on those to see the highest values; the response carries a `warnings` entry reminding you. |
| `sortDirection` | query | no | `asc` or `desc`. |
| `start` | query | no | Offset for paging. Default 0. |
| `limit` | query | no | Rows per page, 1-500. Default 100. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `count` | integer |  |
| `totalAvailable` | integer | Size of the filtered set, for paging. |
| `start` | integer |  |
| `limit` | integer |  |
| `closeouts` | object[] |  |

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

**One closeout, with the binding total**

A single closeout plus `totalPrice` — the closeout price plus the registration year that comes with it. That total is what `/closeout/buy` will charge and what it expects back as `cost`.

`localDomain` tells you which side of the pricing you are on: true means the name is already at Porkbun and is renewed, false means it is transferred in. `available` is false once somebody has claimed it.

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

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `available` | boolean |  |
| `closeout` | object |  |

## POST /api/json/v3/closeout/buy/{domain}

**Buy a closeout outright**

**Spends account credit.** Buys a closeout at its current price and claims the name with the provider in one call. Send `cost` as the exact `totalPrice` from `/closeout/get/{domain}`; any other value is refused with `COST_MISMATCH` so a caller can never be charged a price it did not name. `dryRun: true` with `cost: 0` returns a quote and charges nothing. Honours `Idempotency-Key`.

**The domain does not arrive immediately.** Claiming reserves it; the provider then has to release it, which usually takes a few days. Poll `/domain/listAll` or subscribe to the `domain.registered` webhook rather than expecting it in your account when this returns.

Every post-charge failure refunds automatically and says so via `refunded: true` — including losing the race to another buyer (`CLOSEOUT_UNAVAILABLE`) and the price moving between quote and claim (`COST_MISMATCH`). Closeouts are first-come at a fixed price, so a lost race is not worth retrying on the same name.

Not available with a sandbox key (`SANDBOX_UNSUPPORTED`): the inventory provider has no test environment, so a purchase cannot be rehearsed without claiming a real name. Use `dryRun` against a live key instead — it validates and prices without charging.

Eligibility is the same as registering a domain, plus verified email and phone. There is no account-age or prior-order requirement — a first-time customer can buy a closeout, which matters because listings are first-come and often gone within minutes. An account that support has blocked from auctions and closeouts (past-due invoices, or an auction terms violation) returns `CLOSEOUT_NOT_ELIGIBLE`, which is not retryable.

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

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `cost` | integer | yes | Exact totalPrice in integer US cents, from /closeout/get/{domain}. Use 0 only with dryRun to request a quote. |
| `dryRun` | boolean | no | Validate and price without charging or claiming. |

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `orderId` | integer |  |
| `closeoutId` | integer |  |
| `closeoutPrice` | integer |  |
| `renewalPrice` | integer |  |
| `totalPrice` | integer |  |
| `message` | string |  |

---

## More

- Guides (how-tos): https://porkbun.com/llms/guides
- Topic index: https://porkbun.com/llms
- Full reference (one file): https://porkbun.com/llms-full.txt
- OpenAPI spec (full schemas): https://porkbun.com/api/json/v3/spec
- Short overview: https://porkbun.com/llms.txt
- Official MCP server: https://porkbun.com/mcp (`npx -y @porkbunllc/mcp-server`)
- Create API keys: https://porkbun.com/account/api
