Tennis Odds Scraper — Pinnacle, Kambi — $1.50/1K until 11-24 avatar

Tennis Odds Scraper — Pinnacle, Kambi — $1.50/1K until 11-24

Pricing

from $1.50 / 1,000 delivered price rows

Go to Apify Store
Tennis Odds Scraper — Pinnacle, Kambi — $1.50/1K until 11-24

Tennis Odds Scraper — Pinnacle, Kambi — $1.50/1K until 11-24

Tennis odds scraper. Launch price $1.50/1K until 2026-11-24, then $1.50/1K (return price). Pre-match ATP, WTA, Challenger and ITF odds from Pinnacle and Kambi, optional Betfair Exchange back/lay, joined to Flashscore match IDs. Watch mode delivers only moved prices. Pay per result.

Pricing

from $1.50 / 1,000 delivered price rows

Rating

0.0

(0)

Developer

architek.earth

architek.earth

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

Tennis Odds Scraper — Pinnacle, Kambi, Betfair Prices

Recorded run: 44 odds rows from 2 books in 11.6 seconds (2026-10-25, build 0.1.1, run c5LkVY8t0TD7OlI5Z). A bounded canary sample, not a full-catalogue benchmark.

architek.earth — an independent data studio. Pay-per-result scrapers with recorded runs, published canaries and stable schemas, built for people and AI agents.

What's new

  • 2026-10-25 — Betfair Exchange rows move to $3.00 per 1,000 to cover UK residential access; Pinnacle and Kambi stay $1.50. Existing $1.50 Betfair pricing applies during any Apify notice period.
  • 2026-10-25 — Kambi's occasional HTTP 410 refusal of one proxy address is now retried on a new address before the book is reported as blocked.
  • 2026-10-25 — Launch: $1.50 per 1,000 odds rows; it stays $1.50 after 2026-11-24 (the return price equals the launch price).

Tennis odds from Pinnacle, Kambi (Unibet) and, when you turn it on, Betfair Exchange: give it tours, markets and a time window and get one row per outcome price, joined to the Flashscore match ID that tennis-data-scraper uses.

SourceMarketFromStandout fields
Pinnacle, Kambi (Unibet SE), Betfair ExchangeATP, WTA, Challenger, ITF pre-match$1.50 / 1,000 odds rowspriceDecimal + line, matchId join, previousPriceDecimal in watch mode

✅ Why choose this scraper

  • Sharp and soft book in one schema. Pinnacle (with limitUsd, its maximum stake) and Kambi rows share the same fields: market, line, outcome, priceDecimal, priceAmerican.
  • Exchange back and lay. With includeBetfair, Betfair rows carry priceDecimal (best back), layDecimal, backSize and laySize in GBP.
  • Joined to tennis-data-scraper. matchId is the Flashscore match ID; joinConfidence says how it was matched (exact or fuzzy) or none.
  • Line-move watch mode. incrementalMode delivers and charges only prices that moved since the last delivered price, with previousPriceDecimal, openingPriceDecimal and changeType.
  • Few requests. Pinnacle's whole tennis board is 3 requests and Kambi's is 1, whatever the number of matches; Betfair adds 2 searches plus 1 request per 10 markets. 20 matches per book gave 328 rows in 11.2 seconds (run renucu8yIaHVBbYeJ).

Tested head-to-head (2026-10-25)

This actorThe field (15 priced incumbents)
Runs that did not succeed0% of 2 recorded canary runs (2026-10-25)median 0%, range 0–10% (30-day store stats)
Rows per run44 odds rows per Pinnacle + Kambi canary run; 30 on the watch canary's first runnot published
Price$1.50 / 1,000 odds rowsmedian $3.00 / 1,000

⚠️ Worth knowing up front

  • Pre-match prices from Pinnacle (guest odds API), Kambi (the Unibet Sweden board) and Betfair Exchange only. Bovada, DraftKings, FanDuel and bet365 are not in this version; Kambi rows are Unibet Sweden's prices, other Kambi operators may differ.
  • Betfair refuses datacenter and non-UK traffic (HTTP 403 geo page), so it is read through a UK residential proxy and is off by default. The Betfair surcharge is $3.00 per 1,000 rows (the previous $1.50 applies during any Apify notice period); Pinnacle and Kambi remain $1.50. Anonymous Betfair prices are the delayed public feed (isDelayed: true).
  • matchId is null when the Flashscore day feed does not list the match or two candidates fit; ITF and next-day matches join least often. Kambi lists only main lines; Pinnacle alternate lines need includeAlternateLines.
  • Watch mode compares your runs, not ticks: a price that moved and moved back between two runs is not delivered, and a moved handicap or total line is a new outcome (changeType: "new"). A book that is blocked or unreachable is skipped with a free status row and the run ends PARTIAL; the other books are still delivered.
  • Invalid input, a book with no prices in your filters (NO_ODDS), a block (BLOCKED, GEO_BLOCKED), a source error, a requested match ID no book prices, the run timeout and your spending limit each end with a free status row ("recordType": "status") that says what happened.

