Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

whois

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.

How a lookup works

  1. backend/rdap.ts normalizes what was typed: pasted URLs lose their scheme and path, IDNs are punycoded, a leading www. is dropped.
  2. The TLD is resolved to an RDAP service through IANA's bootstrap file, cached for 12 hours per instance.
  3. GET <service>/domain/<name> decides it: 200 means registered and carries the record, 404 means 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.

What it costs

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/lookup nor 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.uk is priced apart from uk — so bbc.co.uk is priced as co.uk, not uk.

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.

Whether you can actually buy it

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 saysStampVerdict
won't sell itWithheldreserved, blocked, or registry-held — not yours to take
will sell itAvailableconfirmed registrable
premiumAvailableconfirmed, with the per-name quote in the verdict
nothing (see below)Availablethe 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 available verdict 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 avail all return no availability at 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.

Files

PathWhat it does
main.tsHono app: static routes, /api/lookup, /api/availability, /api/pricing
backend/rdap.tsBootstrap cache, normalization, RDAP record parsing
backend/porkbun.tsPer-name availability check, price-list cache, suffix matching
backend/files.tsStatic file helpers, with a local-disk fallback
frontend/The page: HTML, CSS, and the renderer
shared/types.tsThe API response contracts

Running it

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.