10 guides
Match results with a status and a fixed day boundary
ATP, WTA, Challenger and ITF results from TennisExplorer: a derived match status the source never labels, a UTC day boundary that does not move with the reader's timezone, and history back to 1995.

Scrape tennis results
A walkthrough of extracting tennis data: one entity type per run, the day boundary that has to be pinned, and the two enrichments that cost an extra request each.

The tennis API question
The tours publish rankings and draws for readers, not as APIs. Commercial feeds are licensed per use. What is left is reading a results site, and what that does and does not entitle you to.

Deriving match status
An unfinished match just shows a scoreline that never closes. Working out which ones those are, without being told whether a match is best-of-3 or best-of-5, across 2,364 matches.

The moving day boundary
TennisExplorer cuts its day using a timezone cookie, so two runs of the same date can legitimately disagree. Pinning every request to UTC is what makes a dataset reproducible.

Truncated doubles names
The visible team label in doubles is cut short — `Roger-Vas` rather than the two full surnames. The complete names are in the markup, and that is 17.4% of all rows.

Backfilling history
History goes back to 1995 with the same input. What a season actually costs, why the price has two tiers, and the settings that decide how long it takes.

Rankings
Singles, doubles and the season race, for the current week or a past one — with the constraint that a historical week has to be a date the source actually published.

Player profiles
Profiles with as many past seasons as you ask for, one request per season each — and a deliberate reduction for players under 18.

Field reference
What each of the four entity types returns, which fields are derived rather than read, and the one column that is recounted when the source publishes a flag instead.

Why odds are off
The same results page carries match scores and bookmaker odds, and the two sit in different positions. Why one is on by default and the other is not.