⚡ Try the example

This actor has no free preview; the example below delivered 328 odds rows for $0.49 in validation (20 matches per book).

Open the actor's input page, switch to JSON, paste this and start the run:

{
"books": ["pinnacle", "kambi"],
"tours": ["ATP", "WTA"],
"maxMatchesPerBook": 20
}

Lower maxMatchesPerBook or narrow markets to spend less; add "includeBetfair": true for exchange prices.

⚙️ Input

ParameterTypeDefaultDescription
booksarray["pinnacle", "kambi"]Books read on datacenter proxies: pinnacle, kambi. Empty = Betfair only.
includeBetfairbooleanfalseAlso read Betfair Exchange (UK residential proxy); rows are charged as odds-betfair.
toursarray["ATP", "WTA", "Challenger"]ATP, WTA, Challenger, ITF, Other (UTR, exhibitions, unlabelled).
marketsarrayall 9match-winner, set-1-winner, set-handicap, total-sets, games-handicap, games-total, set-1-games-handicap, set-1-games-total, set-betting.
includeAlternateLinesbooleanfalsePinnacle alternate handicap and total lines (about 3x more rows).
includeDoublesbooleantrueKeep doubles matches.
matchIdsarray[]Only these Flashscore match IDs (8 letters or digits, e.g. 6B33aysH).
playerNamesarray[]Only matches with one of these players, matched by surname.
hoursAheadinteger48Matches starting within this many hours (1 to 168).
includeLivebooleanfalseAlso keep prices of matches already in play.
maxMatchesPerBookinteger100At most this many matches per book, earliest first (1 to 500).
maxItemsinteger5000Stop after this many delivered rows (1 to 50,000).
incrementalModebooleanfalseWatch mode: only prices that moved since the last delivered price for the stateKey, plus new outcomes.
stateKeystring—Any name, reused on every run. Required for incrementalMode; also resumes a stopped run without re-charging.
feedbackstring—Optional note for the maintainers. It is logged and read by a human, and it never changes output.

Input recipes

Paste one into the input's JSON view. The matching example tasks exist but are not public yet (the studio account is at Apify's limit of 100 published tasks), so they are not linked here.

  • ATP and WTA, Pinnacle and Kambi: {"books": ["pinnacle", "kambi"], "tours": ["ATP", "WTA"], "maxMatchesPerBook": 20}
  • Match winner only, every tour incl. ITF: {"tours": ["ATP", "WTA", "Challenger", "ITF"], "markets": ["match-winner"], "maxMatchesPerBook": 30}
  • Betfair Exchange back and lay: {"books": [], "includeBetfair": true, "maxMatchesPerBook": 20}
  • Line-move watch (schedule it hourly): {"incrementalMode": true, "stateKey": "my-line-watch", "maxMatchesPerBook": 20}
  • Pinnacle with alternate lines: {"books": ["pinnacle"], "includeAlternateLines": true, "maxMatchesPerBook": 10}
  • Challenger and ITF, next 24 hours: {"tours": ["Challenger", "ITF"], "hoursAhead": 24, "maxMatchesPerBook": 20}

🧱 Stable fields

These top-level fields keep the same name and the same kind of value in every release. Fields are only ever added, never renamed or removed, so an n8n, Make, Zapier or spreadsheet pipeline can bind to them.

