# Product Name Availability Checker - Domain, npm, GitHub, PyPI (`neverempty/product-name-availability-checker`) Actor

For founders, developers and AI agents naming a product or package: one call checks a name on domains (.com .io .ai .dev .app), npm, PyPI, crates.io, RubyGems, NuGet, Homebrew, Docker Hub, GitHub, Hugging Face, YouTube, Bluesky and Discord: taken, available or unknown, never available when unsure.

- **URL**: https://apify.com/neverempty/product-name-availability-checker.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Developer tools, AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.50 / 1,000 name checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Product Name Availability Checker - Domain, npm, GitHub, PyPI

**For AI agents and people naming a product, package or company: one call tells you, for each name, whether it is taken, available, not allowed or could not be checked on domains (.com .io .ai .dev .app or any TLD you choose), npm, PyPI, crates.io, RubyGems, NuGet, Homebrew, Docker Hub, GitHub, Hugging Face, YouTube, Bluesky and Discord.** One row per name, with the exact form checked at each place and the rule that place applies. A place that could not be checked is reported as `unknown` with the reason - never as available.

Unofficial. It asks each registry's public endpoint the same question you would ask by hand. Not affiliated with any of the services named here.

***

### Example

Input:

```json
{ "names": ["neverempty", "quietharbor7319"], "tlds": ["com", "io", "ai", "dev", "app", "co", "de", "org"] }
```

Output of a production run on 2026-09-25 (one row per name; long fields shortened with "..."):

```json
[
  {
    "name": "neverempty",
    "availableEverywhere": false,
    "summary": "\"neverempty\" is available on 11 of 20 places; taken on neverempty.com, neverempty.app, neverempty.co, neverempty.de, neverempty.org, npm, github, youtube, bluesky.",
    "availableOn": ["neverempty.io", "neverempty.ai", "neverempty.dev", "pypi", "crates", "rubygems", "nuget", "homebrew", "dockerHub", "huggingFace", "discord"],
    "takenOn": ["neverempty.com", "neverempty.app", "neverempty.co", "neverempty.de", "neverempty.org", "npm", "github", "youtube", "bluesky"],
    "unknownOn": [],
    "npm": "taken",
    "github": "taken",
    "discord": "available",
    "domains": [
      { "domain": "neverempty.com", "status": "taken", "method": "rdap", "registrar": "Register.com - Network Solutions, LLC", "registeredOn": "2009-10-01T18:42:18Z", "expiresOn": "2027-10-01T18:42:18Z" },
      { "domain": "neverempty.io", "status": "available", "method": "rdap", "caveat": "The registry has no registration for this domain and DNS has no delegation for it. That does not guarantee you can buy it: ..." }
    ],
    "checks": [
      { "place": "npm", "status": "taken", "checkedAs": "neverempty", "url": "https://www.npmjs.com/package/never-empty", "httpStatus": 404, "conflictsWith": "never-empty",
        "reason": "No package is called \"neverempty\", but \"never-empty\" exists and npm refuses a new name that matches an existing one once \"-\", \".\" and \"_\" are removed",
        "rule": "npm: lowercase letters, digits, \"-\", \".\" and \"_\"; ..." }
    ]
  },
  { "name": "quietharbor7319", "availableEverywhere": true, "availableCount": 20, "unknownCount": 0, "...": "..." }
]
```

`neverempty` shows why a plain "does the page exist" check is not enough: there is no npm package called `neverempty`, but `never-empty` exists and npm refuses a new name that differs only by punctuation. This Actor reports that name as **taken** and says which existing package blocks it.

### The four answers

| `status` | Meaning |
|---|---|
| `taken` | Someone has it - or the place refuses it because it is too close to an existing name (`conflictsWith` names that one). |
| `available` | Nothing with that name was found. Every `available` carries a `caveat` saying what the place may still refuse (premium or reserved domains, look-alike rules, names a service keeps for itself). |
| `invalid` | The name breaks that place's naming rules or is reserved there (too short for Docker Hub, a hyphen on Docker Hub, a Python standard-library module name on PyPI, a Node.js core module name on npm, `std` on crates.io, Discord's own reason). |
| `unknown` | No trustworthy answer: rate-limited after the retries, a verification page, an answer that could not be read. The `reason` says which. |

`availableEverywhere` is `true` only when every place said `available`, `false` when any place said `taken` or `invalid`, and `null` when the rest were available but something was `unknown`.

### Places and how each one is checked

