Revised

Pagination and sync

Opaque cursor paging on GET /api/v1/domains, the synced_at watermark, the since change feed, and tombstones.

GET /api/v1/domains is the only paged endpoint. It pages by opaque cursor, and on the Business plan the same endpoint doubles as a change feed.

The page envelope

json
{
  "data": [ /* up to `limit` listings, or tombstones on a `since` page */ ],
  "next_cursor": "00aLaXEG",
  "has_more": true,
  "synced_at": "2026-09-22T01:45:55.000Z"
}
FieldTypeWhat it is
dataarrayListings. On a since page it mixes listings and tombstones — read status on every row.
next_cursorstring or nullPass as cursor for the next page. null on the last page.
has_morebooleanMirrors next_cursor !== null.
synced_atstringThe watermark for your next run. See Incremental sync.

limit

DefaultMaximumMinimum
502501

An integer out of range is clamped, not rejected — ask for 5000 and you get 250, ask for 0 and you get 1.

A limit that is not a whole number is refused with 400 invalid_request, and that includes the empty string. ?limit= is what ?limit=${n} sends for an undefined n, and answering it with a page size of 1 would quietly make a full sync 250 times longer.

cursor

cursor is an opaque string taken from the previous response’s next_cursor.

A cursor belongs to the ordering it came from, so pass the same sort on every page of a run. The default ordering’s cursor is a listing id; sort=latest’s is a position in that order.

Ordering

sortOrderingCursor-safe because
omittedAscending listing id. The default since v1.A listing id is written once and never rewritten.
latestNewest discovered_at first, tie-broken by id.discovered_at is written once and never rewritten.

No other value is accepted. Every other ordering the website offers is over a column an update can move, and a feed that reshuffles under a cursor can skip or repeat rows — so the endpoint refuses those rather than serving one.

latest cannot be combined with since: the change feed is ordered by id.

Paging the whole directory

Follow next_cursor until it comes back null. This works on every plan.

bash
curl "https://getrevised.com/api/v1/domains?limit=250&rsMin=60" \
  -H "Authorization: Bearer $REVISED_API_KEY"
python
import os, requests

BASE = "https://getrevised.com/api/v1"
AUTH = {"Authorization": f"Bearer {os.environ['REVISED_API_KEY']}"}

def page_all(**filters):
    """Every listing matching `filters`, one page at a time."""
    cursor = None
    while True:
        params = {"limit": 250, **filters}
        if cursor:
            params["cursor"] = cursor
        page = requests.get(f"{BASE}/domains", headers=AUTH, params=params, timeout=30).json()
        yield from page["data"]
        cursor = page["next_cursor"]
        if cursor is None:
            return

count = sum(1 for _ in page_all(rsMin=60, tld="com"))
print(f"{count} listings")
Output
text
1843 listings

Paging cannot skip or repeat a row while the directory is updated underneath you, because both orderings are over write-once columns.

Incremental sync

since takes an ISO 8601 instant and is matched, inclusively, against each row’s updated_at.

bash
curl "https://getrevised.com/api/v1/domains?since=2026-09-21T23:45:55.000Z&limit=250" \
  -H "Authorization: Bearer $REVISED_API_KEY"

Use synced_at, not your own clock

Pass the synced_at from the first page of your previous run as the next run’s since. Never datetime.now().

synced_at is taken before any row on that page is read, so a listing updated part-way through a run is picked up by the next run rather than skipped. It is also held deliberately 120 seconds behind server time, which is what makes a row written just before your previous poll impossible to miss: a row is stamped a moment before it is committed, and a watermark taken in that gap would have filtered it out for good.

Consecutive polls therefore overlap on purpose. Expect to see rows you already hold, and write them straight through — a row is fully described by its id plus its updated_at, so upsert on id and ignore a row you already hold at that updated_at.

Changed includes gone

A since page mixes full listings with tombstones: rows that have left the directory since your watermark.

A
json
{
  "id": "00aLaXEG",
  "status": "delisted",
  "updated_at": "2026-09-02T04:11:08.512Z"
}

A tombstone carries id, status and updated_at and nothing else — no name, because you already hold the name against that id from the poll that delivered it. Sold, blocked and delisted all report as delisted; they mean the same thing to a consumer.

Read status before treating a row as a listing. active is a listing to upsert; delisted is an id to drop. A first sync with no since returns listings only.

A complete sync loop

python
import os, requests

BASE = "https://getrevised.com/api/v1"
AUTH = {"Authorization": f"Bearer {os.environ['REVISED_API_KEY']}"}

def sync(since: str | None) -> str:
    """Apply everything changed since `since`. Returns the next watermark."""
    cursor, watermark = None, None
    while True:
        params = {"limit": 250}
        if since:
            params["since"] = since
        if cursor:
            params["cursor"] = cursor

        page = requests.get(f"{BASE}/domains", headers=AUTH, params=params, timeout=60).json()

        # Take the watermark from the FIRST page only: it was read before
        # any row was, so a row written mid-run is caught by the next run.
        if watermark is None:
            watermark = page["synced_at"]

        for row in page["data"]:
            if row["status"] == "delisted":
                drop(row["id"])
            else:
                upsert(row)          # idempotent on (id, updated_at)

        cursor = page["next_cursor"]
        if cursor is None:
            return watermark

watermark = load_watermark()             # None on a first run
watermark = sync(watermark)
save_watermark(watermark)
Output
text
applied 312 listings, dropped 4 ids
next watermark: 2026-09-22T01:45:55.000Z

What since does not catch

Taking, extending or ending a hold does not move a listing’s updated_at, so an incremental poll will not re-deliver a row whose hold state changed. Read the listing directly with GET /api/v1/domains/{id}, or call GET /api/v1/holds for your own.

Listing ids

Ids are eight characters and case-sensitive, mixed case in practice (00aLaXEG, X00w46xl). Store them exactly as they arrive; a lower-cased id matches nothing, including in a cursor.

Filters and paging together

A cursor names a position in the ordering, not the query that produced it. Change a filter mid-run and the next page continues from that position under the new filters, silently skipping every matching row behind it — no error, just a short result. Fix your filters before the first page and keep them, along with sort, for every page of the run. See Filters.

Type to search…

↑↓ navigate openesc close