You can run the Live Tennis API on a schedule — no always-on server, no cron-plus-curl glue — with Kestra, an open-source orchestrator. Two ready-made Kestra blueprints do exactly this: one pulls the daily fixture slate once a day, the other polls live scores on a windowed schedule. Both run on the free Live Tennis API tier.
This is the ingestion side: fetch tennis data on a timer, reshape it, and hand a clean file to the rest of your pipeline — a warehouse load, a notification, a dashboard. Kestra gives you the trigger, retries, a reshape step, internal storage, and full run history in one reviewable YAML file.
What the free tier gives you
Base URL: https://api.livetennisapi.com/api/public/v1. Every request needs the header X-API-Key: <your-key>. Grab a free key at livetennisapi.com/subscribe/free — self-serve, no card, 30 req/min and 100 req/day.
| Endpoint | Returns |
|---|---|
GET /matches?status=live |
every match in play right now |
GET /fixtures |
the upcoming scheduled slate |
GET /matches/{id}/score |
current score for one match |
GET /usage |
your own rate/quota counters |
The free tier serves the current state — the live board and the fixture slate. Completed-match history and point-by-point tape are paid tiers; scheduled ingestion of the live state needs none of them.
Blueprint 1 — pull the daily fixtures on a schedule
The pattern: a Schedule trigger fires once a day, an http.Request task calls GET /fixtures, a JSONata TransformValue reshapes the response, and a Write task persists it to internal storage. The API key lives in the LIVETENNISAPI_KEY secret — never hardcoded, and (following Kestra's guidance) the auth header sits directly on the task rather than in pluginDefaults.
id: tennis-fixtures-daily
namespace: company.team
inputs:
- id: tour
type: SELECT
defaults: ""
required: false
values: ["", atp, wta, challenger, itf, juniors]
tasks:
- id: fetch_fixtures
type: io.kestra.plugin.core.http.Request
uri: "https://api.livetennisapi.com/api/public/v1/fixtures{{ inputs.tour != '' ? '?tour=' ~ inputs.tour : '' }}"
method: GET
headers:
X-API-Key: "{{ secret('LIVETENNISAPI_KEY') }}"
Accept: "application/json"
retry:
type: exponential
interval: PT2S
maxAttempts: 3
- id: build_fixtures
type: io.kestra.plugin.transform.jsonata.TransformValue
from: "{{ outputs.fetch_fixtures.body }}"
expression: |
(
$fixtures := $exists(fixtures) ? fixtures : ($exists(data) ? data : []);
{ "count": $count($fixtures), "fixtures": $fixtures.{
"id": id, "tour": tour, "status": status,
"p1": players.p1.name, "p2": players.p2.name } }
)
- id: store_fixtures
type: io.kestra.plugin.core.storage.Write
content: "{{ outputs.build_fixtures.value | json }}"
extension: ".json"
triggers:
- id: every_morning
type: io.kestra.plugin.core.trigger.Schedule
cron: "0 7 * * *"
One call a day sits far inside the 100/day budget, leaving nearly the whole quota for a live-scores poll alongside it.
Blueprint 2 — poll live scores during match hours only
The live board changes constantly, but you don't need to poll it 24/7. A windowed schedule (every 15 minutes, 09:00–22:45) reads GET /matches?status=live, and a RemoveDuplicates-style dedup keeps only matches newly in progress. The response envelope is { "data": [ ... ] }; each match carries players.p1.name / players.p2.name, status, and a nested score object.
- id: fetch_live
type: io.kestra.plugin.core.http.Request
uri: "https://api.livetennisapi.com/api/public/v1/matches?status=live"
method: GET
headers:
X-API-Key: "{{ secret('LIVETENNISAPI_KEY') }}"
triggers:
- id: match_hours
type: io.kestra.plugin.core.trigger.Schedule
cron: "*/15 9-22 * * *"
*/15 9-22 * * * is roughly 56 calls a day — comfortably inside 30 req/min and 100 req/day. Continuous 60-second polling (~1,440/day) does not fit a free key; for per-second freshness, that's the WebSocket stream on the Ultra tier, not a tighter cron. Always read GET /usage to see your remaining budget rather than guessing.
Why orchestrate this with Kestra
A cron curl writing a file has no retry policy, no reshape step, and no memory of past runs. Kestra supplies the Schedule trigger, per-task retries, a declarative JSONata reshape, internal storage to hand the result downstream, and full lineage for every run — all in one YAML file you can review and version.
Get the blueprints
Both flows are in the official Kestra Blueprints library — import them, add your LIVETENNISAPI_KEY namespace secret, and run. Kestra itself is open-source and self-hostable; start at kestra.io.
Links
- Kestra: https://kestra.io
- Kestra Blueprints: https://kestra.io/blueprints
- Live Tennis API docs and free key: https://livetennisapi.com
- HTTP Request task: https://kestra.io/plugins/plugin-core/tasks/http/io.kestra.plugin.core.http.request
- JSONata TransformValue: https://kestra.io/plugins/plugin-transform/tasks/jsonata/io.kestra.plugin.transform.jsonata.transformvalue