Product Name Availability Checker - Domain, npm, GitHub, PyPI
Pricing
from $10.50 / 1,000 name checkeds
Product Name Availability Checker - Domain, npm, GitHub, PyPI
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.
Pricing
from $10.50 / 1,000 name checkeds
Rating
0.0
(0)
Developer
NeverEmpty
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
20 days ago
Last modified
Categories
Share
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:
{ "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 "..."):
[{"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.