v1
Namester API
REST access to the Namester domain index: one search endpoint across every marketplace we track, and one lookup endpoint for a single name.
Using Claude Desktop, Cursor or another MCP client? See the MCP server.
Every count on this page comes from a single snapshot of the production database taken at 2026-09-09T15:35Z, using exact counts. The index changes by the minute, so read them as a description of that moment rather than a guaranteed size.
- Domains indexed
- 2,920,052
- Reachable via the API
- 2,594,318
- Live auctions
- 2,087,217
- Marketplaces
- 5
- Base URL
- namester.ai
Indexed is not the same as reachable. 2,920,052 rows are indexed, but every code path excludes listings whose end time has passed, so at most 2,594,318 of them can come back from any request and 325,734 rows cannot be reached at all. GET /api/v1/stats returns both numbers as they are right now.
Authentication
Every request needs an API key in the Authorization header. Create and revoke keys in your dashboard. We store only a SHA-256 hash of each key, so the full key is shown exactly once, when you create it.
Authorization: Bearer nmst_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxNever put a key in the URL. Requests carrying any of these query parameters are rejected with 400 api_key_in_query instead of being served: api_key, apikey, api-key, key, token, access_token. URLs end up in access logs, referrer headers and shell history, so a key that has travelled in a URL should be treated as leaked and revoked.
This API sends no CORS headers, deliberately. An API key embedded in browser JavaScript is a public key. Call the API from a server, a scheduled job, or a script.
Rate limits
Free accounts: 20 requests per hour (previously 60). The quota still belongs to the account and is shared by all of its API keys; paid plans are unchanged.
Limits are counted per account: all API keys on your account share one hourly quota, so creating more keys does not add quota. One runaway script therefore uses up the quota of your other keys too; revoke its key to stop it. Limits are not counted per IP address, so callers behind a shared cloud egress address are not lumped together.
| Plan | Requests per account | Window | Max rows per hour |
|---|---|---|---|
| Free | 20 | 1 h | 4,000 |
| Pro | 25,000 | 1 h | 5,000,000 |
Rows per hour is simply the request budget multiplied by the 200-row page cap. The free budget is meant to be enough for real selection and monitoring work, not for mirroring the whole index.
Responses always carry X-RateLimit-Limit, X-RateLimit-Window and X-RateLimit-Scope (always "account": the numbers describe your whole account, not the key you sent), and additionally X-RateLimit-Remaining and X-RateLimit-Reset (unix seconds) whenever we can report your live usage. Use them to back off instead of probing for 429s.
The hourly quota is counted per clock hour: it starts again from zero at the top of every hour (UTC), which is the time X-RateLimit-Reset gives. Over the budget you get 429 with a Retry-After that runs to that reset. If our rate-limit backend is itself unavailable we return 503 rather than serving the request unmetered, because an unmetered public endpoint over a multi-million-row index is the worse failure.
Endpoints
GET/api/v1/domains
Search the index. Every filter below maps onto the same query that powers the site, so results match what you would see in the web UI with the same filters applied (except that the web list hides IDN names by default). Results are paginated with a keyset cursor.
Omitting status is not the same as searching everything. With no status the endpoint returns live auctions only, meaning status=auction that has not ended yet, which was 2,087,217 of the 2,920,052 indexed rows in the snapshot above. Pass status=all to search across every state in one request.
Every status value, all included, also carries a not-yet-expired constraint: a listing whose end time has already passed is never returned. That caps any request at 2,594,318 rows and puts 325,734 expired rows out of reach. Listings with no end time at all, such as fixed-price inventory, are unaffected.
curl -s \
-H "Authorization: Bearer $NAMESTER_API_KEY" \
"https://namester.ai/api/v1/domains?status=auction&tld=com&min_score=70&sort=namester_score&order=desc&limit=50"{
"object": "list",
"data": [ { "domain_name": "…", "namester_score": 91, … } ],
"pagination": {
"page_size": 50,
"page_index": 0,
"next_cursor": "eyJ2Ijoi…",
"has_more": true
},
"total": { "value": 11177, "accuracy": "exact" },
"meta": { "api_version": "v1", "namester_score": "…" }
}The total in that response is the real count for that exact query, status=auction with tld=com and min_score=70, measured at 2026-09-09T15:35Z: 11,177 rows. It is not the size of the index, and sort, order and limit do not change it.
GET/api/v1/domains/{domain}
Look up one domain by name. Send the ASCII or punycode form; the name is lower-cased for you but not otherwise transformed.
A name that is not currently listed at any marketplace we track returns 404 domain_not_found. That is a real answer, not an error on our side: the index only holds names that are listed right now.
curl -s \
-H "Authorization: Bearer $NAMESTER_API_KEY" \
"https://namester.ai/api/v1/domains/example.com"GET/api/v1/stats
Index-wide counts, so you never have to infer the size of the index from a filtered search. Call this before deciding how to page through anything.
total counts every indexed row. live is exactly what the search endpoint returns when status is omitted. retrievable is the ceiling for status=all. by_status and by_source are whole-index counts whose keys are precisely the values you may pass to status (minus all) and to source. Counts are cached for five minutes, and as_of tells you when the batch was taken.
If any single count fails, the whole response is 503 query_failed. A failed count is never reported as zero: once it is in your parser, a zero and a measurement we could not take look identical, and you would drop a whole marketplace from your polling list on the strength of it.
curl -s \
-H "Authorization: Bearer $NAMESTER_API_KEY" \
"https://namester.ai/api/v1/stats"{
"object": "stats",
"data": {
"as_of": "2026-09-09T15:35:02.896Z",
"total": 2920052,
"live": 2087217,
"retrievable": 2594318,
"by_status": {
"auction": 2344608,
"dropping": 118359,
"available": 457085
},
"by_source": {
"namecheap": 1303218,
"godaddy": 1083827,
"dynadot": 495835,
"dropcatch": 22877,
"sedo": 14295
}
},
"meta": { "api_version": "v1", "namester_score": "…", "counts": "…" }
}Query parameters
All parameters are optional and apply to the search endpoint only. The lookup endpoint accepts none.
- An unrecognised parameter is a 400, not a silent no-op. A typo like min_scores would otherwise hand you the top page of the whole index while you believed it was filtered.
- Repeating a parameter is a 400. Keeping only the first value and dropping the rest would look like it worked.
- An empty value is treated as if the parameter were absent, so tld= behaves exactly like omitting tld.
- tld and source are lower-cased before use. They are compared for exact equality against lower-case values in the database, so tld=COM would otherwise be a perfectly valid request that matches nothing at all.
- limit accepts an integer from 1 to 200. Anything below 10 is raised to 10, which is the floor of the underlying query layer. The default is 50. The value that actually applied always comes back as pagination.page_size.
| Parameter | Type | Description |
|---|---|---|
search | string, ≤ 300 chars | Substring match on the domain name. The term is split on whitespace and every word must match, in any order. A word is rejected with 400 that names it if fewer than two characters remain once characters that cannot appear in a hostname are stripped; it is never silently ignored. Repeating a word is fine, but more than five distinct words is a 400, because dropping the sixth would widen the result set rather than narrow it. |
status | auction | dropping | available | all | Which listing state to search. Omit it and you get live auctions only (status=auction, not yet ended); pass status=all to search every state in one request. Any value, all included, still excludes listings whose end time has passed. auction is a live auction, dropping is a name heading for deletion, available is a name listed at a fixed price. |
tld | string, ≤ 63 chars | Full public suffix, which may contain dots: com, it.com, co.uk. This is the whole suffix as the Public Suffix List defines it, not the last label. The value is lower-cased for you. tld=all is a wildcard meaning no TLD filter, not a suffix named all. |
source | string, ≤ 32 chars | Marketplace identifier, lower-cased for you. See the table further down for the values present today. source=all is a wildcard meaning no source filter. |
min_price | number, ≥ 0 | Lower bound on the listing price. Read bid_count alongside it: a price with zero bids is an asking price, not a bid. |
max_price | number, ≥ 0 | Upper bound on the listing price. |
min_length | integer, 1–63 | Minimum length of the brandable part, counted after the public suffix is removed. |
max_length | integer, 1–63 | Maximum length of the brandable part. |
no_hyphens | true | false | Exclude names whose brandable part contains a hyphen. Hyphens inside a punycode suffix do not count. |
no_numbers | true | false | Exclude names whose brandable part contains a digit. |
min_score | number, 0–100 | Minimum namester_score. See the section below for what this score does and does not mean. |
max_score | number, 0–100 | Maximum namester_score. |
min_dr | number, 0–100 | Minimum Ahrefs Domain Rating. Only a minority of rows carry SEO metrics, so any bound above zero also filters out every row where the metric is unknown. Paid plans only (Pro, Max): with a free account's key the whole request is refused with 403 plan_required. |
max_dr | number, 0–100 | Maximum Ahrefs Domain Rating. Paid plans only (Pro, Max): with a free account's key the whole request is refused with 403 plan_required. |
min_tf | number, 0–100 | Minimum Majestic Trust Flow. Paid plans only (Pro, Max): with a free account's key the whole request is refused with 403 plan_required. |
max_tf | number, 0–100 | Maximum Majestic Trust Flow. Paid plans only (Pro, Max): with a free account's key the whole request is refused with 403 plan_required. |
min_backlinks | integer, ≥ 0 | Minimum backlink count. Paid plans only (Pro, Max): with a free account's key the whole request is refused with 403 plan_required. |
min_age | integer, 0–100 | Minimum domain age in years, derived from the first archived snapshot. The oldest names in the index today are 32 years old. |
min_search_volume | integer, ≥ 0 | Minimum monthly search volume for the matching keyword. Paid plans only (Pro, Max): with a free account's key the whole request is refused with 403 plan_required. |
min_word_count | integer, 0–3 | Minimum number of dictionary words that cover the brandable part. 0 means the name could not be split at all, and the observed maximum is 3. This is a filter for readable names, not a quality score: it carries no weight in namester_score. |
min_extensions | integer, ≥ 0 | Minimum number of other public suffixes on which the same brandable part is already registered. This is a demand signal, not a quality score. A value of 0 is not pushed down, because extensions_taken >= 0 would quietly discard every row where the number is unknown. Paid plans only (Pro, Max): with a free account's key the whole request is refused with 403 plan_required. |
ending_within | integer, 1–8760 | Only auctions ending within this many hours. Omitting status is fine, in which case status=auction is implied. It is only honoured for auctions, so pairing it with any other status, all included, is a 400 rather than a filter that gets quietly ignored. |
sort | domain_name | length | current_bid | auction_end_time | bid_count | buy_now_price | namester_score | Field to sort by. |
order | asc | desc | Sort direction. |
limit | integer, 1–200 | Rows per page. |
cursor | string, ≤ 512 chars | Opaque keyset cursor from the previous page. Pass it back verbatim. |
Sortable fields: domain_name, length, current_bid, auction_end_time, bid_count, buy_now_price, namester_score. Unsorted requests fall back to namester_score descending, except status=dropping which falls back to shortest name first.
Pagination
Pagination is keyset-based, not offset-based: each page returns an opaque next_cursor that you pass back verbatim to get the following page. There is no offset or page parameter, on purpose. Offset paging over millions of rows gets slower with every page and skips or repeats rows whenever the underlying data changes between requests, which for a live auction feed is constantly.
Read has_more, or check whether next_cursor is null. Do not infer the end of the results from a full page: a full page means exactly that, not that you have reached the end.
A cursor encodes the filters, sort order and limit it was created with, and it is signed. If you change any of them, or edit the cursor itself, the cursor is rejected as stale and you get the first page of the new result set rather than a page from the old one.
# 翻页:把上一页返回的 pagination.next_cursor 原样传回来
curl -s \
-H "Authorization: Bearer $NAMESTER_API_KEY" \
"https://namester.ai/api/v1/domains?status=auction&tld=com&limit=50&cursor=$CURSOR"total.value is the number of matching rows and total.accuracy tells you how it was obtained: exact is a real count. at_least means the value is a lower bound (the real number is at least this). It is returned in three cases: a search containing a term shorter than 3 characters, or a min_price / max_price filter — those counts stop after 100 matches because an exact count cannot fit the time budget (100 or fewer matches are counted exactly and reported as exact); any query with a search term or a price filter whose count did not finish in time, where the value is the number of rows on this page; and, rarely, when the count came back smaller than the rows actually returned. planned is the query planner estimate, used only for queries with neither a search term nor a price filter, as a fallback when the exact count exceeds its time budget. A planned total can be off by a wide margin and should not be shown as a precise figure.
Response fields
Both endpoints return the same domain object, with this fixed set of fields:
domain_nametldstatussourcelengthhas_hyphenshas_numbersword_countnamester_scorecurrent_bidstart_pricebuy_now_pricebid_countauction_end_timeauction_typemarketplace_urlextensions_takendomain_agevaluationspam_riskahrefs_drahrefs_backlinksmajestic_tfmajestic_cfmajestic_backlinkssemrush_authoritysemrush_backlinkskeyword_search_volumeopen_pagerankcloudflare_rankingumbrella_rankinglast_sold_pricelast_sold_yearlast_checked_atupdated_atFields we do not have a value for are returned as null rather than omitted, so the shape of every object is identical. Internal columns are never returned, which is what lets us add columns without breaking your parser.
About namester_score
namester_score ranks demand and liquidity on a 0 to 100 scale: how contested a name is, and how likely it is to find a buyer. It is computed from length, character composition, the public suffix, domain age, marketplace activity and whatever authority metrics we have for that name.
It is not a price, an appraisal or a valuation, and it must not be used as one. The separate valuation field is an estimate reported by the marketplace and is unrelated to this score. Every successful response repeats this in meta.namester_score so that the caveat travels with the data.
The highest score present in the index was 98 on 2026-09-09. The scale is defined 0 to 100, but the top of the range depends on how much authority data we have managed to enrich, so thresholds close to 100 can legitimately return nothing.
Errors
Errors always use the same envelope, and never include database messages, SQL fragments or stack traces. 4xx means the request needs to change; 5xx means the problem is on our side and retrying is worthwhile.
A failure is never dressed up as an empty result. If a query does not complete you get 503 query_failed, never HTTP 200 with an empty array. Those two things mean opposite things to your code: one should be retried, the other means you should widen your filters. A search that runs and matches nothing returns 200 with an empty data array and a total of zero.
{
"error": {
"code": "invalid_parameter",
"message": "\"min_score\" must be <= 100, got 140.",
"param": "min_score"
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | api_key_in_query | An API key was passed in the query string. The request was refused; revoke that key. |
| 400 | invalid_parameter | A parameter has an unusable value. The param field names it and the message says what is wrong. |
| 400 | unknown_parameter | A parameter is not recognised. The param field names it. |
| 400 | invalid_domain | The path segment is not a valid domain name. |
| 401 | missing_api_key | No Authorization header was sent. |
| 401 | invalid_authorization_header | The Authorization header was present but not in the form Bearer followed by a key. |
| 401 | invalid_api_key | The key is malformed, unknown, or revoked. These three cases return an identical response on purpose, so that the API cannot be used to discover which keys exist. |
| 403 | plan_required | A filter that is only available on paid plans (Pro and Max) was sent with a key that belongs to a free account. The param field names the first one, and nothing was queried. Remove it, or upgrade the account. |
| 404 | domain_not_found | That domain is not currently in the index. |
| 429 | rate_limited | Your account's hourly quota is used up. All of its keys share it, so another key will not help. Wait for the window in Retry-After. |
| 503 | key_lookup_unavailable | We could not check the key because the lookup itself failed. This says nothing about your key. Retry. |
| 503 | plan_lookup_unavailable | We could not resolve your plan, so we could not decide which rate limit applies. We refuse rather than guess, because guessing low locks out paying callers and guessing high hands out quota for free. |
| 503 | rate_limit_unavailable | Rate limiting is temporarily unavailable, so the request was refused instead of being served unmetered. |
| 503 | query_failed | The query did not complete. This is not an empty result set; your filters may well match rows. On the stats endpoint it means at least one count could not be measured, and we refuse to report an unmeasured count as zero. Retry. |
Every 5xx carries Retry-After, and 429 carries one computed from the real rate-limit window reset. Respect them.
MCP server
A small, read-only MCP server lets an AI assistant query this API for you. It runs on your own computer: your MCP client starts it as a local process and talks to it over stdio. There is no hosted MCP endpoint and no URL to paste. Every tool call is an ordinary request to this API made with your key, so it counts against your account's hourly limit, which every key on the account shares.
Tools
search_domains— search the index, using the list endpoint's parameter names (the sparsely populated SEO filters — DR, TF, backlinks, search volume — are left out, and min_extensions works only with a paid plan's key)get_domain— look up a single domain by namemarket_stats— index size by status and by source
None of the tools bids, edits a watchlist or changes anything — the server only reads.
Set it up
- Create an API key in your dashboard. A free account is enough. Manage your API keys →
- Download the server. It is a single file with no dependencies and needs Node.js 18 or later. namester-mcp.mjs
curl -fsSLo namester-mcp.mjs https://namester.ai/mcp/namester-mcp.mjs sha256sum namester-mcp.mjs # macOS: shasum -a 256 namester-mcp.mjsSHA-256 of the current file (33,341 bytes):
3852d5868a767909f41654a3f18754caf2b29534bfa4a0e4ea6cff9e3c0cb2ba - Add it to your MCP client's configuration — for example claude_desktop_config.json for Claude Desktop, or .cursor/mcp.json for Cursor. Use the absolute path of the file you downloaded:
{ "mcpServers": { "namester": { "command": "node", "args": ["/absolute/path/to/namester-mcp.mjs"], "env": { "NAMESTER_API_KEY": "nmst_live_…" } } } }Or, with Claude Code:
claude mcp add namester -e NAMESTER_API_KEY=nmst_live_… -- node /absolute/path/to/namester-mcp.mjs
The key sits in plain text in that configuration file. Treat the file like a password, and revoke the key in your dashboard if it leaks.
The tool descriptions and result text are currently written in Chinese.
Use your own AI model with Namester
Connect the MCP server above to an AI app you already use, then ask in plain language. The model runs in that app, on your own AI subscription or API bill; Namester does not run it or charge for it. Every tool call the model makes is one request to this API with your key, so it counts against your account's hourly quota: 20 requests an hour on a free account and 25,000 on Pro, shared by all of the account's keys.
The prompts below work in AI apps that can start a local MCP server, such as Claude Desktop, Claude Code and Cursor. An app that only accepts hosted (remote) MCP servers cannot use this one, because it runs on your own computer.
Copy a prompt, change the details, and paste it into your AI app.
Shortlist auctions ending today
Use the Namester tools to find .com auctions that end within the next 24 hours, with names of at most 6 characters, no hyphens or numbers, and a Namester Score of at least 60. Show the 20 with the most bids as a table: domain, current bid, number of bids, end time in UTC and marketplace. Do not estimate what any of them is worth.Check the names you are considering
For each domain below, use the Namester get_domain tool and tell me whether it is listed right now, on which marketplace, its status, price, bid count and end time. If a name is not in the index, write "not listed" instead of guessing.
brightlane.com
novahive.io
quickparcel.coLook at the market before a buying session
Call the Namester market_stats tool and summarize how many listings are live by status and by marketplace. Then search for 5-letter .ai and .io names priced at most $100 and list the 15 with the highest Namester Score, with price and end time.Brainstorm, then check what is actually for sale
Suggest 15 short, brandable .com names for a specialty coffee subscription. Then use the Namester search tools to check which of them are listed right now, and show only those, with price, status and marketplace. Say clearly that a name that is not listed may still be registered by someone.What is in the index
Exact counts from the snapshot taken at 2026-09-09T15:35Z. The index holds names that are listed right now at one of the marketplaces below; names that stop being listed are removed within a few days.
This page is a snapshot. GET /api/v1/stats computes the same numbers on demand and is always the more current of the two, so read them from there rather than quoting this page back at us.
| source | Rows |
|---|---|
namecheap | 1,303,218 |
godaddy | 1,083,827 |
dynadot | 495,835 |
dropcatch | 22,877 |
sedo | 14,295 |
- By status: 2,344,608 auction, 118,359 dropping, 457,085 available.
- Only 50,016 of the 118,359 dropping rows had a deletion time still in the future at the moment of measurement. Dropping listings expire in daily batches, so this slice legitimately shrinks to near nothing at certain hours. That is the data, not an outage.
- tld holds the full public suffix, so 13,020 rows have a suffix containing a dot, for example co.uk with 1,986 rows. Query the whole suffix, not the last label.
- 2,333,471 of 2,920,052 rows carry a price with a bid count of zero. That is a starting or asking price, not a bid. Always read bid_count next to current_bid before treating a number as evidence of competition.
- Each marketplace is re-fetched on its own schedule, from hourly to once a day. last_checked_at tells you when we last saw that specific listing, and updated_at when its row last changed.
- In this snapshot the three status counts and the five source counts each add up to exactly 2,920,052, which is how we know it was not taken in the middle of a resync. Do not rely on that holding: the counts are issued concurrently against a table the sync jobs keep rewriting, and an earlier attempt the same afternoon landed in a batch swap and did not add up.