Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XStQKxPfEjrTvWo83KFCxG
names.poweredbyotm.com
Free business-name finder: type a few keywords, get brandable name ideas, and see which domains are actually free — checked against the registries themselves.
- Stack: Next.js 15 (App Router,
output: "standalone"), React 18, no DB, no auth, public. Name generation runs client-side; availability is a server route because browsers can't do DNS and RDAP servers send no CORS headers. - Repo:
tonym/namesongit.poweredbyotm.com. - Deploy: OTM platform — first-party app (
/apps), built + deployed via the platform MCP, not docker-compose-from-this-repo.
How availability works — and what it costs (nothing)
There is no paid domain API here and no key to manage.
- RDAP (RFC 7482), the registries' own replacement for WHOIS. IANA
publishes a bootstrap file at
data.iana.org/rdap/dns.jsonmapping every TLD to its authoritative RDAP server. We cache it for 24h, resolve the TLD ourselves, and query that server directly —404means unregistered,200means registered. Covers ~1,200 TLDs including.com/.net/.org/.ai/.dev/.app. - DNS
NSlookup, for the TLDs that publish no RDAP server at all —.io,.co,.me,.sh,.gg,.us,.de. NXDOMAIN strongly suggests unregistered, but a registered domain with no delegated nameservers looks identical. These reportunverified-availableand the UI shows them in amber as probably free, never as a confirmed green.
Do not "simplify" this by pointing everything at rdap.org. Its redirector
returns a bare 404 both for "this domain is free" and for "I have no RDAP
server for this TLD" — the two cases are indistinguishable from the status code,
which is exactly the bug that would report every .io as available.
Manners, since we're an anonymous client against other people's public endpoints: 60 domains max per request, 8-wide concurrency, a per-IP token bucket, and a 10-minute result cache.
Name generation
src/lib/generate.ts is pure and deterministic — the same
(keywords, style, seed) always yields the same list, so re-renders don't
reshuffle results and a shared URL reproduces what the sender saw.
The finder shows 36 names per page and Show me more appends: the names
already on screen are passed as exclude, dropped before ranking, so each page
is genuinely new (the seed alone would mostly reshuffle the same pool), and only
the new domains get looked up. Changing keywords, style or extensions starts a
fresh list; start over clears it. When the pool runs dry the app says so.
Eight strategies (compound, suffix-word, action, blend, coined, clipped, root,
domain hack) over the vocabulary in src/lib/wordbank.ts. The strategies are
cheap and dumb on purpose — quality comes from scoreName, so tune the
scorer before adding more words.
Three guards exist because of specific bad output, and removing them brings it straight back:
expandKeywordsmatches category hints by prefix, never substring — plain containment letrepairmatch theaicategory (rep-ai-r) and drag neural-network vocabulary into an auto-shop brief.syncopateonly drops the schwa from a final-er(flicker→flickr). The original "drop the last vowel" producedcrema→crem,kettle→kettl,security→securit.isTruncationrejects any candidate that is a strict prefix of a word the generator knows. This is what keeps stumps out of the results.
diversify() caps each strategy at ~28% of the returned list. Without it the
top 20 came back as eighteen coinages — scoring alone clumps badly.
AI names
Sign in with a free OTM account (the link sits next to the generate button) and
an AI names toggle appears: Claude-generated ideas are merged in ahead of the
built-in list, checked for availability like everything else. The call goes
browser → /api/ai-names → OTM's metered Anthropic gateway → Anthropic; the app
holds a per-app gateway token, never a real Anthropic key, and the route refuses
anonymous requests and caps each account at roughly 20 requests an hour. The
platform's monthly AI cap is the backstop.
There's no chat: the AI is briefed from the same keywords box and Style chip
as the built-in generator (src/lib/ai-prompt.ts is the rulebook), and its
names are merged in front of the built-in list, tagged AI, and checked like
everything else. When AI is on, a Tell the AI more field appears — tone,
words to include or avoid, audience — which is appended to that brief (capped
at 300 chars, enforced client- and server-side). The built-in generator ignores it.
Sign-in is OTM SSO: /sso/authorize?aud=app:names on the control plane hands
back a 60-second single-use ticket to /api/auth/otm-sso, which mints a small
HMAC-signed cookie (names.session). There are no user accounts in this app.
Bring-your-own AI key
Optional. /settings stores an Anthropic or Gemini key in localStorage
(names.aiKey) and the browser calls that provider directly — the key never
touches our server. Same pattern as rpo's Google Vision key. It overrides the
platform path above and works without signing in.
The anthropic-dangerous-direct-browser-access header is correct here and would
be wrong in a product that owns the key. The distinction is whose key it is.
Saved names
Every card has a ☆. Pressing it keeps the name in a Saved panel above the
results, so you can shortlist while you keep generating. The list is
localStorage only (names.wishlist, capped at 60) — there are no accounts and
this doesn't need one. Each entry snapshots the availability it last saw, and
the panel's Re-check button re-queries every saved domain (batched through
the same /api/availability route, so it obeys the same manners) and Copy
list puts a plain-text domain — status list on the clipboard.
Registrar links
src/lib/registrars.ts. Links are plain today — every affiliateCode is "".
Fill one in and HAS_AFFILIATE_LINKS flips, which turns on the footer
disclosure automatically. Keeping both in one file is deliberate: a disclosure
that can drift out of sync with whether links actually pay is worse than none.
Local development
npm install
npm run dev # http://localhost:3000
npm run typecheck
npm run build
No env vars are needed to run it. Sign-in and platform AI light up only when the
platform injects AUTH_SECRET, OTM_SSO_SECRET, ANTHROPIC_BASE_URL and
ANTHROPIC_API_KEY; without them /api/auth/session reports platformAi:false
and the UI hides the feature. Tailwind is present only to style the
shared @otm/account-panel widgets (the bottom-right feedback chat-pop) — the
app's own UI is handwritten CSS, so preflight is off and the config scans
node_modules/@otm/account-panel/src. The app is always dark, so <html class="dark"> pins the widget's dark: variants on. The suggestions lightbulb degrades to a quiet
"not configured" state unless GITEA_URL / GITEA_TOKEN / GITEA_REPO are set
(the platform injects those at deploy time).