| Place | Checked with | Name rule applied (also in each row's `rule`) |
|---|---|---|
| Domains | The registry's RDAP service, found through IANA's official bootstrap file (`data.iana.org/rdap/dns.json`); .io .me .sh .ac and .de from their registries' RDAP; .co .us .gg .cc .so .ly (no RDAP) from the registry's WHOIS; DNS delegation (NS records) as a cross-check | one label: letters, digits, hyphens; non-Latin names converted to punycode |
| npm | `registry.npmjs.org/<name>` and every name that differs by one `-`, `.` or `_` | lowercase; npm refuses names equal to an existing one once punctuation is removed; Node.js core module names refused |
| PyPI | `pypi.org/rss/project/<name>/releases.xml` (PyPI normalises the name itself), and the names that differ by one separator | PEP 503 normalisation; standard-library module names refused |
| crates.io | the crates.io index (`index.crates.io`), every `-`/`_` combination | `-` and `_` are the same name on crates.io; reserved names (std, core, alloc, test, con, nul ...) |
| RubyGems | `rubygems.org/api/v1/gems/<name>.json`, then RubyGems' search for the same name in any upper/lower case | letters, digits, `-` `.` `_`; names are unique regardless of case (`RedCloth` blocks `redcloth`) |
| NuGet | `api.nuget.org/v3-flatcontainer/<id>/index.json` | case-insensitive IDs |
| Homebrew | formula and cask JSON on `formulae.brew.sh`, and homebrew-core's alias list | official repositories only; an alias counts as taken (`python` is an alias of `python@3.14`, `stripe` of `stripe-cli`) |
| Docker Hub | `hub.docker.com/v2/users/<name>` (organizations redirect there too) | 4 to 30 lowercase letters and digits |
| GitHub | the page `github.com/<name>` (users, organizations, and GitHub's own pages such as `/settings`) | up to 39 letters, digits, single hyphens |
| Hugging Face | users and organizations API | letters, digits, single hyphens |
| YouTube | `youtube.com/@<handle>` | 3 to 30 characters |
| Bluesky | `<name>.bsky.social` handle resolution | 3 to 18 letters, digits, hyphens |
| Discord | the username check used by Discord's sign-up page | Discord's own answer and reason |

Not covered: X/Twitter, Instagram, TikTok and Reddit (sign-in walls, robots.txt or no reliable signal), GitLab (its robots.txt disallows the API), Go modules (no name registry - a module path is your own domain or repository), Chrome Web Store (extension names are not unique), trademarks.

### Domains: what "available" means

`available` means the registry's RDAP service has no registration **and** DNS has no delegation for the domain (or, for .co .us .gg .cc .so .ly, the registry's WHOIS says it does not exist). It does not guarantee you can buy it at the normal price: registries hold back reserved names, charge premium prices for some, and keep recently deleted domains in a grace period. Every available domain row says so in `caveat`. Registered domains come with `registrar`, `registeredOn` and `expiresOn` when the registry publishes them.

A TLD with neither RDAP nor a recognised WHOIS answer (for example .jp or .to) is `taken` when DNS delegates it and `unknown` otherwise - no delegation alone does not prove a domain is free.

### Watch mode

Set `onlyChanges: true` (and a `watchName` per list) and schedule the run. The first run returns every name. After that a row comes back only when a place's answer changed - for example a domain or a handle that became free, or a name somebody just took - with `changes: [{ "place": "npm", "from": "available", "to": "taken" }]`. A place that could not be checked in one run is never reported as a change; a place that gets its first answer (because you added it, or it could not be checked last time) is reported with `"from": null`. Do not run two overlapping schedules with the same `watchName` (the memory is merged, but Apify's key-value store has no atomic update).

### Input

| Field | Default | What it does |
|---|---|---|
| `names` | - | Names to check, one per line (commas also split). Up to 500 per run. Duplicates (ignoring case and a leading `@`) are checked once. |
| `name` | - | One name, for tools that send a single value. |
| `tlds` | com, io, ai, dev, app | Domain endings to check, up to 20. |
| `places` | all | Which of domains, npm, pypi, crates, rubygems, nuget, homebrew, dockerHub, github, huggingFace, youtube, bluesky, discord to check. |
| `spaces` | remove | `remove` (Never Empty -> neverempty) or `hyphen` (never-empty). |
| `onlyChanges` | false | Watch mode (see above). |
| `watchName` | default | Separate memory per watch list. |
| `resetMonitoringState` | false | Forget this watch and return every name again. |

If `names` and `name` are both empty, the example names `stripe` and `quietharbor7319` are checked.

### Output columns

| Column | Meaning |
|---|---|
| `name` / `checkedName` | The name as you typed it / after the spaces rule. |
| `status` | `ok` on charged rows. Free rows: `incomplete`, `bad-input`, `duplicate`, `budget-reached`, `not-checked`, `no-change`. |
| `availableEverywhere` | See above. |
| `availableOn`, `takenOn`, `invalidOn`, `unknownOn` | Place names (domains appear as the full domain). |
| `availableCount` ... `unknownCount`, `placesChecked` | Counts. |
| `summary` | One sentence an agent can quote. |
| `npm` `pypi` `crates` `rubygems` `nuget` `homebrew` `dockerHub` `github` `huggingFace` `youtube` `bluesky` `discord` | The answer for that place, or `null` if it was not asked. |
| `domains` | One entry per domain: `domain`, `status`, `method` (rdap, dns, whois), `registrar`, `registeredOn`, `expiresOn`, `reason`, `caveat`. |
| `checks` | Every check: `place`, `status`, `checkedAs`, `url`, `httpStatus`, `conflictsWith`, `reason`, `caveat`, `rule`. |
| `changeType`, `changes`, `previousCheckedAt`, `watchName` | Watch mode only. |
| `note` | Free rows: why, and that nothing was charged. |
| `checkedAt` | When the answer was put together (UTC). |

