API Docs

Pagination

List endpoints (/v1/listings, /v1/orders) return cursor-paginated results. Cursors are opaque and stable. They survive concurrent inserts so you can page through a list at any rate without missing or duplicating rows.

Response shape

{
  "object": "list",
  "data": [ /* up to `limit` rows */ ],
  "has_more": true,
  "next_cursor": "eyJpZCI6IjI0Zjg…"
}
  • data: array of resource objects (listing / order).
  • has_more: boolean. true when more rows exist past this page.
  • next_cursor: opaque string. Pass it back as after on the next call. null when has_more is false.

Walking a list

# First page
curl -s "https://watchtraderhub.com/api/v1/listings?limit=100" \
  -H "Authorization: Bearer wth_YOUR_API_KEY"
# → { "data": [...], "has_more": true, "next_cursor": "eyJ..." }

# Subsequent pages, pass the previous next_cursor as `after`
curl -s "https://watchtraderhub.com/api/v1/listings?limit=100&after=eyJ..." \
  -H "Authorization: Bearer wth_YOUR_API_KEY"

Loop until has_more is false (or equivalently, next_cursor is null).

Incremental sync with updated_after

Once you've done an initial backfill, use updated_after to fetch only what changed:

curl -s "https://watchtraderhub.com/api/v1/listings?updated_after=2026-05-08T00:00:00Z" \
  -H "Authorization: Bearer wth_YOUR_API_KEY"

The server returns rows where updated_at > updated_after in the same descending-by-updated_at order as the unfiltered list, and after still works for paging through the filtered subset.

Recommended sync loop

Persist the updated_at of the most recent row you ingested. On the next sync, set updated_after to that value. Webhook events listing.updated / order.received are the realtime path; updated_after is the catch-up path for when your receiver was offline.

Cursors are opaque, really

The string in next_cursor is a base64url-encoded JSON object containing (id, updated_at). That's an implementation detail. Do not parse it, do not modify it, and do not generate one yourself. The format is free to change between API versions.

Hand-crafted cursors return 400 invalid_cursor.

Tie-break behaviour

We order by (updated_at DESC, id DESC). The cursor carries both values so two rows with identical updated_at resolve deterministically. No skipped or duplicated rows at page boundaries, even when timestamps collide (e.g. bulk inserts).

Limits

  • limit defaults to 50, max 250.
  • The endpoint always overshoots by 1 row internally to compute has_more. That extra row is never returned in data.
  • Cursors don't expire. A cursor minted today still works tomorrow against the same dataset.