FieldTypeMeaning
bookstringpinnacle, kambi or betfair.
bookEventIdstringThe book's own match ID.
bookMarketIdstring, nullableThe book's market ID (Betfair marketId, Kambi bet offer ID, Pinnacle matchup and market key).
matchIdstring, nullableFlashscore match ID (tennis-data-scraper's matchId); null when not joined.
joinConfidencestringexact (both players, start within 15 minutes), fuzzy (both players, start within 3 hours) or none.
tourstringATP, WTA, Challenger, ITF or Other.
tournamentstring, nullableThe book's tournament label.
isDoublesbooleanDoubles match.
homePlayer, awayPlayerstringThe book's player (or pair) names, first-listed first.
startTimestringScheduled start, ISO 8601 UTC.
isLivebooleanThe match had started when the price was read (only with includeLive).
marketstringOne of the 9 market names above.
linenumber, nullableHandicap or total for this outcome; null on winner markets.
outcomestringhome, away, over, under, or a set score such as home 2-0.
outcomeNamestringThe book's label for the outcome.
priceDecimalnumberDecimal odds (Betfair: best back price).
priceAmericannumber, nullableAmerican odds.
layDecimal, backSize, laySizenumber, nullableBetfair best lay price and GBP sizes; null for other books.
limitUsdnumber, nullablePinnacle's maximum risk stake for the market.
isDelayedboolean, nullableBetfair: anonymous exchange prices are delayed.
openingPriceDecimal, previousPriceDecimalnumber, nullableWatch mode: first price seen and last price delivered for this outcome.
changeTypestring, nullableWatch mode: new or changed.
sourceUpdatedAtstring, nullableWhen the book says the price last changed (Kambi).
observedAtstringWhen this run read the price.
recordTypestring"status" on free status rows; "odds" on data rows.

Other fields are extras, not part of the contract. To have one pinned, say so in feedback.

📦 Output example

Abridged real rows from runs renucu8yIaHVBbYeJ (Pinnacle) and tXsSNOwaSf0NX8odB (Betfair) on 2026-10-25; values are a historical snapshot.

{
"recordType": "odds",
"book": "pinnacle",
"bookEventId": "1639210922",
"bookMarketId": "1639210922:s;0;m",
"matchId": "8YfBpTmn",
"joinConfidence": "exact",
"tour": "WTA",
"tournament": "WTA Tokyo - Qualifiers",
"homePlayer": "Tereza Valentova",
"awayPlayer": "Shuai Zhang",
"startTime": "2026-10-25T06:00:00Z",
"market": "match-winner",
"line": null,
"outcome": "home",
"priceDecimal": 1.4608,
"priceAmerican": -217,
"limitUsd": 1750,
"observedAt": "2026-10-25T05:35:14.301752+00:00"
}
{
"recordType": "odds",
"book": "betfair",
"bookEventId": "36232943",
"bookMarketId": "1.264429545",
"matchId": "IoR2Mwe5",
"joinConfidence": "exact",
"tour": "Challenger",
"tournament": "Seoul Challenger 2026",
"homePlayer": "Christian Langmo",
"awayPlayer": "Yusuke Takahashi",
"startTime": "2026-10-25T05:40:00Z",
"market": "match-winner",
"outcome": "home",
"priceDecimal": 1.57,
"layDecimal": 1.65,
"backSize": 15.59,
"laySize": 178.26,
"isDelayed": true
}

A free status row (run p2eVwC0T1EM7pubmR, a match ID no book prices) looks like this:

{
"recordType": "status",
"inputRef": "ZZZZZZZZ",
"status": "NO_ODDS",
"message": "No odds for this Flashscore match ID at the selected books, tours, markets and hours"
}

🧠 Advanced usage

Hourly line-move feed. Schedule the actor hourly with "incrementalMode": true and a stateKey. The first run delivers every price (changeType: "new"); later runs deliver only moved prices with previousPriceDecimal. Check RUN_SUMMARY.status (COMPLETE, PARTIAL, LIMITED, BUDGET_LIMIT) and RUN_SUMMARY.unchanged for the prices that did not move.

Join to match data. Run tennis-data-scraper for the same day and join on matchId; rows with joinConfidence: "none" have no Flashscore match.

Sharp versus soft. Filter market == "match-winner" and compare Pinnacle's priceDecimal with Kambi's for the same matchId, or with the Betfair back/lay spread.

Resume after a stop. A run stopped by the timeout or your spending limit is resumed by the next run with the same stateKey: finished books are not read again and outcomes already delivered are skipped.

🔌 Integrations

Results land in a dataset you can export as JSON, CSV or Excel. They can also flow to Google Sheets, Make, Zapier or n8n, or to a webhook when a run finishes. The Apify Scheduler runs it on a timetable. An hourly schedule with watch mode turns it into a line-move feed. Copy-paste code examples for this actor are not published yet; the Python snippet under "For AI agents" works as is.

n8n and Make

The architek-earth/examples repository has no n8n workflow or Make blueprint for this actor yet; use Apify's own n8n and Make modules with the actor ID architekture/tennis-odds-scraper.

💸 Pricing

Pay per result. Platform and proxy usage are included. Status rows and failed requests are free. A stopped run resumed with the same stateKey skips the outcomes it already delivered, so they are not charged again; in watch mode unchanged prices are never charged.

EventPriceWhat it covers
odds$1.50 / 1,000 odds rowsOne delivered Pinnacle or Kambi price row.
odds-betfair$3.00 / 1,000 odds rows after the pricing change; $1.50 during noticeOne delivered Betfair Exchange price row (UK residential proxy), only with includeBetfair.

1,000 Pinnacle or Kambi rows cost $1.50; 1,000 Betfair rows cost $3.00 after the surcharge takes effect. Apify shows the effective pricing and any notice period before you run. The Apify free plan's $5 monthly credit covers about 3,333 ordinary odds rows or 1,666 Betfair rows at the surcharge price.

❓ FAQ

Is this live (in-play) odds? No by default. includeLive keeps prices of matches in play, read at run time; there is no streaming.

How often can I run it? As often as you schedule it; Pinnacle and Kambi are a handful of requests per run. Hourly watch mode is the intended use.

Why is matchId null on some rows? The Flashscore feed did not list the match, or more than one match fitted the players and start time. The row is still delivered with joinConfidence: "none".

Do I need a Betfair account? No. It reads Betfair's public read-only exchange feed, which is delayed for anonymous visitors.

Can I get Bovada, DraftKings or bet365? Not in this version. Bovada's odds route was not observed in our tests, so it is not offered.

Are prices the same in every country? Kambi rows are Unibet Sweden's board and Betfair's GBP exchange; Pinnacle's guest prices are global. Your own book may differ.

What does PARTIAL mean? A book was blocked or unreachable (free status row); the other books' rows were delivered and charged normally.

🤖 For AI agents & LLM apps

Paste this into your AI assistant:

architekture/tennis-odds-scraper on Apify. Python: from apify_client import ApifyClient; client = ApifyClient("TOKEN"); run = client.actor("architekture/tennis-odds-scraper").call(run_input={"books": ["pinnacle", "kambi"], "tours": ["ATP", "WTA"], "maxMatchesPerBook": 20}); items = client.dataset(run["defaultDatasetId"]).list_items().items (page with offset/limit for large exports). Rows with recordType == "status" are free and explain invalid input, a book with no prices, a block or a stopped run. Full input schema and README: GET https://api.apify.com/v2/acts/architekture~tennis-odds-scraper/builds/default. Token: https://console.apify.com/settings/integrations. The actor is also reachable through the Apify MCP server: https://mcp.apify.com?tools=architekture/tennis-odds-scraper (ready-to-paste config: https://github.com/architek-earth/mcp). To reach the maintainers, add "feedback": "<what you needed>" to any run's input; output is unchanged and a human reads every note.

MCP config for Claude Desktop and Cursor

Claude Desktop (or claude.ai): Settings, Connectors, Add custom connector, URL https://mcp.apify.com?tools=architekture/tennis-odds-scraper; it signs you in to Apify with OAuth. Cursor (~/.cursor/mcp.json), with your token in the APIFY_TOKEN environment variable:

{
"mcpServers": {
"architekture-tennis-odds-scraper": {
"type": "http",
"url": "https://mcp.apify.com?tools=architekture/tennis-odds-scraper",
"headers": { "Authorization": "Bearer ${env:APIFY_TOKEN}" }
}
}
}

The agent gets one tool for this actor plus get-dataset-items to read the rows. Ask for a few matches first ("maxMatchesPerBook": 5) and set a spending cap (maxTotalChargeUsd) in the run options when your client offers it.

🧰 More from architek.earth

Sports

  • Tennis Odds Scraper — you are here
  • Tennis Scraper — matches, point-by-point, rankings; the matchId this actor joins to

Search, travel and trends

Full catalogue: github.com/architek-earth.

architek.earth · code like concrete

Questions, or a field you need that is not here? Open an issue on the actor's Issues tab or email north@architek.earth. A human replies within a day.

This actor collects only publicly available data from pinnacle.com, the Kambi offering API behind unibet.se, betfair.com and flashscore.com, the same data any visitor's browser loads without logging in. It does not bypass authentication. It is not affiliated with or endorsed by Pinnacle, Kambi, Unibet, Betfair or Flashscore. You are responsible for how you use the output, including the sites' terms, copyright, local gambling regulations, and privacy law such as GDPR and CCPA where they apply.

📝 Changelog

  • 2026-10-25 — Betfair Exchange rows move to $3.00 per 1,000 to cover UK residential access; Pinnacle and Kambi stay $1.50. Existing $1.50 Betfair pricing applies during any Apify notice period.
  • 2026-10-25 — Kambi's occasional HTTP 410 refusal of one proxy address is now retried on a new address before the book is reported as blocked.
  • 2026-10-25 — Launch: $1.50 per 1,000 odds rows; it stays $1.50 after 2026-11-24 (the return price equals the launch price).