Get the devlog by email
One email a week, only when an issue is published. Nothing else goes to this list, and every email carries a one-click unsubscribe.
Prefer a reader? JSON Feed · RSS
Issue #6
Issue #5 went out on Monday morning; this covers Monday afternoon to Thursday morning. Three things for everyone. A page that explains every endpoint in plain English, grouped by the job you are doing, with what each returns, what it costs and which plan has it. Every racing endpoint now accepts both spellings of the category filter, because two evaluations in one week were refused for guessing the wrong one. And the acceptance fields behind the live board are keyed per race, so a horse engaged twice on one day is enriched in both races instead of in neither. Also: the free tier is 3,000 credits, annual billing exists, large next-to-go requests are priced by size for new keys, the race markets endpoint shipped, the forward window on events and best odds grows with the plan, greyhound history is complete for all seven states, and a free best-odds board is on the site. Where something has no usage yet, it says so.
Every endpoint, in plain English
- puntersedge.online/endpoints is live. Seventy-five calls in nine groups, ordered by the job you are trying to do: watch the live market, settle and score, form, the permanent archive, sports, your key and account, files and widgets and webhooks, health, and the sandbox. Each call says what it returns, when to reach for it, what a call costs and which plan has it, with a link to its reference page and an example request.
- Why. Measured on Monday: a paying key touches 5.5 of the roughly thirty endpoints on average, and the two newest Plus features, closing line value scoring and the race markets view, had no paying caller a month after shipping. The reference documents each operation exactly; nothing said, per endpoint, what it is for. This does. It is the page to read before the reference.
- Nothing numeric on it is typed. The operation list, paths and example requests are read from the live schema; the credit cost comes from the API's own cost sentence, then a scan of the billing code; the plan gates come from the same live gate sets the pricing page uses. Six tests hold it to the schema: no prose for an endpoint the API no longer publishes, no endpoint the page says nothing about, no typed credits or plan names. It is linked from the footer, the about page, the API page, the developer hub, the reference index and the key console.
Both spellings, one rule
Asked by two evaluations that were refused with a hard 422 in one week for guessing the wrong plurality.
- Every racing endpoint accepts
categories= and category=. Six live-board routes and the change feed took the plural; the two archive routes took the singular; the guard that refuses an unknown parameter, correctly, turned the disagreement into a hard 422. One trial hit it on next-to-go and stopped evaluating the next day; our own smoke test hit it the other way on closing lines. Both spellings now work everywhere, and the guard still protects you from a misspelt filter being silently ignored and billed. - An unknown value is still refused, free.
greyhounds, thoroughbred and dogs get a 422 naming the three valid values, horse, greyhound and harness, and nothing is charged. The alternative, folding the plural quietly, would bill you for an empty result that looks like a quiet market. Six racing routes used to do exactly that for an unknown value; they now refuse it, as the change feed always has. - Four documents said
/v1/racing/events takes date=. It never did. A customer read one of them, sent it, took the 422 and fell back to a 24-hour window. All four copies are corrected on the live docs; an earlier fix had corrected one of five. date= is accepted by results, closing lines and acceptances, and now by events and best odds as an alias for the forward window described below. - Next-to-go serves every country by default. Its description used to say Australia and New Zealand. Measured over ten days, 60.8% of the race catalogue is outside them, and a race outside carries a median of one bookmaker against thirteen for an Australian one, so an Australian platform polling unfiltered spends most of its credits on one-book foreign races. Pass
country=AU. The description, the MCP tools and the welcome email now say so on the first call. A customer asked for an explicit field size on the same endpoint: the runners array already excludes scratched runners, which are returned separately under scratchings, so the current field is the length of one and the declared field the sum of both.
A horse in two races
- The problem, measured. The acceptance fields behind the live board, jockey, trainer, barrier, weight and form for every thoroughbred runner, were stored one row per horse per day, keyed with no race number. A horse declared in one race and moved to another at the same meeting kept its old row, so the runner in the new race took the old race's barrier. Over three days of settled results, 1,080 runners matched a stored row at the right meeting, 1,076 agreed on the race number and 4 did not, and every one of the 4 had been served a wrong barrier.
- Tuesday night, the guard. The enrichment now also checks the race number and declines a fill that disagrees, and counts what it refuses. The first decline landed within minutes: a horse priced in Goulburn race seven whose stored row said race four.
- Wednesday night, the table. The row is now the slot: date, venue, race and horse. A horse engaged twice on one day is two rows, each true about its own race, and the board picks the one whose venue and race match. The first collection under the new key wrote 481 acceptances and dropped none as ambiguous; the pass before it had written 449 and dropped 14, which were the dual engagements this closes. Thursday's card holds 18 horses in two races each, Artemex at Goulburn and Hawkesbury among them, and both of their runners are now enriched. Under the old key those horses had no jockey, barrier or weight in either race.
- Sportsbet's runners had been bare for three weeks. Enrichment runs in two processes and the switch was set on one. Every polled bookmaker's runners carried the official fields; the pushed feed's did not, all 327 of them on the day it was measured, and nothing logged it. It now defaults on in both, and turning it off says so in the log.
Plans and prices
- The free tier is 3,000 credits a month, from Wednesday morning. It was 1,500. Every free key has the new allowance now; nothing to do. For about a day the pricing card still said 1,500 while the FAQ below it said 3,000; that card is now tested against the API's own plan table so the two cannot disagree again.
- Annual billing, on every paid plan. Ten months' price for twelve months of service; credits still reset monthly and the allowance does not pool. Nobody has taken it yet. A promotion code cannot be applied to a yearly term: every code we issue is a first-invoice discount, and on an annual term the first invoice is the whole year, so both checkout doors refuse the combination rather than sell a year at half price by accident.
- Large next-to-go requests are priced by size, for new keys. Two credits for up to 20 races, three for 21 to 60, four for 61 to 200. The default request of ten races costs what it always did, and every key created before 22 September pays two at any size. The reference table and the endpoint's own cost line said a flat two until Wednesday night; both now state the tiers.
- The forward window grows with the plan. On events and best odds, Free and Hobby reach 24 hours ahead, Standard 48, Plus and above seven days, and a request past the ceiling is a free 403 that names the tier that reaches further rather than a silently clamped answer. Both endpoints take
hours_ahead and a date= alias, and the best-odds endpoint's six-hour window is now its default, not its limit. That is why the new Melbourne Cup build page leads on the horizon: a free key cannot see the Cup field until the afternoon before.
Race markets, shipped
Asked by the exotics and Same Race Multi requests that have sat in the open list since issue #4.
GET /v1/racing/markets, Plus and above, 2 credits. One race, every market the panel quotes on it, book by book: the win price, the place price and the Same Race Multi Top 2, Top 3 and Top 4 from the four books that quote them, each book's market percentage, the best price per runner, and once the race has run, the settled exotic dividends. Ask by race id, or by venue, date and race number.- It is captured every minute, because the live table forgets. Runners are purged ten minutes after the jump and closing lines carry win and place only, so a capture keeps the last close of every market: 80,250 rows over 1,899 races since Sunday. After the jump the stored close is served, not the decaying live table. Two bulk datasets go with it, markets and exotic dividends, the latter backfilled to 15 August.
- No paying customer has called it yet, and closing line value scoring has been called once in the week, by a free key. Both are on the endpoints page now; if they are not what you needed, reply and say what is.
On the site
- A free best-odds board. puntersedge.online/best-odds: search a race or a game, see every bookmaker's current price with the best highlighted, and the exact API request behind the view printed under it, with a button that runs it in the playground. At launch it showed 60 races across 14 books and 240 games. It is fed by the site's own key, cached, and refreshed only while someone is looking, so no visitor can spend the month's allowance.
- The playground grew up. Nine keyed endpoints, results, horse runs, greyhound form, closing lines, movers, acceptances, track conditions, venues and premierships, each shown by example from the API's own documented response; a Node SDK tab beside curl and Python; and links that open the sandbox on a real request from the best-odds board.
- Racing leads. Every default, example and reference now puts racing first and sports second, which is the order customers actually buy in. The comparison pages carry figures, including the ones we lose on. The llms.txt file that assistants read undercounted the MCP server's tools by four and now lists thirty.
Plumbing, and what went wrong
- Greyhound history is complete for all seven states. The 90-day sweep finished on Monday: 69,753 runs from 23 June to 12 September, 11,157 dogs, 46 tracks; New South Wales 21,577, Victoria 22,286, Queensland 13,092, South Australia 5,307, Western Australia 4,319, Tasmania 2,443, the Northern Territory 729. Form and stats read the whole window.
- TAB betslip links are back. The daily check had failed since 8 September because TAB blocks the server's browser, not because the link changed; it now runs from a machine TAB serves and passed on Wednesday. Ladbrokes and Neds open a native app and are deliberately not machine-checked.
- The venue-split alarm could not ring. The detector that finds one meeting stored as two races ran every night and its alert went nowhere, because the job never loaded the messaging token; it now fails loudly and timestamps its log.
- Cancellation reasons. The billing portal has asked why on every cancellation all along, with four options; the answer landed on the subscription record and nothing read it, so the one answer we had, too expensive, sat unread for ten days. Eight options now, and the answer is written against the key and surfaced weekly. If you cancel, say why; it is read.
- The racing worker restarted 22 times in seven days, 14 of them in the last day, and the cause is fixed. Systemd brought it back within five seconds each time; ten of the fourteen came in a 22-minute burst just after midnight Sydney time on Thursday, each costing a poll or two of ingest, and its stall watchdog never fired because the process was not stalled, it was dying. The trace ends in the events writer: the racing poll gathers every bookmaker's fetch and passes failures back as values, but a fetch cancelled on timeout comes back as a cancellation, which is not an exception in Python's hierarchy, so it passed the error check and was handed to the writer as if it were a list of races. The check now catches both. Fixed and restarted on Thursday morning; the counter starts again from zero.
Still open
- Saturday 12 September's result fields. Still 55 of 104 with the full field; the backfill pass has not been run. Every day since has filled: Monday 22 of 22, Tuesday 29 of 29, Wednesday 37 of 37 thoroughbred races; greyhounds Wednesday 133 of 138.
- Racing Australia's form wall. Every fresh form read since Monday morning has been answered with a bot challenge, 72 hours at the time of writing; 945 horses queued.
/v1/racing/horses/runs answers from our own results in the meantime. - The racing worker's restarts. Fixed on Thursday morning; watching that the count stays at zero through Saturday's card.
- Unibet's day card is still off. The lane shipped on 14 September and is switched off pending the duplicate-race check; Unibet sits on its nearest-20-per-code cap, 32 races on Thursday morning against TAB's 64.
- Overage has never billed anyone. Off for every existing key until switched on; the first invoice that can carry it is 1 October.
- MCP 0.4.0. Built, with the race markets tool and the doc corrections; PyPI still serves 0.3.0 until it is uploaded. The Node client is 1.2.0 on npm.
- Race markets and closing line value have no paying callers. Shipped, documented, unused; see above.
- Unchanged: neighbour-matched duplicate races; Hong Kong, prices only; the showcase has no entries; BSP and traded volume wait on the Betfair data licence; the compare pages are 82% boilerplate.
Reply with anything you want on the list. The two changes this issue that matter most to anyone building on the racing data are the endpoints page, which finally says what each call is for, and the per-race acceptance key, which means the board no longer has to choose between a wrong barrier and no barrier. Write to hamish@punters-edge.com or use the feature request form.