TV Fixtures API

BP_DOCS_NAME_HTML TV Fixtures

API reference · OpenAPI · Plain Markdown

What you get

An API for fixtures, kickoff times, competitions and TV channels across every available sport. There is no viewing app, video or right to rebroadcast. The backoffice manages keys and billing. The api plan has a seven-day free trial with no payment details. Prices are sent by invoice or shown by the reselling partner. It includes every language available, a rolling window from yesterday through ten days ahead, 60 requests per minute, no streams, and up to three live keys. Check /tv-fixtures/v1/plans for the current scope and /tv-fixtures/v1/status for data freshness before importing. Availability of a channel listing does not guarantee reception in your region.

The primary API origin is https://dev.feed.sportlive.hr, with product prefix /tv-fixtures/v1. The old https://dev.feed.sportlive.hr/tv-fixtures/v1 is a deprecated alias, served without redirects. On a partner account use the API origin returned with your credentials.

Shell
BASE=https://dev.feed.sportlive.hr
curl "$BASE/tv-fixtures/v1/plans"
curl "$BASE/tv-fixtures/v1/status"

API reference: GET /tv-fixtures/v1/plans GET /tv-fixtures/v1/status

Get a key

You can sign up on the offer page or use HTTP on the creator's store. For the following two calls set BASE=https://store.dev.tantu.stream. First find the offer whose slug is tv-fixtures. Read its terms.url, price, plan, trial length and availability. Set TERMS_VERSION to its exact terms.version; only accept terms you have read. Replace the example email with your own.

Shell
BASE=https://store.dev.tantu.stream
curl "$BASE/api/v1/offers"
curl "$BASE/api/v1/offers/tv-fixtures/signups" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: tv-fixtures-signup-20261021-01" \
  -d '{"email":"[email protected]","termsAccepted":true,"termsVersion":"'$TERMS_VERSION'"}'

API reference: GET /api/v1/offers POST /api/v1/offers/{slug}/signups

Keep the same Idempotency-Key and body when retrying this signup. Use a fresh key for a new signup. Do not fill the optional website field. When next.type is check_inbox, confirm your email first and follow the returned next step. A checkout next step needs payment in the browser; thanks has no further action. Do not assume every offer starts a trial.

For next.type=welcome, request the returned next.url with Accept: application/json. The following URL illustrates its shape: set HANDOFF to the last segment of that URL, and keep its exact host, slug and path. A handoff expires after 30 minutes. The JSON answer has welcome.state and welcome.revealUrl. POST to that exact reveal URL with the handoff in the body. The example shows the same creator store host.

Shell
curl "$BASE/o/tv-fixtures/start/$HANDOFF" -H "Accept: application/json"
curl "$BASE/o/tv-fixtures/welcome/reveal" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"handoff":"'$HANDOFF'"}'

API reference: GET /o/{slug}/start/{handoff} POST /o/{slug}/welcome/reveal

Save login, password, apiKey, apiBaseUrl and docsUrl securely: credentials are revealed once. Do not log this response or put the key in source control. A consumed handoff cannot reveal credentials again. Partner storefronts use the browser handoff; credentials are revealed on the creator's page, never through a partner frontend.

First call

