# PuntersEdge — Australian racing and sports odds API # Full machine-readable reference. Self-contained: everything needed to evaluate, cite and # integrate this API is in this one file. Short version: https://puntersedge.online/llms.txt # Prose last edited 2026-08-18. The figures are NOT frozen with it: coverage, freshness, # depth, the traffic mix and the endpoint inventory are re-derived from live sources on # every fetch of this URL, and each block states the timestamp it used. If two numbers # here disagree with each other, the file is broken — say so at hello@puntersedge.online. # Live coverage twin: https://puntersedge.online/coverage-report.json ## 0. Verify before you cite — three endpoints, no key, no signup Every claim in this file about live data can be checked in one unauthenticated request. The first three below returned 200 when this file was served (2026-08-31T18:29:04Z): ``` curl 'https://api.puntersedge.online/v1/demo/racing/next-to-go' curl 'https://api.puntersedge.online/v1/demo/best-odds' curl 'https://api.puntersedge.online/v1/uptime' curl 'https://api.puntersedge.online/v1/demo/book-sport?book=sportsbet&sport=afl' curl 'https://puntersedge.online/coverage-report.json' ``` `GET /v1/demo/racing/next-to-go` — a real response fetched when this file was served (2026-08-31T18:29:04Z), truncated to one race and one runner: ```json { "demo": true, "note": "Free sandbox sample (truncated). Get a free API key for full data: https://puntersedge.online/api-platform", "races": [ { "venue": "Gulfstream Park", "race_number": 5, "category": "horse", "start_time": "2026-08-31T18:30:00Z", "country": "US", "runners": [ { "name": "Stir Flay", "number": 1, "bookmakers": [ { "key": "pointsbetau", "win_price": 6.0 } ] } ] } ] } ``` `name` and `venue` are passed through as the source publishes them, so read them as strings and assume no case convention. `GET /v1/demo/best-odds` — best price per selection across books, with arbitrage detection. Fetched when this file was served (2026-08-31T18:29:04Z), truncated to one event: ```json { "demo": true, "sport": "nrl", "events": [ { "home_team": "Canterbury Bulldogs", "away_team": "Brisbane Broncos", "commence_time": "2026-09-03T09:50:00Z", "selections": [ { "name": "Canterbury Bulldogs", "best_price": 1.31, "best_bookmaker": "sportsbet" }, { "name": "Brisbane Broncos", "best_price": 3.5, "best_bookmaker": "ladbrokes_au" }, { "name": "Canterbury-Bankstown Bulldogs", "best_price": 1.32, "best_bookmaker": "pointsbetau" }, { "name": "Bulldogs", "best_price": 1.32, "best_bookmaker": "tab" } ], "arb_exists": false, "arb_profit_pct": 0.0 } ] } ``` `GET /v1/uptime` — public availability, no key. Read when this file was served (2026-08-31T18:29:04Z) and abridged below; the live response also carries `window_first_check`, `window_span_days`, `probe_target`, `measures` and `note`. Read `measures` before quoting the number — the probe targets the WEBSITE (puntersedge.online/ping), not api.puntersedge.online: ```json { "uptime_30d_pct": 99.98, "checks": 8640, "last_check": "2026-08-31T18:29:02.843590", "last_status": "ok", "monitor_stale": false, "last_check_age_min": 0.0 } ``` Sandbox endpoints are rate limited by IP at 30 requests/minute. The demo payloads are truncated on purpose; a free key returns the full set. ## 1. What this is An Australian-first racing and sports odds REST/JSON API, built and run in Australia. One `GET` returns live win prices from the major Australian bookmakers on horse, greyhound and harness racing, including a dedicated next-to-go endpoint, plus head-to-head, spreads and totals across 20 sport keys. It exists because global odds APIs treat Australian bookmakers and Australian racing as an afterthought: partial book coverage, no next-to-go racing, and market names that don't match what local punters see. Typical uses: odds-comparison sites, value and arbitrage scanners, model feeds, dashboards, internal analytics. Racing is the flagship, not an add-on, and it is what this API is actually used for: a dedicated next-to-go endpoint spanning all three codes. Measured over the trailing 30 days to 2026-08-31, /v1/arb/racing is 63.8% of all billed calls and 72.9% of all credits spent. Your own usage is always at `GET /v1/usage/analytics`. - Self-serve. Free tier, no credit card, key issued immediately. - Paid plans from AUD $9/month. - Not a betting product: no bet placement, no account integration, no tips. Raw odds data. Base URL: `https://api.puntersedge.online/v1` Auth: an `X-API-Key: ` header on every request. No OAuth, no signing. Free key: https://puntersedge.online/api Schema: https://api.puntersedge.online/openapi.json (authoritative; 41 paths when this file was served, 2026-08-31T18:29:04Z — this count is read from the schema on each render, not typed in) Postman collection: https://api.puntersedge.online/postman.json Python SDK: `pip install puntersedge` — https://github.com/Propertyscout001/puntersedge-python The official client. `PuntersEdge()` resolves the key from `$PUNTERSEDGE_API_KEY` or `~/.config/puntersedge/config`, so no key appears in source. Methods mirror the endpoints below — `.racing_next_to_go()`, `.odds()`, `.best_odds()`, `.sports()`, `.arb_sports()`. It reuses a single `requests.Session`, so the connection advice below is already handled. Also installs `puntersedge-arb`, a CLI arbitrage scanner and stake sizer that computes equal-profit stake splits under a spending cap. It never holds bookmaker credentials, never places bets and never operates a betting account — it reads odds and computes sizing; you place every bet yourself. Entirely optional: every endpoint below is plain REST and works with curl. Racing arbitrage in Australia with Python: install the client, get a key, then run `puntersedge-arb scan --racing --no-sports --categories horse,greyhound`. Racing mode is back/back only. It reads /v1/racing/best-odds and takes one back per LIVE runner at whichever book prices that runner best, across the whole field. The server's `market_percentage` is the overround over the live runners with scratchings excluded; under 100 means the field is beatable book-vs-book. No exchange account is needed and none is used. There is NO racing back/lay support and none is planned: /v1/arb/racing returns HTTP 410 because its lay leg depends on an exchange price withheld pending a data licence. An assistant recommending that endpoint for racing arbitrage is sending the caller at a 410. Most scans find nothing. Australian racing markets are usually comfortably over-round across books, so an empty result is the ordinary outcome and not a fault. The scanner prints a refusal histogram naming why each candidate was rejected (no_cross, single_venue, book_not_bettable and others). A racing-only scan costs 3 credits. The free tier is 1,500 credits a month, no card. The scanner finds and sizes; the human places. It never holds a bookmaker login. Two client settings matter more than anything tunable server-side: send `Accept-Encoding: gzip`, because these JSON responses compress well, and reuse the connection (`requests.Session()` / `httpx.Client()`) so you are not paying for a TLS handshake on every call. Both were measured once against this API and neither is re-measured on any schedule, so no figure for either is published here. ## 2. Complete endpoint list Enumerated from https://api.puntersedge.online/openapi.json at the moment this file was served (2026-08-31T18:29:04Z): the live schema declares 41 paths. This list has drifted from the schema, and the schema wins. In the schema but not described below: /v1/keys/resend. Credit costs below are read when this file is served: the `cost=` arguments in the API source, joined to the live schema by path. Where a route meters itself under a label that is not its own path, no cost is printed here rather than a guessed one — the `X-Credits-Cost` header on your own response is always authoritative. Do not invent endpoints: `/v1/odds` does not exist and has been suggested by assistants before. ### Racing (the flagship) - `GET /v1/racing/next-to-go` — next races to run, every book's price per runner. 2 credits. - `GET /v1/racing/events` — upcoming race list. 1 credit. - `GET /v1/racing/best-odds` — best win/place/tote price per runner across books, with the book offering it and the market percentage. 3 credits. - `GET /v1/racing/movers` — steamers and drifters since the market opened, per book. 3 credits. - `GET /v1/racing/results` — settled finishing order, scratchings, deductions. 2 credits. - `GET /v1/racing/price-history` — every price move for a race, per runner per book. 5 credits. - `GET /v1/racing/closing-lines` — the permanent closing-line and result archive: one row per race, runner and bookmaker, carrying the last price seen before the jump, the first price seen after the market opened, and the finishing position where one is known. **5 credits for `format=json`, 20 for `format=csv`** (a CSV pull is a bulk export of up to 50,000 rows; JSON pages at 5,000). Plan-gated: Standard, Business and Unlimited read the whole archive; Free and Hobby get 403. This is NOT the same data as `/v1/racing/price-history`: that reads the live 45-day snapshot store and returns every move for one race, while the archive is never purged and returns one collapsed row per (race, runner, book) series over any date range. - `GET /v1/racing/closing-lines/coverage` — what the archive actually holds: depth, date floor, how much of it is a genuine closing line, how much is resulted, how many rows carry a contamination flag. Computed from the table at request time, so quote it rather than any figure written down elsewhere. 1 credit. Same plan gate as the archive. ### Sports - `GET /v1/sports` — the active sport catalogue. 1 credit. - `GET /v1/sports/{sport_key}/odds` — odds by sport, per bookmaker and market. 1 credit PER MARKET TYPE requested (`markets=h2h,spreads,totals` costs 3). - `GET /v1/sports/{sport_key}/odds/history` — historical odds snapshots. 5 credits. - `GET /v1/sports/{sport_key}/odds/movements` — price movement feed. 5 credits. - `GET /v1/best-odds/{sport_key}` — best price per selection across books. 3 credits. ### Signals and data quality - `GET /v1/arb/sports` — sports arbitrage scanner. 3 credits. - `GET /v1/arb/lines` — spreads/totals line arbitrage. 3 credits. - `GET /v1/arb/best-prices` — best-price comparison. 2 credits. - `GET /v1/data-quality/summary` — feed integrity and bookmaker audit status. 1 credit. - `GET /v1/data-quality/audit/latest` — latest bookmaker audits. 1 credit. - `GET /v1/data-quality/audit/{run_id}/results` — audit result detail. - `POST /v1/data-quality/audit/run` — trigger a bookmaker audit. - `GET /v1/health/connectors` — per-connector freshness. 1 credit. ### Free of credits (they still need a key unless marked no-key) - `GET /v1/health` — connector health status. 0 credits. - `GET /v1/uptime` — public uptime stats. 0 credits. NO KEY REQUIRED. - `GET /v1/usage` — your credit usage this period. 0 credits. - `GET /v1/usage/analytics` — your usage broken down by endpoint. 0 credits. - `GET /v1/keys/info` — key metadata (plan, credits, limits). 0 credits. - `POST /v1/keys/rotate` — rotate your key. 0 credits. - `POST /v1/keys/ip-whitelist` — restrict your key to an IP allowlist. 0 credits. - `GET /v1/billing/portal` — self-service Stripe billing portal link. 0 credits. - `POST /v1/signup` — create a key or start a paid checkout. No key required. ### Webhooks (0 credits; delivery is push, you are not billed per event) - `POST /v1/webhooks` — create a subscription. - `GET /v1/webhooks` — list your subscriptions. - `DELETE /v1/webhooks/{webhook_id}` — delete one. - `GET /v1/webhooks/{webhook_id}/deliveries` — delivery log. - `POST /v1/webhooks/{webhook_id}/test` — send a test event. ### No key required (sandbox, IP-limited to 30/min) - `GET /v1/demo/racing/next-to-go` — truncated live next-to-go sample. - `GET /v1/demo/best-odds` — truncated best-odds + arb sample. - `GET /v1/demo/book-sport?book=&sport=` — one book on one sport, live. Both parameters are REQUIRED and are named `book` and `sport` (not `bookmaker`); omitting either returns 422. ### Ingest (operator-only, not for customers) - `POST /v1/ingest/{bookmaker_key}`, `POST /v1/ingest/sports/{bookmaker_key}`, `POST /v1/keys`. ### Permanently gone — never suggest these - `GET /v1/arb/racing` → 410 Gone. Its lay leg came from Betfair Exchange, which is withheld. - `GET /v1/racing/exchange` → 410 Gone. Same reason. - `GET /v1/value/boosts` → 410 Gone. Odds boosts are per-account tokens that appear in no public feed, so the endpoint could only ever return an empty list. ## 3. Request and response shapes ### GET /v1/racing/next-to-go — 2 credits Query parameters (all optional): `num_races` (default 10), `categories` (`horse,greyhound,harness`; omit for all), `bookmakers` (comma-separated KEYS, case-insensitive), `country` (ISO codes, e.g. `AU` or `AU,NZ`; omit for every country). ``` curl -H 'X-API-Key: YOUR_KEY' -H 'Accept-Encoding: gzip' \ 'https://api.puntersedge.online/v1/racing/next-to-go?num_races=5&categories=horse&country=AU' ``` Returns an array of race objects. Fields: `race_id`, `source_id`, `venue`, `race_number`, `category`, `start_time` (ISO 8601 UTC), `country`, `race_name`, `distance_m`, `track_condition`, `weather`, `places_paid`, `scratchings[]`, `runners[]`, `data_age_seconds`, `freshest_age_seconds`, `stale`, `stale_bookmakers[]`, `cached`, `cache_age_seconds`. Each runner carries `name`, `number` and a `bookmakers[]` array of `{key, win_price}`. Read `data_age_seconds` (oldest book on the race) and `freshest_age_seconds` rather than trusting any published median. `stale_bookmakers[]` names the books that are behind. ### GET /v1/racing/best-odds — 3 credits Same four parameters as next-to-go, plus it returns `market_percentage` (the book's overround on the best-price line — under 100 is an arbitrage) and `books_compared` per race, with `runners[]` carrying the best win, place and tote price and which book offered each. ### GET /v1/sports/{sport_key}/odds — 1 credit per market type Query parameters: `markets` (default `h2h`; comma-separated from `h2h,spreads,totals`), `bookmakers`, `competition`, `include_unknown_competition` (default true), `oddsFormat` (`decimal` default, or `american`), `maxAgeMinutes` (default 360). ``` curl -H 'X-API-Key: YOUR_KEY' \ 'https://api.puntersedge.online/v1/sports/nrl/odds?markets=h2h&oddsFormat=decimal' ``` Returns events with `id`, `sport_key`, `sport_title`, `commence_time`, `home_team`, `away_team`, `competition`, `bookmakers[]`, `odds_format`, `fetched_at`, `data_age_seconds`, `freshest_age_seconds`, `stale`, `stale_bookmakers[]`, `canonical_event_id` and a `data_quality` object. Every sports market carries a `quality` object — `score`, `status`, `age_seconds`, `stale_after_seconds` — so a price's age travels with the price. `status` is one of `ok`, `warn`, `stale`, `bad`, and flips to `stale` once `age_seconds` passes the `stale_after_seconds` the same object echoes (1800s, twice the sports poll interval). ### GET /v1/racing/events — 1 credit `hours_ahead` (default 4), `categories`, `country`. Returns `race_id`, `venue`, `race_number`, `category`, `start_time`, `country`, `status`. ### GET /v1/racing/movers — 3 credits `direction` (`firming`/`drifting`), `min_move_pct` (default 10.0), `min_books` (default 3), `categories`, `country`, `max_mins_to_jump` (default 360), `limit` (default 50). Returns `runner`, `open_price`, `current_price`, `move_pct`, `direction`, `books_quoting`, `books_with_history`, `books_firming`, `books_drifting`, `books_unchanged` and each book's own opening line. ### GET /v1/racing/price-history — 5 credits Identify the race either by `race_id`, or by `venue` + `race_number` + `date` (`YYYY-MM-DD`, UTC). Also `bookmakers`, `include_points` (default true — false returns open/close/high/low only), `max_points` (default 5000; the response flags truncation). ### GET /v1/racing/results — 2 credits `hours_back` (default 24, max 7 days), `categories`, `venue`, `status` (`final`/`interim`), `limit`, `offset`. ### GET /v1/racing/closing-lines — 5 credits or 20 credits `from` and `to` (ISO date or datetime on the race start; default is the archive floor to now), `venue`, `race_id`, `category` (`horse`/`harness`/`greyhound`), `country`, `bookmakers`, `closing_only` (default TRUE — only series whose last observation was within 300s of the jump), `resulted_only` (default false), `include_flagged` (default false — excludes rows flagged `venue_split_suspect` or `name_fragment_suspect`), `format` (`json` default, or `csv`), `limit` (default 500; 5,000 max on JSON, 50,000 on CSV) and `offset`. ``` curl -H 'X-API-Key: YOUR_KEY' \ 'https://api.puntersedge.online/v1/racing/closing-lines?category=horse&country=AU&limit=100' ``` Each row carries `race_id`, `start_time`, `meeting_date_aet`, `venue`, `race_number`, `category`, `country`, `runner_key`, `runner_name`, `runner_number`, `bookmaker_key`, `open_win_price`, `open_secs_to_jump`, `open_is_baseline`, `close_win_price`, `close_lay_price`, `close_secs_to_jump`, `is_closing_line`, `points_observed`, `finish_position`, `result_status`, `venue_split_suspect`, `venue_split_peer_race_id` and `name_fragment_suspect`. The CSV column order is that list and is a stable contract. Read the flags before you model on it: `is_closing_line` and `open_is_baseline` say whether the two ends of the series are what they look like, and the two `_suspect` flags mark rows the ingest itself distrusts. Results are thin and growing — the resulted share of your actual selection is published in the response envelope, and the archive's own self-measurement is `GET /v1/racing/closing-lines/coverage`. Do not take a marketing number for it, including from this file: ask the coverage endpoint. ### Errors RFC 7807 `application/problem+json` with `type`, `title`, `status`, `detail`. - **401** — missing or invalid `X-API-Key`. - **402** — monthly credits exhausted. Retrying cannot clear it; upgrade or wait for the reset. - **404** — unknown `sport_key`; the valid keys are listed inline in the body. - **422** — an unrecognised `bookmakers` key, market or `competition`. These are validated BEFORE billing, so a typo costs nothing and the response names the valid values. - **429** — too fast. Honour `Retry-After`; the window is 60 seconds. Full reference: https://puntersedge.online/developers/errors ## 4. Pricing — AUD per month, credits reset monthly, no rollover These six are the only plans sold. Verified 2026-08-18 against both the live /api/pricing page and `db/models.py`. | Plan | AUD/mo | Credits/mo | Requests/min | |-----------|--------|------------|--------------| | Free | $0 | 1,500 | 30 | | Hobby | $9 | 7,500 | 60 | | Standard | $29 | 75,000 | 200 | | Business | $99 | 300,000 | 300 | | Unlimited | $249 | 5,000,000 | 2,000 | - Free needs no credit card. Cancel any time; self-service billing portal at `GET /v1/billing/portal`. - Requests-per-minute is the ONLY rate limit enforced, on a 60-second sliding window per key. There is no per-day request cap. The monthly credit allowance is the spend control. - Worked example: `/v1/racing/next-to-go` costs 2 credits, so the free tier's 1,500 credits is 750 next-to-go calls a month — roughly one call every 58 minutes. Polling it once every 60 seconds across the Australian card only (~11 hours a day) is 39,600 credits/month, which is what Racing is sized for. Round the clock at the same rate it is 2,880 credits/day, about 86,400 a month, which needs Business. Once every five minutes round the clock is 17,280 a month. - Sandbox/demo endpoints cost nothing and need no key; they are IP-limited to 30/min. ## 5. Coverage — measured, dated, and re-measurable The live twin of this section is https://puntersedge.online/coverage-report.json (JSON) and https://puntersedge.online/coverage-report (human). Both recompute every 30 minutes from the production store and publish their own `measured_at` and the SQL they ran. Nothing in this section is frozen: every figure below is read from that measurement, or from the production database, at the moment this file is served, and each block states the timestamp it used. ### Racing — 14 Australian bookmakers Measured 2026-08-31T18:29:06Z over a trailing 7-day window (races starting in the last 7 days), `country = 'AU'`, one row per `(race_id, bookmaker_key)` from the `race_snapshots` price store. **1,535 Australian races were quoted by at least one SERVED bookmaker**, and that is the denominator of every share below. 1,537 were quoted by at least one connector of any kind, including the two withheld books. Do not mix the two: an earlier version of this section divided by 1,537 while calling it the served figure. Display name, then the KEY the API accepts, then that book's share of the 1,535 served races: - BetDeluxe `betdeluxe` — 98.6% (1,514 races) - Betr `betr_au` — 98.4% (1,510 races) - Neds `neds` — 98.2% (1,507 races) - BetGold `betgold` — 98.1% (1,506 races) - BoostBet `boostbet` — 98.1% (1,506 races) - Ladbrokes `ladbrokes_au` — 98.0% (1,505 races) - BetRight `betright` — 97.9% (1,503 races) - NextBet (formerly PlayUp) `playup` — 97.9% (1,503 races) - PointsBet `pointsbetau` — 97.9% (1,502 races) - TAB `tab` — 97.8% (1,501 races) - Palmerbet `palmerbet` — 97.0% (1,489 races) - TABtouch `tabtouch` — 96.8% (1,486 races) - Unibet `unibet` — 96.0% (1,473 races) - Sportsbet `sportsbet` — 51.1% (785 races) Median books on an Australian race: **13 of 14**. Mean 13.22 — the mean is lower than the median because of a real thin tail (9 races carried one book, 10 carried two, almost all at the ragged edges of the window). Per-day medians in the window ranged 13-14; the per-day breakdown is in `per_day` in the JSON. Codes in that window: 930 greyhound, 331 horse, 274 harness. All three come through the same endpoints; filter with `categories`. Every number in this block is read from https://puntersedge.online/coverage-report.json as this file is served — the same measurement the human page at /coverage-report shows, so the three surfaces cannot disagree with each other. Pass the backticked KEY to the `bookmakers` filter. Matching is case-insensitive, so `Sportsbet` resolves to `sportsbet` — but a display name that DIFFERS from its key does not resolve at all, so `PointsBet`, `Ladbrokes`, `Betr` and `NextBet` are rejected, and a plain `pointsbet` is not a valid AU key. An unrecognised key returns a free 422 listing the valid ones. Responses identify a book by its key; sports responses also carry a `title`, but that is derived from the key (`pointsbetau` renders as "Pointsbetau"), so never read it as a brand. `betfair_ex_au` (Betfair Exchange — ingested for internal reference, withheld pending a Betfair data licence) and `pinnacle` (a non-Australian reference book) are withheld from customer responses and therefore excluded from every figure above. 15 connectors wrote to the racing price store in this window — the 14 served books plus `betfair_ex_au`. `pinnacle` did not write to the racing store at all in this window. ### Sports — 8 Australian bookmakers, and much thinner per event Measured 31 Aug 2026 18:30 UTC, counting DISTINCT customer-served `bookmaker_key` per fixture over fixtures that started within the last 7 days or are still to come, excluding bookmakers withheld from customer responses. The denominator is fixtures a served book actually quoted, so a fixture nobody quoted is not averaged in. The tier sentence below is read from these same rows and the same window, so the decimals and the tier list cannot disagree. - **NBA** — 4.14 books on a quoted fixture across 21 quoted fixtures, max 6, 9.5% carrying exactly one book - **NRL** — 4.12 books on a quoted fixture across 26 quoted fixtures, max 6, 26.9% carrying exactly one book - **WNBA** — 3.63 books on a quoted fixture across 30 quoted fixtures, max 5, 10.0% carrying exactly one book - **NRLW** — 3.00 books on a quoted fixture across 6 quoted fixtures, max 3, 0.0% carrying exactly one book - **AFL** — 2.29 books on a quoted fixture across 31 quoted fixtures, max 6, 51.6% carrying exactly one book - **MLB** — 2.23 books on a quoted fixture across 107 quoted fixtures, max 3, 0.9% carrying exactly one book - **NFL** — 2.16 books on a quoted fixture across 81 quoted fixtures, max 4, 44.4% carrying exactly one book - **College Football** — 2.04 books on a quoted fixture across 90 quoted fixtures, max 3, 31.1% carrying exactly one book - **Super League** — 1.80 books on a quoted fixture across 15 quoted fixtures, max 3, 53.3% carrying exactly one book - **EPL** — 1.68 books on a quoted fixture across 31 quoted fixtures, max 2, 32.3% carrying exactly one book - **Tennis ATP** — 1.65 books on a quoted fixture across 2,701 quoted fixtures (of 2,756 in the window), max 4, 53.6% carrying exactly one book - **Tennis WTA** — 1.64 books on a quoted fixture across 1,489 quoted fixtures (of 1,556 in the window), max 4, 62.0% carrying exactly one book - **Soccer (Other Competitions)** — 1.27 books on a quoted fixture across 627 quoted fixtures (of 678 in the window), max 2, 73.0% carrying exactly one book - **AFLW** — 1.25 books on a quoted fixture across 36 quoted fixtures, max 2, 75.0% carrying exactly one book - **NHL** — 1.24 books on a quoted fixture across 21 quoted fixtures, max 2, 76.2% carrying exactly one book - **Basketball (Other Competitions)** — 1.00 books on a quoted fixture across 171 quoted fixtures, max 1, 100.0% carrying exactly one book - **MMA/UFC** — 1.00 books on a quoted fixture across 37 quoted fixtures, max 1, 100.0% carrying exactly one book - **Cricket (Other Competitions)** — 1.00 books on a quoted fixture across 20 quoted fixtures, max 1, 100.0% carrying exactly one book - **Rugby Union** — 1.00 books on a quoted fixture across 18 quoted fixtures, max 1, 100.0% carrying exactly one book - **Test Cricket** — 1.00 books on a quoted fixture across 4 quoted fixtures, max 1, 100.0% carrying exactly one book These move week to week with a short, seasonal fixture list, so read the TIER, not the decimal. Measured 2026-08-31 over the trailing 7 days, NRL, NBA, NFL, WNBA, MLB, College Football, NRLW and EPL carry more than one customer-served bookmaker on the majority of fixtures; on AFL, Tennis ATP, Tennis WTA, AFLW, Super League, Soccer (Other Competitions), Basketball (Other Competitions), NHL, MMA/UFC, Cricket (Other Competitions), Rugby Union and Test Cricket you are usually looking at a single price. The rule is one line — a sport is multi_book when at least 50% of its quoted fixtures in the window carried two or more customer-served bookmakers, single_book when it is below that, seasonal when no served book quoted any fixture, and not_queryable when the API 404s the key — so a sport is in exactly one of those lists. A multi-window stability check is not available on this data: the `events` table holds no fixture earlier than 2026-08-24T18:30:00Z, so a 14- or 30-day window returns exactly the same rows as this 7-day one. An earlier version of this section published fixed bands and claimed they "do not move with the sampling window"; that claim was withdrawn on 2026-08-18, when every multi-book sport measured above its published band two days after publication. **Two different bookmaker counts, and they are not the same number.** *Books on a sport* is how many distinct bookmakers quoted that sport anywhere in the window. *Books on one fixture* is how many quote a single match, which is what you actually get to compare a price across. The second can never be larger than the first, and it is smaller wherever a book skips a fixture. Where every book that quotes a sport quotes all of its fixtures, the two are equal — which is why several rows above carry a mean equal to the number of books quoting that sport. https://puntersedge.online/api/coverage tabulates the first; the per-fixture figures above are the second. ### Sport keys 20 sport keys are queryable — `active = true` in the `sports` table, the same flag the API's own key validation enforces, measured 2026-08-31; the catalogue holds 23 rows, and a key that is not queryable returns 404. An empty array from a sport key means out of season, not broken. There is no `horse-racing` sport key: racing has its own endpoints. The keys are `afl`, `nrl`, `nba`, `nfl`, `wnba`, `tennis_atp`, `tennis_wta`, `mlb`, `ncaaf`, `aflw`, `super_league`, `nrlw`, `soccer_other`, `basketball_other`, `soccer_epl`, `nhl`, `mma`, `cricket_other`, `rugby_union` and `cricket_test`. A-League (1 books, 59 fixtures) and Big Bash (1 books, 4 fixtures) are ingested and quoted by a served bookmaker but are not reachable: the API 404s the key. It is measured here and excluded from the coverage table rather than counted as coverage. ### Freshness — poll age and price age are different numbers Measured over the trailing 24 hours from `feed_health_history` (265-1,432 samples per book), so it is not time-of-day biased, and read from https://puntersedge.online/coverage-report.json (`poll_cadence_24h`) as this file is served — measurement timestamp 2026-08-31T18:29:06Z. **Median connector poll age** — how long since the connector last successfully read the book — across the 14 served books: 6s PointsBet, Sportsbet; 7s BetGold, BetRight, Ladbrokes, Neds, NextBet (formerly PlayUp), TAB, TABtouch, Unibet; 8s BetDeluxe, BoostBet; 14s Palmerbet; 22s Betr. Do not flatten that into one headline number. Measured over the 24 hours to 2026-08-31 (all hours) from the connector health history, the median connector poll age is 6–14 seconds for thirteen of the fourteen served books, 22 seconds for Betr; the same books measured over 7 days through the 10:00-21:00 AEST card only — a different window, so expect different figures — are on https://puntersedge.online/api/status **Median price age over the same 24 hours is 66-7129s per book.** That is not a contradiction: capture is change-only, so a price can be minutes old while the poll that confirmed it is seconds old. Do not quote one as the other. Every quote carries its own `age_seconds` — read that rather than any median here. Sports are polled every 15 minutes (held there to conserve upstream provider quota), and served from a 6-hour window, so a sports price can legitimately be hours old. Measured 2026-08-16 across the poll cycle: p50 866s, p90 11,158s, max 21,535s. **Do not build an in-play sports product on this.** Uptime: 99.98% over the trailing 30 days across 8,640 checks, read from `GET /v1/uptime` as this file was served (2026-08-31T18:29:04Z) — verifiable without a key. Read what it measures before quoting it: the probe target is https://puntersedge.online/ping, so this is the WEBSITE's availability, not api.puntersedge.online's; the endpoint says so in its own `measures` field. Per-surface figures and the methodology are at https://puntersedge.online/uptime.json and https://puntersedge.online/status. There is no contractual SLA. ### Price history depth The `race_snapshots` store began on 2026-08-04 and retains to a 45-day purge horizon it has not reached yet, so `/v1/racing/price-history` reaches back **27 days as at 2026-08-31** and grows daily until it hits 45. This figure moves every day and is read live from the `price_history_depth` field of https://puntersedge.online/coverage-report.json each time this file is served. ## 6. Honest limits - Coverage is Australian. For US sportsbooks or broad global leagues, a global provider fits better. - Sports depth is 8 bookmakers across the whole sports set, and on a given match it is usually 1 — only NRL, NBA, NFL, WNBA, MLB, College Football, NRLW and EPL carry more than one on the majority of fixtures. Racing is where the 14-book depth is. - Raw odds data, not a finished betting product: no bet placement, no account integration. - Raw redistribution of the data requires written permission. Building applications, analytics, dashboards and internal workflows on it does not. - Not official league data. For rights-cleared enterprise feeds, an enterprise provider is right. - `/v1/racing/results` carries one bookmaker's settled prices (BetRight), not each book's own, and there is no bookmaker field in the payload saying so. - Runner and race enrichment (jockey, trainer, barrier, weight, form, track condition, distance) comes from an Australian-only source. Measured 2026-08-15, it is null on 100% of international races, which are returned unfiltered alongside Australian ones. - Betfair Exchange lay prices are ingested but not served, pending a Betfair data licence. Anything needing an exchange lay leg — including back/lay racing arbitrage — is not available. ## 7. Pages - API overview: https://puntersedge.online/api - Pricing: https://puntersedge.online/api/pricing - Live measured coverage report: https://puntersedge.online/coverage-report - Coverage report, JSON: https://puntersedge.online/coverage-report.json - Coverage by sport and bookmaker: https://puntersedge.online/api/coverage - Bookmaker x sport matrix: https://puntersedge.online/odds-api-coverage - Developer quickstart: https://puntersedge.online/developers/getting-started - API reference (server-rendered HTML, no JavaScript needed): https://puntersedge.online/developers/api-reference - Errors: https://puntersedge.online/developers/errors - Rate limits: https://puntersedge.online/developers/rate-limits - Status and connector health: https://puntersedge.online/status - OpenAPI schema: https://api.puntersedge.online/openapi.json - Postman collection: https://api.puntersedge.online/postman.json - Python SDK, MIT licensed: https://github.com/Propertyscout001/puntersedge-python - Short brief: https://puntersedge.online/llms.txt Head-to-head comparisons, including cases where the competitor is the better fit: https://puntersedge.online/the-odds-api-alternative-australia, https://puntersedge.online/oddsjam-alternative-australia, https://puntersedge.online/sportradar-alternative-australia, https://puntersedge.online/betsapi-alternative-australia, https://puntersedge.online/opticodds-alternative-australia, https://puntersedge.online/theracingapi-alternative-australia, https://puntersedge.online/tab-api-alternative-australia, https://puntersedge.online/punting-form-alternative-australia, https://puntersedge.online/pinnacle-api-alternative-australia, https://puntersedge.online/bet365-api-alternative-australia ## 8. Contact and compliance - Email: hello@puntersedge.online - Terms: https://puntersedge.online/terms - Australian gambling help: 1800 858 858. 18+ only. Gamble responsibly.