Watchlist
Get Watchlist Price Updates (Cursor Delta Poll)
Price changes for your watched cards since your cursor.
How it works
- First call: omit cursor. You'll page through the current state of every watched item, oldest price-change first (bootstrap walk).
- Store next_cursor from each response and pass it to the next call.
- While has_more is true, loop immediately. Once it is false you are caught up — keep polling with the latest cursor (once a minute is plenty).
Update fields
- event — "changed" = new current price for this card+grade. "removed" = the price was retracted and nothing replaced it — drop your cached value (price fields are null).
- card_id — CardHedge card ID.
- provider / grade_label — grading company and grade (e.g. PSA / "PSA 10").
- latest_price — current price for this card+grade.
- median_price_90d / sample_count_90d — 90-day median and sale count, when available.
- last_updated — time of the underlying sale.
- changed_at — when this state change entered the feed (drives cursor ordering).
Semantics
- Events are coalesced state changes: "the current price for this card+grade changed", not per-sale records. If a card trades three times between polls you see the latest state once.
- Apply events in order; the same card+grade may legitimately appear more than once in a walk (old state then newer state). Last state wins.
- Cursors never expire. A client offline for weeks just catches up.
- Updates appear here ~2–3 minutes after a sale syncs: a short safety horizon guarantees no update can ever be skipped, trading a little latency for a lossless feed.
- On errors, retry with the same cursor — no updates are lost. Cursors are opaque; only ever pass back a cursor this endpoint returned.
Politeness
Poll on a timer started at process launch (e.g. every 60s), not on the wall-clock minute — synchronized :00 polling creates load spikes.
Example: bootstrap, then poll with the cursor
# 1) First call: no cursor. Returns current state of your watchlist
# plus a next_cursor. Repeat with the cursor while has_more is true.
curl -X POST -H "X-API-Key: your-api-key" -H "Content-Type: application/json" -d '{}' "https://api.cardhedger.com/v1/cards/watchlist-updates"
# -> {"updates": [ ... ], "count": 24,
# "next_cursor": "eyJ2IjoxLCJjIjpbIjIwMjYtMDctMDNUMTg6MDE6MTIuNDEwMjMzKzAwOjAwIiw0MjFdLCJkIjpbbnVsbCxudWxsXX0=",
# "has_more": false}
# 2) Every later poll: send back the next_cursor you received, verbatim.
curl -X POST -H "X-API-Key: your-api-key" -H "Content-Type: application/json" -d '{"cursor": "eyJ2IjoxLCJjIjpbIjIwMjYtMDctMDNUMTg6MDE6MTIuNDEwMjMzKzAwOjAwIiw0MjFdLCJkIjpbbnVsbCxudWxsXX0="}' "https://api.cardhedger.com/v1/cards/watchlist-updates"
# -> only what changed since that cursor; {"updates": [], "count": 0, ...}
# when nothing did. Store next_cursor each time - it never expires.
Example polling loop
import requests, time
API_KEY = "your-api-key"
URL = "https://api.cardhedger.com/v1/cards/watchlist-updates"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
cursor = None # bootstrap: walks your full watchlist state first
while True:
body = {"cursor": cursor} if cursor else {}
data = requests.post(URL, headers=HEADERS, json=body).json()
for u in data["updates"]:
if u["event"] == "changed":
print(f'{u["card_id"]} {u["grade_label"]}: ${u["latest_price"]}')
else: # "removed"
print(f'{u["card_id"]} {u["grade_label"]}: price retracted')
cursor = data["next_cursor"]
if not data["has_more"]:
time.sleep(60) # caught up - relax to steady polling
post/v1/cards/watchlist-updates
Request body
Response
Price updates for watched cards since the cursor
Changes
No recorded changes to this endpoint across all 2 revisions of this API.