71.8M listings
Every country's schedule under one key, one shape and one set of ids.
GET /v1/now
What every channel in a country is showing this minute, with the next programme after it.
MIT, no lock-in
Every lab tool reads only the public API with your own key, so it runs on the free key.
Working normally
Checked every minute from outside our own servers.
A small client that handles keys, errors and rate limits, and two scripts that already work: what is on now in any country, and any channel's day in local time. Copy them, set your key, run.
$ php now.php GB 4seven Gogglebox 22:00 The Great British Bake Off 5Action Police Interceptors 22:00 Highway Thru Hell 5SELECT Warship: Life in the Royal Navy 22:00 Battlefront - WWII: Road To Victory 5STAR Ambulance: Code Red 22:00 A&E After Dark 5USA Wild Cards 22:00 Law & Order: Special Victims Unit 8 Out Of 10 Cats Episode 5 22:02 Episode 4 African Movie Channel Se Lodge 49 22:10 Lodge 49 Ahlebait TV Preaching of Ahlebait (a.s) 22:00 Islamic Movie Akaal Channel Live: Sikligar Wanjare Gurdwara 22:25 Today's Sikh History Alb UK TV Programi i ALB UK 23:00 Programi i ALB UK
tvguideapi.com/loginphp -m | grep curlexport TVG_KEY=tvg_live_...php now.php GB Europe/LondonA client, a now-and-next script and a schedule script. The Output tab is what php now.php GB printed a few minutes ago, from the live guide.
<?php // tvguide.php: a small client for the TV Guide API. PHP 8.1+, the curl extension only. final class TvGuide { public function __construct(private string $key, private string $base = 'https://api.tvguideapi.com') {} public function get(string $path, array $query = []): array { for ($try = 1; ; $try++) { $ch = curl_init($this->base . $path . ($query ? '?' . http_build_query($query) : '')); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_TIMEOUT => 20, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $this->key, 'Accept: application/json']]); $raw = curl_exec($ch); if ($raw === false) throw new RuntimeException(curl_error($ch)); $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $body = json_decode(substr($raw, $size), true) ?? []; if ($code === 429 && $try < 3) { // wait as long as the API asks, then try again preg_match('/^retry-after:\s*(\d+)/mi', substr($raw, 0, $size), $m); sleep((int) ($m[1] ?? 2)); continue; } if ($code >= 400) throw new RuntimeException(($body['error']['code'] ?? 'http_' . $code) . ': ' . ($body['error']['message'] ?? 'request failed')); return $body; } } /** Channel names by id, for one country. */ public function channelNames(string $country): array { $names = []; foreach ($this->get('/v1/channels', ['country' => $country, 'limit' => 500])['data'] as $c) $names[$c['id']] = $c['name']; return $names; } public function findChannel(string $name, ?string $country = null): ?array { return $this->get('/v1/channels/search', array_filter(['q' => $name, 'country' => $country]))['data'][0] ?? null; } }
<?php // now.php: what is on now in a country, with what comes next. // php now.php GB Europe/London require __DIR__ . '/tvguide.php'; $key = getenv('TVG_KEY'); if (!$key) exit("Set TVG_KEY to your key first\n"); $tv = new TvGuide($key); $country = strtoupper($argv[1] ?? 'GB'); $tz = new DateTimeZone($argv[2] ?? 'Europe/London'); $names = $tv->channelNames($country); foreach ($tv->get('/v1/now', ['country' => $country])['data'] as $row) { $now = $row['now']['title'] ?? 'Off air'; $next = $row['next'] ? (new DateTimeImmutable($row['next']['start']))->setTimezone($tz)->format('H:i') . ' ' . $row['next']['title'] : ''; printf("%-24s %-36s %s\n", mb_strimwidth($names[$row['channel']] ?? $row['channel'], 0, 24), mb_strimwidth($now, 0, 36), $next); }
<?php // schedule.php: one channel's day, in local time. // php schedule.php "BBC One" GB 2026-10-10 Europe/London require __DIR__ . '/tvguide.php'; $key = getenv('TVG_KEY'); if (!$key) exit("Set TVG_KEY to your key first\n"); $tv = new TvGuide($key); [$name, $country, $day, $zone] = [$argv[1] ?? 'BBC One', $argv[2] ?? 'GB', $argv[3] ?? date('Y-m-d'), $argv[4] ?? 'Europe/London']; $channel = $tv->findChannel($name, $country); if (!$channel) exit("No channel called $name in $country\n"); $tz = new DateTimeZone($zone); foreach ($tv->get("/v1/channels/{$channel['id']}/schedule", ['date' => $day, 'tz' => $zone])['data'] as $slot) { $t = (new DateTimeImmutable($slot['start']))->setTimezone($tz)->format('H:i'); echo $t, ' ', $slot['title'], $slot['flags']['live'] ? ' [LIVE]' : '', "\n"; }
$ php now.php GB
4seven Gogglebox 22:00 The Great British Bake Off
5Action Police Interceptors 22:00 Highway Thru Hell
5SELECT Warship: Life in the Royal Navy 22:00 Battlefront - WWII: Road To Victory
5STAR Ambulance: Code Red 22:00 A&E After Dark
5USA Wild Cards 22:00 Law & Order: Special Victims Unit
8 Out Of 10 Cats Episode 5 22:02 Episode 4
African Movie Channel Se Lodge 49 22:10 Lodge 49
Ahlebait TV Preaching of Ahlebait (a.s) 22:00 Islamic Movie
Akaal Channel Live: Sikligar Wanjare Gurdwara 22:25 Today's Sikh History
Alb UK TV Programi i ALB UK 23:00 Programi i ALB UK
Built on the client above. Each opens its endpoint in the documentation, with Try it.
$grid = $tv->get('/v1/now', ['channels' => 'ch_a,ch_b,ch_c'])['data'];Cache a day as JSONFree key$day = $tv->get("/v1/channels/$id/schedule", ['tz' => 'Europe/London']);
file_put_contents('today.json', json_encode($day)); // refresh every 10 minutesFind a channel idFree key$id = $tv->findChannel('TF1', 'FR')['id']; // store it: ids never changeWhen is a show on nextFree key$sid = $tv->get('/v1/shows/search', ['q' => 'Bluey'])['data'][0]['id'];
$next = $tv->get("/v1/shows/$sid/airings", ['country' => 'GB', 'days' => 7])['data'];Live sport on TV nowPaid plans$live = $tv->get('/v1/events/live', ['country' => 'GB'])['data'];An XMLTV file for your playerPaid plans// the export is XML, so fetch it as a file
exec('curl -s -H "Authorization: Bearer ' . getenv('TVG_KEY') . '" "https://api.tvguideapi.com/v1/xmltv?country=GB&days=7" -o guide.xml');A past day, for researchPaid plans$past = $tv->get("/v1/channels/$id/history", ['from' => '2024-07-05T00:00:00Z', 'to' => '2024-07-06T00:00:00Z']);Your calls leftFree keyprint_r($tv->get('/v1/usage')['data']);The same shape for every country. Fields in full on Now on.
{
"data": [
{
"channel": "ch_...",
"now": {
"title": "Gogglebox"
},
"next": {
"title": "The Great British Bake Off",
"local_start": "22:00"
}
},
{
"channel": "ch_...",
"now": {
"title": "Police Interceptors"
},
"next": {
"title": "Highway Thru Hell",
"local_start": "22:00"
}
}
],
"meta": {
"count": 10
}
}Everything above except sport, XMLTV and the archive runs on the free key.
A free key in a minute, with Google. No card. 41,066 channels in 155 countries.