Type a domain, find out whether it's registered. The answer comes from the registry of record over RDAP — the protocol ICANN requires registries to run alongside WHOIS — rather than from a reseller's cached list.
backend/rdap.tsnormalizes what was typed: pasted URLs lose their scheme and path, IDNs are punycoded, a leadingwww.is dropped.- The TLD is resolved to an RDAP service through IANA's bootstrap file, cached for 12 hours per instance.
GET <service>/domain/<name>decides it:200means registered and carries the record,404means the registry has no entry.
A handful of registries run RDAP without appearing in the bootstrap file
(.ac, .de, .io, .sh). Those are listed in SUPPLEMENTAL_SERVICES, each
verified against a known-registered name in that exact TLD. Never point a TLD at
a service that isn't authoritative for it — a foreign service answers 404, and
404 is what makes this site say "available".
TLDs with no RDAP service at all (.co, .me, and other ccTLDs) return
unsupported instead of a guess.
GET /api/pricing?domain= answers with Porkbun's list price for the name's
suffix — first year, renewal, and inbound transfer, in USD. Porkbun publishes
its whole price list
unauthenticated, so this needs no API key.
Two things shape how it's wired up:
- Nobody waits on it who didn't ask. Porkbun rebuilds the entire 900-TLD
list per request and measures 15–20 seconds cold, so this is neither part of
/api/lookupnor fired on every search: the settled card offers a button, and only a visitor who presses it pays the wait. A shared 6-hour cache (plus in-flight de-duplication, since a 20-second fetch is long enough for visitors to pile up behind it) means only the first press pays it at all. - The suffix is matched longest-first. Porkbun sells some ccTLDs at the
second level —
co.ukis priced apart fromuk— sobbc.co.ukis priced asco.uk, notuk.
Entries marked specialType: handshake are dropped: those are Handshake names,
which resolve outside ICANN DNS and so describe nothing this site can look up.
These are list prices for ordinary names; premium and reserved names are quoted
per-domain. Losing this call costs the price panel and nothing else.
RDAP answers "is there a registration record?", which is not the question a
visitor is asking. Reserved, blocked and registry-held names own no RDAP object,
so the registry reports 404 and the site reads that as available — correctly,
and still misleadingly. claude.run is the example that prompted this: no
registry record, no DNS delegation, and not for sale.
Set both of these to close the gap:
PORKBUN_API_KEY=pk1_... PORKBUN_SECRET_API_KEY=sk1_...
With them set, GET /api/availability?domain= asks Porkbun's authenticated
checkDomain whether it will
sell that exact name and returns an availability block: registrable,
premium, and the per-name price.
The page asks before it answers. A name the registry has no record of gets rendered without a stamp — the card shows the domain, the source, and the plain fact that the registry holds no record, then says it is asking Porkbun. The stamp is pressed only once that comes back, so nobody is ever shown an Available that turns into a Withheld a second later. Which stamp lands depends on the answer:
| Porkbun says | Stamp | Verdict |
|---|---|---|
| won't sell it | Withheld | reserved, blocked, or registry-held — not yours to take |
| will sell it | Available | confirmed registrable |
| premium | Available | confirmed, with the per-name quote in the verdict |
| nothing (see below) | Available | the registry's own hedged reading, unchanged |
Four constraints shape it:
- The check is rationed. Porkbun's default is one check per 10 seconds per account, not per caller, so every visitor shares one budget. Results are cached per name for 10 minutes, concurrent checks for the same name share one request, and a name that misses the cache while the window is shut simply goes unanswered rather than queueing behind it. A card is waiting on this: the registry's hedged answer now beats the confirmed one ten seconds late.
- It travels alone. The check is a separate endpoint from the price list precisely because the verdict blocks on it. It carries one small question and answers in about a second; the list's cold 20-second rebuild stays behind its button where it can't hold up an answer.
- Only an
availableverdict spends a check. That is the one answer a registrar can contradict; a name the registry already reports as registered has nothing to learn from asking. - Silence is never a confirmation. No credentials, gate shut, timeout, rate
limit, or an unrecognised
availall return noavailabilityat all, and the card falls back to exactly the hedged wording it used before the check existed. Only an explicit answer from Porkbun moves the verdict.
Without the credentials every name resolves to that last row, which is how the page behaved before any of this existed.
| Path | What it does |
|---|---|
main.ts | Hono app: static routes, /api/lookup, /api/availability, /api/pricing |
backend/rdap.ts | Bootstrap cache, normalization, RDAP record parsing |
backend/porkbun.ts | Per-name availability check, price-list cache, suffix matching |
backend/files.ts | Static file helpers, with a local-disk fallback |
frontend/ | The page: HTML, CSS, and the renderer |
shared/types.ts | The API response contracts |
vt push # deploy to Val Town deno run -A serve.ts # or preview locally, see below
For a local preview, point Deno's server at the exported handler:
import app from "./main.ts";
Deno.serve({ port: 8787 }, app);
backend/files.ts reads from disk when it sees a file:// URL, so the static
routes work outside Val Town too.