Tools
Reference for the nine tools the Revised MCP server exposes — arguments, return shapes and refusal codes.
Nine tools, listed in the order an agent should reach for them. Only reveal_domain can spend; everything else is free within the rate limit.
Tool results are JSON, returned as a single text content block. A tool that ran and refused returns a result with isError: true and this shape:
{
"error": {
"code": "QUOTA_EXHAUSTED",
"message": "Your reveals for this month are used up.",
"resets_at": "2026-10-01T00:00:00.000Z"
}
}code is UPPER_SNAKE_CASE and is what to branch on. Transport-level failures — rate_limited, insufficient_scope, invalid_token — arrive instead as an HTTP status with no tool result at all; see MCP authentication.
whoami
Read-only. Free. The right first call in any session: everything else is bounded by these numbers.
Takes no arguments.
planstringfree, pro or business. The owner’s plan, resolved on every request — a lapsed subscription reads as free without anything having revoked the credential.authstringoauth if the person signed in through their browser, api_key if a key was configured.client_idstring | nullkey_prefixstring | nullrevealsobjectlimit (null means no ceiling), remaining, used_this_month, resets_at, and unlimited — the unambiguous form of limit: null, because null reads as “unknown” to a careful model and as zero to a careless one.rate_limitobjectper_minute, remaining_this_minute, and resets_at — when the current fixed minute window rolls over.holdsobjectlimit (0 means the plan cannot hold) and window_hours.sync_feedbooleansince change feed is included on this plan.{
"plan": "pro",
"auth": "oauth",
"client_id": "client_2xK9",
"key_prefix": null,
"reveals": {
"limit": 500,
"remaining": 483,
"used_this_month": 17,
"resets_at": "2026-10-01T00:00:00.000Z",
"unlimited": false
},
"rate_limit": {
"per_minute": 60,
"remaining_this_minute": 59,
"resets_at": "2026-09-22T01:48:00.000Z"
},
"holds": { "limit": 3, "window_hours": 48 },
"sync_feed": false
}The rate-limit window is fixed, not sliding, so remaining_this_minute can read the same before and after a burst simply because the minute turned over.
list_categories
Read-only. Free, and cached. The right call before the first search.
Takes no arguments.
categoriesarraycode, label, listings (how many you can see in it) and open (how many of those are on the open tier, whose names cost no reveal).countinteger{
"count": 20,
"categories": [
{ "code": "saas", "label": "SaaS", "listings": 412, "open": 138 },
{ "code": "education", "label": "Education", "listings": 297, "open": 96 },
{ "code": "fitness", "label": "Fitness", "listings": 88, "open": 31 }
]
}The vocabulary is not a closed list and the counts move, so call the tool rather than caching what it returned. The codes are Revised’s own and are not guessable from a person’s wording: a task about “marketing” or “software” maps onto codes you have to read off this list. search_domains refuses an unknown code rather than answering with an empty shelf, so a guess costs a round trip and teaches nothing.
Some listings carry no category at all. They are in the directory and every other filter reaches them, but no category search returns them, so they are in none of these counts.
search_domains
Read-only. Free and unlimited within the rate limit. It never spends a reveal, and a listing the caller is not entitled to name comes back fully populated with its metrics and a masked domain.
Arguments
querystringpathFree text over the masked name hint, keyword tags, category and blurb. Maximum 80 characters. Does not search real domain names — searching for a name you already have in mind will not find it. The tags and blurb it reads are not returned on a row, so a match cannot be explained from the response.
categorystringpathCategory code from list_categories, e.g. saas. Comma-separate for several. An unknown code is refused, not answered empty.
tldstringpathExact TLD without the dot — com, org, com.au. Comma-separate for several.
tierstringpathOne of open, regular, featured. Comma-separate for several. open names are published on the Revised website, cost no reveal and may be republished — the cheapest place to start. regular and featured are disclosed under the API terms and may not be republished.
spamstringpathSpam band ceiling: low or moderate.
min_revised_scoreintegerpath0 to 100, compared against the low end of each listing’s published range. Every listing has one, so this excludes nothing for being unknown.
min_age_yearsintegerpath0 to 100. Listings whose age is unknown never match; absence is not a zero.
min_citabilityintegerpath0 to 100 — how often sources that AI assistants cite link to this domain. Listings with no citability row never match.
recommendedbooleanpathOnly listings that clear Revised’s own heuristic: a category of any kind (including the literal other), at least one recognisable linking site, and a Revised Score floor.
sortstringpathdefault: featuredOne of age, availability, backlinks, citability, da, featured, newest, price-asc, price-desc, rd, rs, snapshots, spam, value.
featured is the curated shelf. rs Revised Score, rd referring domains, da Domain Authority, newest most recently listed, availability most recently re-checked, spam cleanest first. value and price-desc are the same sort — estimated value, highest first — and price-asc is its reverse. Rows with a null in the sorted field go last. Estimated value and the metric bands each take few distinct values, so expect wide ties; they break on listing id, which is stable but arbitrary.
fieldsstringpathdefault: summarysummary returns the fields a shortlist is ranked on and costs roughly a third of a full row — use it for every first pass. full returns every field, which at the maximum limit is a five-figure token response.
limitintegerpathdefault: 201 to 50. Clamped at both ends rather than refused, so 0 reads as 1 and 500 as 50. Budget roughly 60 tokens a summary row and 230 a full one.
An argument outside the values listed here is refused with INVALID_ARGUMENT, naming the argument and the legal set. Nothing is silently dropped, so a filter that was accepted was applied.
Result
listingsarrayfields.countintegertotalintegertruncatedbooleancount < total. Stated rather than implied, so a short page is not read as “that is everything”.fieldsstring{
"count": 2,
"total": 91,
"truncated": true,
"fields": "summary",
"listings": [
{
"id": "00aLaXEG",
"domain": "greenfieldfarms.com",
"name_disclosed": true,
"tier": "open",
"tld": "com",
"revised_score": "61-70",
"referring_domains": "100-500",
"domain_authority": "20-30",
"citability": 47,
"age_years": 14,
"category": "education",
"spam": "low",
"availability_checked_at": "2026-09-19T08:02:14.771Z",
"metrics_missing": []
},
{
"id": "X00w46xl",
"domain": "t▓▓▓▓▓▓labs",
"name_disclosed": false,
"tier": "regular",
"tld": "com",
"revised_score": "64-70",
"referring_domains": "50-100",
"domain_authority": null,
"citability": null,
"age_years": null,
"category": "saas",
"spam": "low",
"availability_checked_at": "2026-09-20T11:40:02.118Z",
"metrics_missing": ["age_years", "agent_citability", "domain_authority"]
}
]
}On a summary row, domain carries the public partial mask when name_disclosed is false. name_disclosed is the test, not the presence of a mask: a mask is on every row whether or not anything is masked. A mask is something to show a person, never something to hand a registrar.
get_domain
Read-only. Free. One listing, every field — how you read closely the handful of rows a summary search shortlisted.
idstringpathA listing id from a search_domains result, e.g. k3mq7x2p. Eight characters, case-sensitive.
namestringpathA registrable domain name, e.g. example.com. Resolves only for a name you are already entitled to: an open-tier listing, one this account has already revealed, or — on a plan with no reveal ceiling — any listing.
Any other name reports not-found whether or not it is in the directory: Revised will not confirm that a masked name is listed. A masked row’s domain is a mask, so passing one back here will not resolve it.
Returns the full listing — the same field set the REST GET /api/v1/domains/{id} returns, including backlinks, snapshots, linked_by, agent_citability, estimated_value_cents, hold_state, hold_expires_at, mask_hint, discovered_at and updated_at.
reveal_domain
Idempotent: calling it again for the same listing is free and returns the same revealed_at. It also records the reveal, which is what hold_domain requires.
idstringpathrequiredThe listing id.
Result
The full listing with its real name, plus:
revealed_atstringwas_freebooleanreasonstring | nullwas_free is true: open_tier (the name is already public), already_revealed (bought earlier) or unlimited_plan (the plan has no reveal ceiling).remaining_revealsinteger | nullnull on a plan with no ceiling.{
"id": "00aLaXEG",
"domain": "greenfieldfarms.com",
"name_disclosed": true,
"tier": "open",
"revealed_at": "2026-09-22T01:47:10.334Z",
"was_free": true,
"reason": "open_tier",
"remaining_reveals": 483
}The response reports the cost rather than leaving it to be inferred. The REST equivalent does not: POST /api/v1/domains/{id}/reveal returns the listing plus revealed_at only.
Refusals
code | Meaning |
|---|---|
QUOTA_EXHAUSTED | The month’s allowance is gone. The refusal carries resets_at; retrying before then will not work. |
FEATURED_REQUIRES_PRO | A free plan against a curated listing. Search with tier: "open" instead. |
HELD_BY_OTHER | Another account holds it. The refusal carries no timestamp — read hold_expires_at off the listing for when it frees up. Who holds it is never disclosed. |
NOT_FOUND | No such listing, or one that has left the directory. |
hold_domain
Reserve a listing inside Revised so no other account can reveal or take it while someone decides. Costs no reveal, but requires that this account has already revealed the listing — a name disclosed by an unlimited plan is not a reveal on record.
idstringpathrequiredThe listing id.
Returns hold (id, listing_id, held_at, expires_at, can_extend, extend_hours) and holds (active, limit, hold_hours, plan).
{
"hold": {
"id": "h_01K6M2QX",
"listing_id": "00aLaXEG",
"held_at": "2026-09-22T01:50:00.000Z",
"expires_at": "2026-09-24T01:50:00.000Z",
"can_extend": true,
"extend_hours": 24
},
"holds": { "active": 1, "limit": 3, "hold_hours": 48, "plan": "pro" }
}Use it when shortlisting for someone else to approve: it is cheaper than revealing repeatedly, and it is the only way to keep a candidate from under another account.
Refusals
HOLDS_REQUIRE_PRO (the plan allows no holds), REVEAL_REQUIRED (reveal it first), HELD_BY_OTHER, HOLD_CAP_REACHED (release one first), HOLD_COOLDOWN (your own hold on this listing ended in the last 30 days; available_at says when — anybody else may hold it now), NOT_FOUND.
list_holds
Read-only. Free. This account’s holds, never anybody else’s.
Takes no arguments.
activearraydomain, because holding a listing required revealing it first.endedarrayended_at and an outcome of expired, released or registered.holdsobject | nullactive, limit, hold_hours, plan.Holds are account-scoped, not credential-scoped: every key and every OAuth session on the account sees, extends and can end the same holds.
extend_hold
Push one of your holds’ expiry back by extend_hours, the window the plan allows. Free, and it takes no slot against the cap — the hold is already yours.
hold_idstringpathrequiredA hold id from list_holds or hold_domain.
Returns hold and holds, as hold_domain does.
Once per hold. can_extend says whether the extension is still on the table; once taken it is false and ALREADY_EXTENDED is the answer to asking again. The time is added to expires_at, not to now, so an extension taken early loses nothing.
Refusals: ALREADY_EXTENDED, HOLD_OVER, HOLD_NOT_FOUND, HOLDS_REQUIRE_PRO.
release_hold
Destructive. End one of your holds early and give the listing back. Free, and it frees a slot against the cap immediately.
hold_idstringpathrequiredA hold id from list_holds or hold_domain.
outcomestringpathdefault: releasedreleased if you are giving it back, registered if you have registered the name yourself. expired is not accepted — that outcome belongs to the clock.
ended_atstringoutcomestringreleased or registered, as passed.registrationstring | nullregistered release, and only when the re-check answered: available, taken or unknown. unknown is not the same as available.holdsobjectSay registered when you have: it triggers a fresh availability check and retires the listing if the name now resolves, which keeps it from being offered to anybody else.
Argument refusals
Every tool refuses an out-of-range or out-of-enum argument with INVALID_ARGUMENT, naming the argument and listing the legal values, and INVALID_REQUEST when a required argument is missing.
{
"error": {
"code": "INVALID_ARGUMENT",
"message": "`tier` must be one of: featured, open, regular.",
"argument": "tier"
}
}Nothing is filtered, guessed or silently dropped, so fix the value and call again. A filter that was accepted was applied — which is the point: a wrong answer indistinguishable from a right one is the worst shape a tool result can take.