domainavail
README.md
# domainavail
You have a list of names you like. Which of them could you
actually register? domainavail answers that for a whole
list at once: hand it candidate names and it walks each
one through a series of public checks, tells you which
names look free, which are taken, and — for the free ones —
what they cost and where to buy them. It is strictly
read-only: it never registers, reserves or changes
anything.
## How a name is checked
Names are normalised first (scheme and path stripped,
lowercased). A name without a dot is expanded over each
configured TLD; a full domain is checked as-is. Each
expanded name then goes through a funnel:
1. **DNS delegation** — if the name has NS records it is
delegated, so it is `taken`. `ENOTFOUND` / `ENODATA`
mean not delegated; other DNS errors leave the name on
the unknown path.
2. **RDAP** — a 200 means the name is registered
(`taken`); the expiration event is reported as
`expires`, and a pending-delete status adds the note
`being deleted; may become available soon`. A 404 means
the name looks free.
3. **WHOIS fallback** — used when the TLD has no RDAP or
RDAP fails. Text matching `No match` / `NOT FOUND` /
`No Data Found` means free-looking; a record means
`taken`; a WHOIS failure means `unknown`.
4. **Registrar confirmation** — a free-looking name stays
`likely_available` unless a configured registrar
confirms it. Registrars are asked in the order
Name.com → Cloudflare → Spaceship → Porkbun, until one
answers definitively: then the name becomes `available`
(with `premium: true` when the registrar says premium,
and its price when given), `reserved`, or `taken`.
Possible statuses: `available`, `likely_available`,
`taken`, `reserved`, `unknown`.
The output is one JSON object: a `summary` with counts
per status, `results` in input order (except `saved`
domains, which are marked `saved: true` and listed
first), a `skipped` list for inputs that were not domain
names or that fell over the expansion cap, and
`warnings`. With `details: true` each result also
carries `evidence` — the per-step record of what DNS,
RDAP, WHOIS and the registrar said.
## Configuration
All configuration is via `DOMAINSCOUT_*` environment
variables:
- `DOMAINSCOUT_TLDS` — TLDs for expanding bare names
(default `com,net,ai`). The per-call `tlds` argument
overrides this.
- `DOMAINSCOUT_REGISTRARS` — comma list filtering which
registrars are used for offers and confirmation.
- `DOMAINSCOUT_MAX_CONCURRENCY` — parallel checks
(default 10).
- `DOMAINSCOUT_PER_HOST_CONCURRENCY` — default 2. Parsed
but not separately enforced in this rebuild (a labelled
simplification).
- `DOMAINSCOUT_TIMEOUT_MS` — per-request timeout
(default 10000).
- `DOMAINSCOUT_MAX_DOMAINS` — expansion cap (default 500,
hard max 10000). Excess names go to `skipped` with the
reason `over DOMAINSCOUT_MAX_DOMAINS`.
- `DOMAINSCOUT_CONFIRM_MAX` — most names confirmed with
registrars per call (default 50).
- `DOMAINSCOUT_DNS` — DNS step on/off (default on), and
`DOMAINSCOUT_DNS_SERVERS` — custom DNS servers.
- `DOMAINSCOUT_PRICES` — price offers on/off (default on).
- `DOMAINSCOUT_AFFILIATE` — accepted (default on); buy
links here are plain links and carry no affiliate
templates.
- Registrar keys: `DOMAINSCOUT_NAMECOM_USERNAME` /
`DOMAINSCOUT_NAMECOM_TOKEN`,
`DOMAINSCOUT_CLOUDFLARE_ACCOUNT_ID` /
`DOMAINSCOUT_CLOUDFLARE_API_TOKEN`,
`DOMAINSCOUT_SPACESHIP_API_KEY` /
`DOMAINSCOUT_SPACESHIP_API_SECRET`,
`DOMAINSCOUT_PORKBUN_API_KEY` /
`DOMAINSCOUT_PORKBUN_SECRET_KEY`. Each registrar's keys
are sent only to that registrar.
Prices come from the Porkbun public pricing API and a
Cloudflare pricing mirror, ordered cheapest first. An
offer is `{registrar, price, renewal?, url}`.
### Endpoint provenance
The RDAP lookup uses `https://rdap.org/domain/<domain>`
(endpoint choice labelled). The registrar confirmation
endpoints and the two price-source URLs are
reconstructions, labelled as such; the Cloudflare search
URL `https://www.cloudflare.com/domains/search/?q=<domain>`
is the documented example, and the Porkbun checkout URL
is our rendering of the same idea.
## Tools
- `check_domains` — checks whether domain names are free
to register, with prices and where to buy the free
ones. Arguments: `domains` (required array of strings,
1–1,000 entries), `tlds` (optional array of strings),
`confirm` (optional boolean, default true), `details`
(optional boolean, default false), `saved` (optional
array of strings). Validation fails with
`domains must be an array of 1 to 1000 names`; entries
that are empty after normalising go to `skipped` with
the reason `not a domain name`.
## CLI
```bash
npx @reneturcios/domainavail check_domains '{"domains":["example","widget.com"],"details":true}'
npx @reneturcios/domainavail --version
```
Output is the tool's result text (the JSON described
above). Exit codes: `0` on success, `1` on a runtime
error (including validation), `2` on a usage error.
## MCP server
```bash
npx -y @reneturcios/domainavail mcp
```
MCP client config:
```json
{
"mcpServers": {
"domainavail": {
"command": "npx",
"args": ["-y", "@reneturcios/domainavail", "mcp"]
}
}
}
```
## Docker
```bash
docker run -i ghcr.io/cloudcorprecords/domainavail
docker run --rm ghcr.io/cloudcorprecords/domainavail check_domains '{"domains":["widget.com"]}'
```
## Development
```bash
npm install
npm run build # tsc -> dist/
npm test # tsx --test test/*.test.ts (injected fakes only, no network)
npm run dev # run the CLI from source
```
## License
MIT — Copyright (c) 2026 Rene Turcios
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues