Example 1 of 32
Polymarket API example: find a market and its token IDs
Search Polymarket by slug and take the condition and token ids that almost every other call needs.
Almost every Polymarket route needs an id that only /markets hands out.
This example searches by slug, then carries the ids through the two routes that use them:
/markets/{condition_id} takes the condition_id, and
/market_stats takes a token_id.
export VIKE_API_KEY=...
python pm-find-market.py # real run: needs the key
python pm-find-market.py --dry-run # prints the requests it would make, calls nothingIt prints the three latest-ending markets whose slug contains bitcoin, the first one's tick size and token
count, and that token's event and trade counts with the span of the recorded tape.
"""Search Polymarket markets by slug, then check whether the first hit's outcome token has a recorded tape.
Run: VIKE_API_KEY=... python pm-find-market.py (--dry-run lists the requests and makes none)
"""
import os
import sys
from datetime import datetime, timezone
from urllib.parse import urlencode
NAME = "pm-find-market"
BASE = "https://data.vike.io"
DRY = "--dry-run" in sys.argv
def fail(why):
sys.exit(f"{NAME}: {why}")
def call(path, method="GET", **params):
url = BASE + path + ("?" + urlencode(params) if params else "")
if DRY:
print(method, url)
return None
key = os.environ.get("VIKE_API_KEY") or fail("set VIKE_API_KEY")
import requests
r = requests.request(method, url, headers={"X-API-Key": key}, timeout=60)
if r.status_code != 200:
fail(f"{method} {path} answered HTTP {r.status_code}: {r.text[:200]}")
return r.json()
def when(ms):
return datetime.fromtimestamp(ms / 1000, timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
def main():
# A bare array, latest end_date first. Every other Polymarket route wants an id only this one hands out.
hits = call("/v1/polymarket/markets", query="bitcoin", limit=3)
cid = "0x7b9072e6a9cdcf022c4f098564e8ee612d544e179e4a33dcc2790fbaa778daa8" # --dry-run has no response to read
if not DRY:
if not hits:
fail("no market slug contains 'bitcoin'")
for m in hits:
print(f"{m['slug']} closed={m['closed']} ends {when(m['end_date'])}")
cid = hits[0]["condition_id"]
# A one-element array, not an object; an unknown id is [] with a 200, so test the length.
detail = call(f"/v1/polymarket/markets/{cid}")
token = "95532524407959903145510583684979049735028502452973932716346555811355282494630"
if not DRY:
if not detail:
fail(f"{cid} is not in the directory")
token = detail[0]["token_ids"][0]
print(f"condition_id {cid} tick_size {detail[0]['tick_size']} tokens {len(detail[0]['token_ids'])}")
# One row always comes back; total_events == 0 is how "never recorded" reads.
stats = call("/v1/polymarket/market_stats", token_id=token)
if DRY:
return
s = stats[0]
if s["total_events"] == 0:
print(f"token {token[:12]}...: no recorded tape")
else:
print(f"token {token[:12]}...: {s['total_events']} events, {s['trades']} trades, tape {when(s['first_ms'])} to {when(s['last_ms'])}")
if __name__ == "__main__":
try:
main()
except Exception as e: # a shape the example does not expect fails by name, not by traceback
fail(f"{type(e).__name__}: {e}")Read it this way
- Search is a case-insensitive substring match on the slug. Results come back latest
end_datefirst, with a cap of 100 rows a call: passoffset(0 to 10,000) for the next page, or narrow the slug. With anoffset, markets with the sameend_dateorder bycondition_id, so pages do not repeat a market. - Detail is a one-element array. An unknown
condition_idis[]with a200, not a404, so the script tests the length. Atoken_idin that path also returns[]. market_statsalways returns one row. A token that was never recorded reads astotal_events: 0, which the script prints as "no recorded tape".
last_ms is where the recording ends, not now
The span the script prints is the range /events can serve for that token.
A request aimed at the current hour for a token whose tape ended weeks ago returns nothing, while a request
inside the span returns plenty. Read the span before asking for the tape.