Developers / Affiliate links
Store your tracking link for each bookmaker once. Every racing price then comes back with your link already built, ready to put on your page.
One PUT /v1/link-templates stores a template for each bookmaker: your affiliate tracking link, with {url} where the bookmaker's page for the race goes.
From your next request, every price on next-to-go, best-odds and markets carries source_link beside source_url: your tracking link with that race already inside it. Betslip links get the same.
Available on Plus and above. Storing, reading and removing templates costs no credits.
A template is your tracking link with a placeholder where the bookmaker's own page for the race goes. When a price is served, the placeholder is replaced with that price's source_url.
| Placeholder | Inserts |
|---|---|
{url} | The bookmaker's URL, percent-encoded. Use this when the URL goes inside a query parameter of your tracking link, which is almost always. |
{url_raw} | The bookmaker's URL exactly as it is, for a tracker that expects it unencoded. |
A template must start with https://, contain one of the two placeholders, and be 500 characters or fewer. Nothing of ours is added to it: no tracking parameters and no redirect through our servers.
The templates go in one document:
per_bookmaker: a rule for each bookmaker key. The rule is a template, "source" to link straight to the bookmaker, or "none" for no link. Bookmaker keys are the key on each bookmaker entry in /v1/racing/next-to-go, for example ladbrokes_au, neds, unibet and tab. An unknown key is refused with a 422 that names it, so a typo cannot quietly drop a book's link.mode: what a bookmaker without its own rule gets. "source" (the default) links straight to the bookmaker, "template" uses template below, and "none" gives no link.template: the template for every book without its own rule, when mode is "template".{
"mode": "source",
"per_bookmaker": {
"ladbrokes_au": "https://your-tracker.example/click?aff=YOUR_ID&dest={url}",
"unibet": "https://your-tracker.example/go?pid=YOUR_ID&url={url}",
"playup": "none"
}
}
Here Ladbrokes and Unibet prices link through your tracking links, PlayUp prices carry no link, and every other bookmaker links straight to its own race page.
curl -X PUT "https://api.puntersedge.online/v1/link-templates" \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "source",
"per_bookmaker": {
"ladbrokes_au": "https://your-tracker.example/click?aff=YOUR_ID&dest={url}",
"unibet": "https://your-tracker.example/go?pid=YOUR_ID&url={url}",
"playup": "none"
}
}'
The response returns what was stored, and a preview of each rule built against an example address, so you can check the encoding before anything reaches your page:
"preview": [
{"bookmaker": "ladbrokes_au",
"source_url": "https://www.bookmaker.example/racing/race/1",
"source_link": "https://your-tracker.example/click?aff=YOUR_ID&dest=https%3A%2F%2Fwww.bookmaker.example%2Fracing%2Frace%2F1"},
{"bookmaker": "playup", "source_url": "https://www.bookmaker.example/racing/race/1",
"source_link": null},
{"bookmaker": "unibet", ...},
{"bookmaker": "(every other book)",
"source_url": "https://www.bookmaker.example/racing/race/1",
"source_link": "https://www.bookmaker.example/racing/race/1"}
]
The same from Python, with requests:
import requests
API = "https://api.puntersedge.online"
HEADERS = {"X-API-Key": "YOUR_KEY"}
templates = {
"mode": "source",
"per_bookmaker": {
"ladbrokes_au": "https://your-tracker.example/click?aff=YOUR_ID&dest={url}",
"unibet": "https://your-tracker.example/go?pid=YOUR_ID&url={url}",
"playup": "none",
},
}
r = requests.put(f"{API}/v1/link-templates", json=templates, headers=HEADERS, timeout=20)
r.raise_for_status()
for p in r.json()["preview"]:
print(p["bookmaker"], "->", p["source_link"])
A PUT replaces the whole document, so send every rule each time. It takes effect on your next request.
Use the *_link field as the link for the price. It is present when there is something to link. When it is absent, the bookmaker gave no race link or your rule for that book is "none": show the price without a link.
source_link, built from source_url: on each bookmaker entry in /v1/racing/next-to-go, on each quote in /v1/racing/best-odds (best_win, best_place and the rest), and on each runner in /v1/racing/markets.betslip_win_link and betslip_place_link, built from betslip_win_url and betslip_place_url: in /v1/racing/next-to-go and /v1/racing/changes.betslip_link, built from betslip_url: in /v1/racing/best-odds.The *_url fields are unchanged, so code that already reads them keeps working. From Python, every price on the next three Australian horse races with its link:
import requests
API = "https://api.puntersedge.online"
HEADERS = {"X-API-Key": "YOUR_KEY"}
races = requests.get(f"{API}/v1/racing/next-to-go", headers=HEADERS, timeout=20,
params={"country": "AU", "categories": "horse", "num_races": 3}).json()
for race in races:
for runner in race["runners"]:
for leg in runner["bookmakers"]:
link = leg.get("source_link") # absent: show the price without a link
print(race["venue"], race["race_number"], runner["name"], leg["key"],
leg.get("win_price"), link or "(no link)")
From JavaScript on your server, the win market for the next horse race as price cells, each linked through your template where it has a link:
// Node 18+ (fetch is built in). Run it on your server: never put your API key in browser code.
const API = "https://api.puntersedge.online";
const headers = { "X-API-Key": process.env.PE_API_KEY };
const races = await (await fetch(
`${API}/v1/racing/next-to-go?country=AU&categories=horse&num_races=1`, { headers })).json();
const { markets } = await (await fetch(
`${API}/v1/racing/markets?race_id=${races[0].race_id}`, { headers })).json();
const win = markets.find((m) => m.market === "win");
for (const book of win.bookmakers) {
for (const r of book.runners) {
const price = r.price.toFixed(2);
const cell = r.source_link
? `<a href="${r.source_link}" rel="sponsored noopener" target="_blank">${price}</a>`
: price;
console.log(book.key, r.number, r.name, cell);
}
}
Mark affiliate links rel="sponsored", which is how Google asks for paid links to be labelled.
Some bookmakers also return a betslip link that opens with the runner already selected: Unibet and TAB in a web browser, Ladbrokes and Neds in their apps. Your templates apply to these as well. Ladbrokes and Neds betslip links carry betslip_app_only: true. In a desktop browser they go to the app download page, so on desktop use source_link for those two.
GET /v1/link-templates returns the stored document, the fields and endpoints it applies to, and the preview. Any key on the account can read it.PUT the whole document again. Changing an affiliate ID needs nothing redeployed on your site.DELETE /v1/link-templates removes them. The *_link fields stop appearing from your next request.ETag on /v1/racing/next-to-go changes when your templates do, so a conditional request never answers 304 with your old links.*_link fields stop appearing and the stored templates are kept for when it moves back."source" to link its race page directly, or "none".Templates change where a price links, not what your licence asks of the page. From section 8.3 of the terms:
Wherever the Display or Commercial licence lets you show our prices to other people, every surface that displays them must carry a visible Prices by PuntersEdge credit linking to puntersedge.online, legible at the size it is shown.
Where our responses name the bookmaker a price came from, you must not attribute it to a different bookmaker or imply that any bookmaker supplies or endorses you.
Because each template belongs to one bookmaker, every link already goes to the book that quoted the price. The white-label widget on Platform is the version without the credit.
Getting started · API reference · Freshness · Rate limits · Widgets · Data licence