Find domains
Filter and page the directory with GET /api/v1/domains without burning a rate-limit window.
GET /api/v1/domains is the endpoint you will spend most of your requests on. It is free and unlimited inside your rate limit — a page never charges for the rows it matched, whatever tier they are on. Filter structurally on the server, page with the cursor, then narrow locally.
This guide assumes you have a key and know what a tier is. If not, start with the Quickstart.
Filter on the server first
curl -s -G https://getrevised.com/api/v1/domains \
-H "Authorization: Bearer $REVISED_KEY" \
-d category=education \
-d tld=com \
-d spam=low \
-d rsMin=70 \
-d limit=250| Parameter | Accepts | Notes |
|---|---|---|
category | A category code, comma-separated for several | See the codes. An unknown code is refused. |
tld | An exact TLD without the dot — com, com.au | |
spam | low, moderate | |
tier | open | The only value this filter accepts. There is no way to ask for regular or featured rows specifically. |
featured | boolean | Reads the shelf rank. Not the same thing as tier == "featured". |
recommended | boolean | Listings that pass the recommended heuristic: categorised, at least one marquee linker, and a Revised Score floor. |
rsMin, rsMax | 0–100 | Bounds on the published Revised Score range. rsMin filters the lower bound. |
citabilityMin | 0–100 | Minimum Agent Citability score. |
ageMin | 0–100 | Minimum age in whole years. Rows with a null age_years never match — see below. |
checkedDays | 1–3650 | Only listings whose availability was checked within that many days. |
rd | Band ids, comma-separated: lt10, 10-50, 50-100, 100-250, 250plus | Referring-domain bands. The ids are not the values the response returns — see below. |
source | Marquee-linker display names, comma-separated, case preserved — Hacker News,Wikipedia | |
priceRange | under-100, 100-249, 250-999, 1000-2499, 2500-plus | Buckets the same figure the response reports as estimated_value_cents, so a band and the values it returns agree. |
q | 1–80 characters | Free text across the masked hint, tags, category and blurb. |
sort | latest | Omitted: ascending listing id. latest: newest discovered_at first. |
limit | 1–250, default 50 | Clamped, not rejected — see below. |
cursor | An opaque cursor from a previous response | |
since | ISO 8601 instant | Business plan only. See Incremental sync. |
Every other endpoint and parameter on this API answers on every plan.
Page with the cursor
Paging is by opaque cursor, not offset. Stop on has_more, never on an empty page, and keep sort the same across every page of a run — a cursor belongs to the ordering it came from.
import time
import requests
BASE = "https://getrevised.com"
def fetch_listings(session, max_pages=10, limit=250, pause=0.6, **filters):
"""Page /api/v1/domains with a cursor. Returns (rows, watermark)."""
rows, cursor, watermark = [], None, None
for _page in range(max_pages):
params = dict(filters, limit=limit)
if cursor:
params["cursor"] = cursor
response = session.get(f"{BASE}/api/v1/domains", params=params, timeout=30)
response.raise_for_status()
payload = response.json()
rows.extend(payload["data"])
if watermark is None:
watermark = payload["synced_at"] # first page only — see Incremental sync
if not payload["has_more"]:
break
cursor = payload["next_cursor"]
time.sleep(pause)
else:
print(f"stopped at the {max_pages}-page cap — narrow the filters or raise it")
return rows, watermarkThree details in that loop are load-bearing:
- The
max_pagescap. A bug in a filter cannot then run away with your whole rate-limit window. time.sleep(pause). Your ceiling is per key per minute: 20 on Free, 60 on Pro, 120 on Business. Atlimit=250a 0.6-second pause keeps a Free key comfortably inside its window.- The watermark comes from the first page only. It is read before any row is, so a listing updated mid-run is picked up next time rather than skipped.
The same thing in shell, one page at a time:
curl -s -G https://getrevised.com/api/v1/domains \
-H "Authorization: Bearer $REVISED_KEY" \
-d tier=open -d limit=250 \
| jq -r '.next_cursor, .has_more, (.data | length)'c7Kd0Pq1
true
250Category codes
Category is the strongest filter the directory has, and the codes are Revised’s own — marketing and software are not among them. There is no categories endpoint on the REST API, so hardcode the list. (The MCP server does expose a free list_categories tool.)
Counts are as of 22 September 2026.
| Code | Label | Listings | On the open shelf |
|---|---|---|---|
other | Other | 1,553 | 359 |
media | Media & Entertainment | 1,154 | 260 |
saas | SaaS | 599 | 155 |
education | Education | 512 | 114 |
ecommerce | E-Commerce | 440 | 86 |
healthcare | Healthcare | 186 | 33 |
travel | Travel & Hospitality | 174 | 39 |
social | Social Media | 168 | 38 |
gaming | Gaming | 133 | 29 |
productivity | Productivity | 112 | 22 |
real-estate | Real Estate | 108 | 15 |
fintech | Fintech | 106 | 20 |
food | Food & Beverage | 104 | 22 |
automotive | Automotive | 90 | 16 |
crypto | Crypto & Web3 | 84 | 14 |
ai | AI & Machine Learning | 79 | 26 |
security | Security | 79 | 18 |
fitness | Fitness & Wellness | 75 | 15 |
legal | Legal | 52 | 15 |
iot | IoT & Hardware | 29 | 5 |
Those counts total 5,837 categorised listings out of 17,713 in the directory, so a category filter reaches about a third of it. The rest carry no category at all and no category value will return them. If breadth matters more than precision, filter on something else and sort locally.
An unknown code is refused with 400 invalid_request rather than answered with an empty page, which is what you want — a typo fails loudly instead of looking like a quiet day in the directory.
Traps
limit is clamped, but an empty limit is refused. Ask for 5,000 and you get 250; ask for 0 and you get 1. But ?limit= — which is what ?limit=${n} sends for an undefined n — is a 400, on purpose: answering it with a page size of 1 would quietly make a full sync 250 times longer.
Minimum filters exclude null rows. rsMin, citabilityMin and ageMin drop rows where the metric is missing rather than ranking them low. Since agent_citability is null whenever the domain has no row in the citability index, a mild-looking citabilityMin=20 can remove most of a page. Check metrics_missing on the rows you do get back.
ageMin is the one that catches people out, because age is unknown on a large share of the directory — 9 of the 30 open-tier rows bundled with the Python walkthrough carry a null age_years. So ageMin=1 does not mean “at least a year old”; it means “at least a year old, and we know how old it is”. If you want old domains without silently dropping every unaged row, filter on something else and sort on age_years locally, treating null as unknown rather than young.
rd takes band ids, not band values. The response reports referring_domains as "<10", "10-50", "50-100", "100-250" or "250+". The filter takes lt10, 10-50, 50-100, 100-250 or 250plus — the ids exist so you never have to percent-encode < or +. Only the middle three coincide, so copying a value straight out of a response into the filter is a 400 on exactly the two ends of the range you are most likely to want.
# WRONG — these are response values, not filter ids
curl -s -G https://getrevised.com/api/v1/domains -d 'rd=<10,250+' ...
# RIGHT
curl -s -G https://getrevised.com/api/v1/domains \
-H "Authorization: Bearer $REVISED_KEY" \
-d rd=100-250,250plus \
-d source='Hacker News,Wikipedia'sort=latest cannot be combined with since. The change feed is ordered by id.
Unpublished parameters are ignored, published ones are enforced. A parameter this API documents, carrying a value it cannot honour, is a 400 naming the parameter and what it accepts — never a silently wider page. Parameters it does not document are ignored, so tracking and cache-busting keys on the query string are harmless.
Read status before you read anything else. A since page mixes full listings with tombstones. Outside the change feed every row is status: "active", but branching on it costs nothing and makes the code correct if you later add since.
Errors
| Status | error.code | What happened |
|---|---|---|
| 400 | invalid_request | A published parameter carried a value it cannot honour, or a cursor this endpoint did not issue. The message names the parameter. |
| 401 | unauthorized | No key, or a key that no longer resolves. |
| 402 | upgrade_required | On this endpoint it means exactly one thing: since on a plan without the change feed. |
| 405 | method_not_allowed | Carries Allow. |
| 429 | rate_limited | Carries Retry-After alongside the X-RateLimit-* headers every response has. |