Skip to main content
Glama
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