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

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

Pricing

from $10.50 / 1,000 name checkeds

Go to Apify Store
Product Name Availability Checker - Domain, npm, GitHub, PyPI

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

NeverEmpty

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

20 days ago

Last modified

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

statusMeaning
takenSomeone has it - or the place refuses it because it is too close to an existing name (conflictsWith names that one).
availableNothing 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).
invalidThe 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).
unknownNo 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

PlaceChecked withName rule applied (also in each row's rule)
DomainsThe 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-checkone label: letters, digits, hyphens; non-Latin names converted to punycode
npmregistry.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
PyPIpypi.org/rss/project/<name>/releases.xml (PyPI normalises the name itself), and the names that differ by one separatorPEP 503 normalisation; standard-library module names refused
crates.iothe crates.io index (index.crates.io), every -/_ combination- and _ are the same name on crates.io; reserved names (std, core, alloc, test, con, nul ...)
RubyGemsrubygems.org/api/v1/gems/<name>.json, then RubyGems' search for the same name in any upper/lower caseletters, digits, - . _; names are unique regardless of case (RedCloth blocks redcloth)
NuGetapi.nuget.org/v3-flatcontainer/<id>/index.jsoncase-insensitive IDs
Homebrewformula and cask JSON on formulae.brew.sh, and homebrew-core's alias listofficial repositories only; an alias counts as taken (python is an alias of python@3.14, stripe of stripe-cli)
Docker Hubhub.docker.com/v2/users/<name> (organizations redirect there too)4 to 30 lowercase letters and digits
GitHubthe page github.com/<name> (users, organizations, and GitHub's own pages such as /settings)up to 39 letters, digits, single hyphens
Hugging Faceusers and organizations APIletters, digits, single hyphens
YouTubeyoutube.com/@<handle>3 to 30 characters
Bluesky<name>.bsky.social handle resolution3 to 18 letters, digits, hyphens
Discordthe username check used by Discord's sign-up pageDiscord'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

FieldDefaultWhat 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.
tldscom, io, ai, dev, appDomain endings to check, up to 20.
placesallWhich of domains, npm, pypi, crates, rubygems, nuget, homebrew, dockerHub, github, huggingFace, youtube, bluesky, discord to check.
spacesremoveremove (Never Empty -> neverempty) or hyphen (never-empty).
onlyChangesfalseWatch mode (see above).
watchNamedefaultSeparate memory per watch list.
resetMonitoringStatefalseForget this watch and return every name again.

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

Output columns

ColumnMeaning
name / checkedNameThe name as you typed it / after the spaces rule.
statusok on charged rows. Free rows: incomplete, bad-input, duplicate, budget-reached, not-checked, no-change.
availableEverywhereSee above.
availableOn, takenOn, invalidOn, unknownOnPlace names (domains appear as the full domain).
availableCount ... unknownCount, placesCheckedCounts.
summaryOne sentence an agent can quote.
npm pypi crates rubygems nuget homebrew dockerHub github huggingFace youtube bluesky discordThe answer for that place, or null if it was not asked.
domainsOne entry per domain: domain, status, method (rdap, dns, whois), registrar, registeredOn, expiresOn, reason, caveat.
checksEvery check: place, status, checkedAs, url, httpStatus, conflictsWith, reason, caveat, rule.
changeType, changes, previousCheckedAt, watchNameWatch mode only.
noteFree rows: why, and that nothing was charged.
checkedAtWhen 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):

RunTime
2 names (the example input)8.4 s
6 names x 8 TLDs (20 places each)14.1 s
100 names275 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.