18+ Only  |  Gambling can be addictive — please gamble responsibly  |  Gambling Help: 1800 858 858  |  GambleAware

PuntersEdge › US & Canadian racing › US Horse Racing API in Python

Wagering is for adults, where it is legal. Problem gambling help in the US: the National Problem Gambling Helpline, call or text 1-800-MY-RESET (1-800-697-3738), chat at ncpgambling.org. In Ontario: ConnexOntario, free and 24/7, 1-866-531-2600.

US & Canadian racing · Python

The US tote board and payoffs in Python.

Four short scripts: today's board with each race's favourite, one race's odds history and exacta probables, yesterday's exacta payoffs, and the races only the tote prices. Plain requests against a REST API; the official client is optional.

Set PUNTERSEDGE_API_KEY in your environment. Every call below also works with pip install puntersedge; the examples stay on requests so each parameter is visible.

1. Today's tote board

import os, requests

API = "https://api.puntersedge.online/v1"
H = {"X-API-Key": os.environ["PUNTERSEDGE_API_KEY"]}

board = requests.get(f"{API}/racing/tote", params={"country": "US,CA"},
                     headers=H, timeout=20).json()
for race in board["races"]:
    tote = race["tote"]
    fav = next((r for r in tote["runners"] if r.get("favorite")), None)
    print(race["post_time"], race["venue"], race["race_number"], tote["status"],
          tote.get("track_condition"), fav and (fav["name"], fav["win_odds"]))

One call returns every race on the US Eastern day; 307 races at the moment this page was rendered.

2. One race's odds history and probables

# One race in full: how its win odds moved, and the exacta probables.
sid = board["races"][0]["source_race_id"]
race = requests.get(f"{API}/racing/tote",
                    params={"source_race_id": sid, "history": "true", "exotics": "true"},
                    headers=H, timeout=20).json()["races"][0]
for point in race["history"] or []:
    print(point["secs_to_post"], point["win_pool"],
          {r["number"]: r["win_odds"] for r in point["runners"]})
exacta = next((p for p in (race["exotics"] or {}).get("probables", []) if p["code"] == "EX"), None)
if exacta:
    for first, second, payout in sorted(exacta["combos"], key=lambda c: c[2])[:5]:
        print(f"{first}-{second} pays {payout} for {exacta['min_wager']}")

history and exotics take one race; the history starts an hour before post.

3. Yesterday's results and exacta payoffs

# Yesterday's results, the exacta payoff on each race.
results = requests.get(f"{API}/racing/results",
                       params={"country": "US,CA", "hours_back": 24, "limit": 200},
                       headers=H, timeout=30).json()
for res in results:
    ex = next((d for d in (res.get("dividends") or {}).get("exotics", []) if d.get("code") == "EX"), None)
    winner = res["placings"][0] if res.get("placings") else None
    print(res["venue"], res["race_number"], res.get("surface"), res.get("track_condition"),
          winner and winner["name"], ex and (ex["selection"], ex["payout"], ex["base"]))

Payoffs come with their base stake, so a $2 and a $1 pool compare on the same footing.

4. The races only the tote prices

# The races no bookmaker prices: the tote is the only market on them.
tote_only = [race for race in board["races"] if race["race_id"] is None]
print(len(tote_only), "of", len(board["races"]), "races carry the tote alone")
for race in tote_only[:5]:
    print(race["venue"], race["race_number"], race["tote"].get("race_class"),
          race["tote"].get("purse"), race["tote"].get("purse_currency"))

race_id is null on a race no bookmaker prices; the tote board still carries it in full.

Questions

Do I need the Python client?

No. Every example on this page is plain requests against the REST API. The client on PyPI (pip install puntersedge) wraps the same calls.

How many credits does the tote board cost?

Two credits a call, whatever it returns, so the whole day's North American board is one call. The endpoint reference lists every cost.

Can I find the races no bookmaker prices?

Yes: on the tote board their race_id is null, and source_race_id is the key to ask for one of them again. /v1/racing/events?country=US,CA lists them too, as tote_only entries.

Can I run these on a free key?

The calls on this page take any key; what you may do with the data follows your plan.

Run it against today's board.

A free key reaches the tote board, the race cards and the results.

Get a free key
This site contains wagering-related analysis and is intended for Australian users aged 18+. Gambling involves risk. Please gamble responsibly.