Set BASE to the origin of the returned apiBaseUrl (normally https://dev.feed.sportlive.hr), removing a trailing /tv-fixtures product path if present. Set API_KEY to the revealed apiKey in your local environment. The examples below append the product path themselves.

Shell
BASE=https://dev.feed.sportlive.hr
curl "$BASE/tv-fixtures/v1/me" -H "X-API-Key: $API_KEY"

API reference: GET /tv-fixtures/v1/me

Then read one fixture:

Shell
curl "$BASE/tv-fixtures/v1/fixtures?limit=1" -H "X-API-Key: $API_KEY"

API reference: GET /tv-fixtures/v1/fixtures

The answer includes tenant, product, plan, status, trialEndsAt, payUrl, limits and scope. Use the returned limits rather than assuming the plan will never change. The keyless /tv-fixtures/v1 index links to this guide, OpenAPI, offer and status.

Fixtures

Shell
curl "$BASE/tv-fixtures/v1/fixtures?limit=100&sport=Soccer&lang=de" \
  -H "X-API-Key: $API_KEY"

API reference: GET /tv-fixtures/v1/fixtures

fixtures is an array; nextCursor is a string or null on the last page. Fixtures are ordered by kickoff then stable id. Pass the previous nextCursor as cursor, URL-encoded, with identical filters. Page size is 1..2000 (default 500). Treat the cursor as opaque. from and to are inclusive kickoff instants; use ISO-8601 UTC values ending in Z. updatedSince is inclusive and refers to updatedAt, not kickoff. sport is the original sport label, case-insensitive, and does not follow lang.

lang selects the single names: requested language, else English, else German, else any known language. homeNames, awayNames, sportNames, competition.names and channel names retain every known translation. Never join by a translated name.

For incremental sync, fix from and to for an entire paginated pass, keep the prior watermark unchanged while paging, and upsert each fixture by id. Only after all pages succeed persist the largest updatedAt seen. Begin the next pass with that inclusive watermark, so repeats at the boundary are expected. On failure retry the same pass; never advance the saved watermark after a partial import. Delete locally on status=removed. Removed fixtures remain available for 24 hours. If offline longer, rebuild the rolling window; expire local fixtures outside it, including when the window advances.

Shell
curl "$BASE/tv-fixtures/v1/fixtures?updatedSince=2026-10-21T00:00:00Z&limit=100" \
  -H "X-API-Key: $API_KEY"

API reference: GET /tv-fixtures/v1/fixtures

This Python standard-library example replaces the separate demo client. Set FROM and TO to a fixed window inside the current plan scope, and optional UPDATED_SINCE to your last completed watermark. It prints pages; replace printing with durable upserts before saving the watermark. URL encoding also handles opaque cursors safely.

Python
import json
import os
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from datetime import datetime

base = os.environ['BASE'].rstrip('/')
params = {'from': os.environ['FROM'], 'to': os.environ['TO'], 'limit': 500, 'lang': 'en'}
watermark = os.environ.get('UPDATED_SINCE')
if watermark:
    params['updatedSince'] = watermark
while True:
    url = base + '/tv-fixtures/v1/fixtures?' + urlencode(params)
    request = Request(url, headers={'X-API-Key': os.environ['API_KEY']})
    with urlopen(request, timeout=30) as response:
        page = json.load(response)
    for fixture in page['fixtures']:
        print(fixture)
        # Compare parsed instants if your stored watermark uses a different precision.
        stamp = fixture['updatedAt']
        if watermark is None or datetime.fromisoformat(stamp) > datetime.fromisoformat(watermark):
            watermark = stamp
    if page['nextCursor'] is None:
        break
    params['cursor'] = page['nextCursor']
print('Completed watermark:', watermark)

API reference: GET /tv-fixtures/v1/fixtures

Channels

Shell
curl "$BASE/tv-fixtures/v1/channels?lang=en" -H "X-API-Key: $API_KEY"

API reference: GET /tv-fixtures/v1/channels

channels contains channel names, language maps, regions, types, satellite positions and receiver references where known. A nullable receiver field is unknown, not an empty string. Use each fixture's channels for its actual listings; the directory is not a fixture filter.

Ids

Ids are reference data for joining your schedule. A missing id never removes a fixture. Every fixture has an id: the Betradar match id (sr:match:61301157) when it is known, else our own id (tvf:fixture:1042). Both stay the same for that fixture; use it as your local key. Never infer an id from a name or fabricate one.

Every other id is in ids, one map per subject: the match, the home team, the away team and the competition. Each map is {system: id}. A system is in a map only when the id is known; it is never null. The keyless GET /tv-fixtures/v1/id-systems lists the systems served now, what they have ids of, their format, an example and the sports that have ids in them.

JSON
{"ids": {
  "match":       {"betradar": "sr:match:61301157", "soccerdonna": "168515"},
  "home":        {"betradar": "sr:competitor:42",  "soccerdonna": "2061"},
  "away":        {"betradar": "sr:competitor:30",  "soccerdonna": "1150"},
  "competition": {"betradar": "sr:tournament:17",  "soccerdonna": "WMQR"}
}}
SystemFormatCoverage
betradarsr:match:<n>, sr:competitor:<n>, sr:tournament:<n>Match, team and competition ids where known across sports
soccerdonnaBare: digits (match, team), a code (competition)Some women's football matches, teams and competitions

GET /tv-fixtures/v1/id-systems is the authority: a system it does not list is not served, and other systems may appear there later. Do not assume coverage for every fixture or subject. An id can arrive after the fixture: the fixture's updatedAt moves, so an updatedSince poll gets it. An id can also be corrected or removed the same way. These labels do not grant rights to third-party data.

Shell
curl "$BASE/tv-fixtures/v1/id-systems"
curl "$BASE/tv-fixtures/v1/fixtures/by-id/betradar/sr:match:61301157" -H "X-API-Key: $API_KEY"
curl "$BASE/tv-fixtures/v1/fixtures?system=betradar&team=sr:competitor:42" -H "X-API-Key: $API_KEY"

API reference: GET /tv-fixtures/v1/id-systems GET /tv-fixtures/v1/fixtures/by-id/{system}/{id} GET /tv-fixtures/v1/fixtures

by-id answers one fixture, or 404 fixture_not_found. system with team (home or away) and/or competition keeps the fixtures of that team or competition. To look up many at once, POST /tv-fixtures/v1/fixtures/lookup takes up to 500 {system, id} pairs; the answer's fixtures is aligned with them, null where no fixture has the id:

Shell
curl "$BASE/tv-fixtures/v1/fixtures/lookup" -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids":[{"system":"betradar","id":"sr:match:61301157"}]}'

API reference: POST /tv-fixtures/v1/fixtures/lookup

Limits and Retry-After

The plan permits 60 requests/minute shared by your tenant's keys, zero streams and three live keys. On HTTP 429 read Retry-After (seconds) and retryAfterSeconds, pause at least that long, then retry with backoff and jitter. A new key does not reset the quota. Bound retries for 503 responses, respecting Retry-After when supplied. There is no streaming endpoint.

Keys over HTTP

Use the API origin with /account/v1; this path has no product prefix. Lists contain metadata and current, never plaintext. Create and rotate return plaintext once; securely replace your application's key before discarding that response. Retrying with the same idempotency key within 24 hours returns the same result. Use a different idempotency key for a different action. Set KEY_ID to an id from the key list. The delete example is for an unused key, never your last key.

Shell
curl "$BASE/account/v1/keys" -H "X-API-Key: $API_KEY"
curl -X POST "$BASE/account/v1/keys/$KEY_ID/rotate" \
  -H "X-API-Key: $API_KEY" -H "Idempotency-Key: rotate-key-20261021-01"

API reference: GET /account/v1/keys POST /account/v1/keys/{id}/rotate

Shell
curl -X POST "$BASE/account/v1/keys" \
  -H "X-API-Key: $API_KEY" -H "Idempotency-Key: create-key-20261021-01"
curl -X DELETE "$BASE/account/v1/keys/$KEY_ID" -H "X-API-Key: $API_KEY"

API reference: GET /account/v1/keys DELETE /account/v1/keys/{id}

Rotation leaves the old key valid for 24 hours (expiresAt), then it stops working. An expiring rotated key does not count against the three live keys. Revocation takes effect on the next request. The last live key cannot be revoked. Account home provides the same controls.

Errors

Product and account errors use {error, code, message, next}; error equals code. next is an array of actions with an optional href. Follow a supplied URL only after checking its destination. Product parameter errors also name field where applicable. For example, limit=0 is outside 1..2000 and answers 400 invalid_parameter with field: limit:

Shell
curl "$BASE/tv-fixtures/v1/fixtures?limit=0" -H "X-API-Key: $API_KEY"

API reference: GET /tv-fixtures/v1/fixtures

codeMeaningFixnext action
invalid_keyMissing, expired or invalid keySupply a valid keyget_key
invalid_parameterInvalid query value or lookup body (field, e.g. system, ids[3].system)Fix field and retryInspect next
fixture_not_foundNo fixture served has this id in this systemCheck the system and the subject: a team's id is not a match'sNone
invalid_cursorCursor cannot be readRestart pagination with identical filtersInspect next
range_too_largeKickoff range exceeds scopeNarrow from and toInspect next
payment_requiredAccess needs paymentOpen payUrl or the provided linkpay
suspendedAccount suspendedContact supportcontact_support
not_entitledProduct not on this accountContact supportcontact_support
rate_limitedRequest quota reachedWait for Retry-Afterretry_after
unavailableService temporarily unavailableRetry with bounded backoffretry_after
not_a_tenantCalling identity is not a tenantUse a tenant keyNone
permission_requiredKey action not permittedAsk the account owner for permissioncontact_support
tenant_inactiveAccount key changes disabledContact supportcontact_support
key_not_foundNo such key on your accountRefresh key metadatalist_keys
key_limitMaximum live keys reachedRevoke an unused keyrevoke_key
last_keyCannot revoke the last live keyCreate a replacement firstcreate_key
key_rotatingKey already has a replacementUse its replacement or create anotherlist_keys
key_revokedKey already revokedCreate a replacementcreate_key
idempotency_key_requiredMissing or invalid idempotency keySend a fresh printable keysend_idempotency_key
idempotency_key_reusedKey used for a different requestUse a new keyuse_new_idempotency_key
idempotency_in_progressThe first signup with this key still runsRetry after Retry-After secondsInspect response
not_foundOffer or handoff not foundCheck the returned URL and offer slugInspect response
validation_failedSignup fields invalidFix all reported fieldsInspect response
fullTrial capacity reachedTry later or contact supportInspect response
origin_not_allowedBrowser origin refusedUse the creator storefrontInspect response

Storefront errors currently use {error, fields, retryAt} rather than the product error body. For signup 429, use Retry-After or retryAt. A reused signup idempotency key with a changed body returns 409; a spent reveal cannot disclose the credentials again. Do not retry a consumed reveal in a loop. Contact the offer's support address to recover account access.

Trial, payment and pause

The seven-day trial starts on the server without payment details. We request payment before it ends, by invoice or card through payUrl. Starting the trial does not authorize a charge; no automatic charge is made without your action and consent. Prices are shown or sent with your invoice or by the reselling partner. If unpaid at trial end, access is suspended. While paused, requests return 402 with next: [{action: "pay", href: "…"}]; payment restores access. A complimentary grant needs no payment while active and keeps the same plan limits. It may end on a stated date; after that, normal trial/payment rules apply again. An unlimited grant has no trial end. Account home shows billing, grants, limits and keys; there are no other product screens. Support is listed on the offer and in your welcome message.

Network

If your code runs in a hosted sandbox with an outbound allowlist, allow api.example.test, dev.feed.sportlive.hr, store.dev.tantu.stream and docs.dev.tantu.stream. Keep HTTPS certificate validation enabled. The primary API is served directly over HTTPS.

Changelog