API reference#
domainsearch answers one question, is this domain registered?, over a plain JSON API. No account, no API key, no cookies. Base URL:
https://domainsearch24.eu/api/v1
How availability is determined#
Every check runs through layered signals, ordered by cost and freshness, and never guesses. The availability status is one of:
| Status | Meaning |
|---|---|
registered |
The domain is taken. |
free |
The registry itself answered "not registered" (RDAP 404). |
unknown |
No signal was available: see the reason field. |
Each answer names its source:
| Source | Layer | Freshness |
|---|---|---|
czds |
ICANN zone files: every delegated name per covered gTLD | ≤ 24 h |
commoncrawl |
Common Crawl web-graph domain set (~120 M domains) | months |
dns |
NS delegation probe: works for every TLD | live |
rdap |
The registry's own RDAP answer: the only source that can assert free |
live |
policy |
The label violates the TLD's own documented length rules (e.g. below the registry's minimum): classed registered/taken without any network lookup |
n/a |
Two important consequences:
freeis only assertable where RDAP exists. Some ccTLDs (e.g..eu) run no public RDAP; their unregistered domains stay honestlyunknown. Use/tldsto know upfront.freeis a moment-in-time registry answer, not a reservation.
GET /check#
GET /api/v1/check?domains=example.com,example.io[&fast=1]
Checks up to 30 comma-separated domains and returns all results in one
response (extra domains beyond 30 are dropped). Domains must be registrable
names in punycode (IDNs are not auto-encoded). Whole-batch budget: ~9 s;
slow registries yield per-domain unknown and retries are cheap (availability results
are cached server-side: registered 24 h, free 1 h, unknown 10 min).
fast=1 skips RDAP and answers from the local layers only: milliseconds,
but it can assert registered, never free. Use it to pre-filter
candidate lists before confirming the survivors with a full check (or use
streaming and get both in one request).
curl 'https://domainsearch24.eu/api/v1/check?domains=example.com,zqhx.dev'
{
"results": [
{"domain": "example.com", "status": "registered", "source": "czds"},
{"domain": "zqhx.dev", "status": "free", "source": "rdap"}
]
}
GET /check/{domain}#
The curl-friendly single-domain form. Same semantics, bare result object.
curl https://domainsearch24.eu/api/v1/check/example.com
{"domain": "example.com", "status": "registered", "source": "czds"}
GET /check/stream#
GET /api/v1/check/stream?domains=example.com,example.io[&fast=1]
Same input as /check, but the response is NDJSON
(application/x-ndjson): one result object per line, written as each
domain resolves. Local-layer answers arrive in milliseconds while RDAP
confirmations trail in over a few seconds: ideal for progressive UIs.
Line order is completion order, not input order; match on domain. The
stream ends when every domain has answered (budget ~15 s).
curl -N 'https://domainsearch24.eu/api/v1/check/stream?domains=a.com,b.io,c.dev'
GET /bulk/stream#
GET /api/v1/bulk/stream?label=acme[&confirm=1][&tlds=com,io,dev][&group=<TLD group>]
The bulk search behind the landing page: ONE label checked across every registerable delegated TLD (~937 domains by default; see the TLD groups) on a single NDJSON stream.
By default only the fast local layers run (zone files, Common Crawl,
DNS): seconds for the whole TLD space, no registry traffic. A clean local
miss classes as free: no zone entry, no crawl entry, NXDOMAIN. Its
strict status stays unknown: only a registry can formally assert
free: and the registrar checkout is the final word either way.
tlds=com,io,dev restricts the sweep to a comma-separated subset
(unknown TLDs are ignored; it wins over group -- naming a TLD directly
is unambiguous intent). DNS probe outcomes are cached server-side for
24 hours, so repeated sweeps are effectively instant.
group= sweeps one node of the TLD group hierarchy
instead of the default registerable: e.g. group=open skips every
community-gated TLD, group=restricted-geographic checks just the
country-gated ones, group=all includes even closed TLDs. Every line
carries restricted/restriction_kind regardless -- the group only
controls which TLDs are swept at all.
confirm=1 adds a second phase: RDAP for the still-unknown names on
RDAP-capable TLDs, popular TLDs first, until the ~90 s budget runs out.
Upgrade lines stream with the same domain; a confirm line supersedes
the local one (upgrading assumed-free to registry-confirmed free, or
catching the rare registered-but-undelegated name).
Each line adds a class refining the raw status for filtering:
| class | Meaning |
|---|---|
taken |
Registered. |
free |
No registration found (clean local miss), or registry-confirmed when status is free. |
premium |
Registered with registry status reserved: typically premium/registry-held inventory. |
aftermarket |
Registered, but delegated to a known parking/for-sale marketplace nameserver (heuristic). |
unknown |
No signal either way: e.g. the DNS probe failed, or confirmation is pending. |
class/status are pure availability. Separately, every result also
carries restricted (bool) and, when true, restriction_kind
(geographic or eligibility): a restricted TLD can be genuinely free
by these fields and still require the buyer to qualify for it (a real bank
for .bank, a Swiss company for .swiss, ...). Never treat restricted
as a taken/free fact -- it's a separate "you may not be eligible" caveat.
The stream opens with {"label":"acme","total":937} and closes with
{"done":true,"confirm":…,"queued":…,"confirmed":…,"truncated":…}:
truncated: true means the confirmation budget ran out before every
queued name was verified.
curl -N 'https://domainsearch24.eu/api/v1/bulk/stream?label=acme'
Bulk searches are rate limited per IP as whole searches (they already fan out server-side); API keys lift the limit.
GET /tlds#
GET /api/v1/tlds[?group=<TLD group>]
Delegated root-zone TLDs (per IANA) with capability flags; by default the registerable ones (~937).
{"tlds": [{"tld": "com", "rdap": true, "zone": true}, ...]}
rdap: an RDAP authority is known, sofreeis assertable.zone: a zone-file index is loaded, soregisteredanswers are complete to within ~24 h without any registry lookup.restricted(omitted when false): gated to a specific eligible community -- still genuinely registerable, just not by everyone.restriction_kind(only whenrestrictedis true):geographicoreligibility; see the hierarchy below.closed(omitted when false) andclosed_kind(brand,government,discontinued): no public registration path; only listed under theallandclosed*groups.
Cacheable for an hour; the underlying sources change at most daily.
TLD groups#
group= on /bulk/stream and /tlds
selects one node of the registrability hierarchy (a parent contains
everything its children contain); the web UI's TLD selector and the MCP
tools' tlds_group speak the same tokens:
| group | Contains |
|---|---|
all |
Every delegated TLD in the DNS root, even ones no one can register. |
registerable |
The default. Every TLD with a public registration path: open + restricted. |
open |
Anyone can register: no eligibility requirements. |
restricted |
Registerable, but gated to a specific eligible community. |
restricted-geographic |
Requires residency, citizenship, or a registered company in a specific country (.swiss, .dubai, ...). |
restricted-eligibility |
Requires a verified license, membership, or institutional status (.bank, .museum, .travel, ...). |
closed |
No public registration path at all. |
closed-brand |
Single-company dot-brand gTLDs (.google, .bmw, ...). |
closed-government |
State, military, treaty and internet-infrastructure TLDs (gov, mil, int, arpa). |
closed-discontinued |
No longer accepting new registrations (.tel). |
A restricted TLD can be genuinely free and still require the buyer to
qualify (a real bank for .bank, a Swiss company for .swiss); never
treat restriction as a taken/free fact. An invalid group value is a 400.
GET /status#
Layer readiness and uptime: also our health check:
{
"ok": true,
"uptime_s": 86400,
"layers": {
"dns": {"enabled": true, "ready": true},
"rdap": {"enabled": true, "ready": true},
"czds": {"enabled": true, "ready": true, "detail": "853 zones indexed"},
"commoncrawl": {"enabled": true, "ready": true}
}
}
Errors#
Errors use one envelope, always JSON:
{"error": {"code": "rate_limited", "message": "anonymous rate limit exceeded; retry later or use an API key"}}
| HTTP | code | When |
|---|---|---|
| 400 | invalid_request |
No domains given. |
| 404 | not_found |
Unknown endpoint. |
| 429 | rate_limited |
Anonymous allowance exhausted: honor Retry-After. |
| 501 | unavailable |
Checking is not configured on this deployment. |
Malformed domains are not request errors: they come back as per-domain
unknown results with a reason, so one bad name never fails a batch.
Rate limits & keys#
Anonymous use is limited per IP, per domain checked (a 30-domain batch costs 30 units): 120 domains/minute with burst headroom of twice that. That is plenty for interactive use.
Calling the API from a service? An API key lifts the limit: request one
at liam@domainsearch24.eu and send it as
Authorization: Bearer <key>.
API access is governed by the terms of service: in short, use the results in your own product freely, but don't resell access, redistribute the data, or build a competing domain-search service on it.
CORS & OpenAPI#
The API sends Access-Control-Allow-Origin: *: call it straight from the
browser. A machine-readable spec is at
/api/openapi.yaml; this page is also served as plain
markdown at /docs.md for LLM agents (see /llms.txt).