### Speed

Measured on Apify on 2026-09-25 with the default 256 MB of memory and the default places and TLDs (17 places per name):

| Run | Time |
|---|---|
| 2 names (the example input) | 8.4 s |
| 6 names x 8 TLDs (20 places each) | 14.1 s |
| 100 names | 275 s - about 2.8 s per name, 3,987 requests, 1,698 of 1,700 place answers definite (the 2 unknown were YouTube handles that YouTube redirects to other pages) |

The pace is set by the slowest registry you ask: Google Registry's RDAP (.dev and .app) answers about one query a second. Leave .dev and .app out of `tlds` for faster bulk runs.

### Pricing

- **Name checked**: one row per name, all the places you asked for.
- **Run start**: once per run that returned or re-checked at least one name.
- **Name re-checked** (watch mode): a name whose answers did not change - no row is returned, and it costs much less than a name.

Free: a name for which fewer than 75% of the places gave an answer (you still get the row, marked `incomplete`), bad input, duplicates, and anything left out because of the run's maximum charge. A run whose maximum charge cannot cover the start fee and one name checks nothing and costs nothing.

### Manners

Each registry is asked at a steady pace with a gap per host that keeps inside its published limits (Docker Hub 180 requests a minute, Hugging Face 500 per 5 minutes, RubyGems 10 per second, crates.io's crawler policy, Google Registry's RDAP about one a second). A 429 is retried after the wait the service asks for, then from another Apify datacenter address. Verification pages are never solved or bypassed - that place becomes `unknown`. WHOIS is asked only when DNS shows no delegation, at most one query per 1.5 seconds per registry. Discord's check uses the endpoint its sign-up page uses, which Discord's robots.txt lists under `Disallow: /api/`; it is sent without any account or token, and a CAPTCHA is never solved.

### Support

Something wrong, or a place you want added? Open an issue in the Issues tab.

# Actor input Schema

## `names` (type: `array`):

One product, company or package name per line (or several separated by commas). Each name gets one row with an answer for every place below. Upper/lower case and a leading @ are handled per place; spaces inside a name follow the Spaces setting. The same name is checked and charged once. Up to 500 names per run. If this and the single name field are both empty, the example names stripe and quietharbor7319 are checked.

## `name` (type: `string`):

One name, for agents and tools that send a single value. It is checked together with anything in the list above.

## `tlds` (type: `array`):

Which domains to check for each name, without the dot (com, io, ai, dev, app, org, net, co, de, xyz ...). Up to 20. Answered from the registry's RDAP service; .co .us .gg .cc .so .ly, which publish no RDAP, from the registry's WHOIS. A TLD with neither is answered from DNS only (taken when delegated, otherwise unknown).

## `places` (type: `array`):

Which places to check. Leave all selected for the full answer. domains = the TLDs above.

## `spaces` (type: `string`):

What to do with spaces in a name such as "Never Empty": remove them (neverempty) or turn them into hyphens (never-empty). The row shows the exact form checked at each place (checkedAs).

## `onlyChanges` (type: `boolean`):

Return a row only for names whose answer changed since the last run of this watch (for example a domain or handle that became free). The first run returns every name. A name with no change returns no row and is charged the small re-check fee instead of the name price. A place that could not be checked is never reported as a change.

## `watchName` (type: `string`):

Keeps separate memories for separate watch lists (letters, digits, ".", "\_" and "-"). Do not run two overlapping schedules with the same watch name.

## `resetMonitoringState` (type: `boolean`):

Clear what this watch remembers, so the run returns every name again.

## Actor input object example

```json
{
  "names": [
    "neverempty",
    "quietharbor7319"
  ],
  "tlds": [
    "com",
    "io",
    "ai",
    "dev",
    "app"
  ],
  "places": [
    "domains",
    "npm",
    "pypi",
    "crates",
    "rubygems",
    "nuget",
    "homebrew",
    "dockerHub",
    "github",
    "huggingFace",
    "youtube",
    "bluesky",
    "discord"
  ],
  "spaces": "remove",
  "onlyChanges": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

## `results` (type: `string`):

One row per name: availableEverywhere, the places where it is available, taken, not allowed or could not be checked, a one-sentence summary, one column per place (npm, pypi, crates, rubygems, nuget, homebrew, dockerHub, github, huggingFace, youtube, bluesky, discord), the domains with registrar and expiry, and every check with the exact form checked, the rule applied, the HTTP status and the reason. Input that is not a name, repeated names, names with too few answers and anything cut short by the charge limit come back as free rows with a note.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "names": [
        "neverempty",
        "quietharbor7319"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/product-name-availability-checker").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "names": [
        "neverempty",
        "quietharbor7319",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/product-name-availability-checker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "names": [
    "neverempty",
    "quietharbor7319"
  ]
}' |
apify call neverempty/product-name-availability-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/product-name-availability-checker"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/1Q1yicQSiwz9LzzPC/builds/avz1o1l6f8ouP3rNE/openapi.json
