Stream TV Channel Feed
curl --request GET \
--url https://api.example.com/api/tv/feedimport requests
url = "https://api.example.com/api/tv/feed"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.example.com/api/tv/feed', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/tv/feed",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/tv/feed"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.example.com/api/tv/feed")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/tv/feed")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"t": "<string>",
"d": {
"id": "<string>",
"orientation": "<string>",
"players": [
{}
],
"ply": 123,
"uci": "<string>",
"san": "<string>",
"fen": "<string>",
"clock": {},
"winner": "<string>",
"status": "<string>"
}
}TV & Streaming
Stream TV Channel Feed
Stream real-time updates from a Lichess TV channel as games progress
GET
/
api
/
tv
/
feed
Stream TV Channel Feed
curl --request GET \
--url https://api.example.com/api/tv/feedimport requests
url = "https://api.example.com/api/tv/feed"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.example.com/api/tv/feed', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/tv/feed",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/tv/feed"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.example.com/api/tv/feed")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/tv/feed")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"t": "<string>",
"d": {
"id": "<string>",
"orientation": "<string>",
"players": [
{}
],
"ply": 123,
"uci": "<string>",
"san": "<string>",
"fen": "<string>",
"clock": {},
"winner": "<string>",
"status": "<string>"
}
}Overview
Stream the events of the current TV game for a specific channel. This endpoint uses Server-Sent Events (SSE) to push updates in real-time as moves are played.Endpoints
Stream Default Channel (Best)
GET https://lichess.org/api/tv/feed
Stream Specific Channel
GET https://lichess.org/api/tv/:channel/feed
Authentication
No authentication required. These are public endpoints.Parameters
string
The TV channel to stream. If omitted, defaults to “best”.Available channels:
best- Top Rated (2150+ standard games)bullet- Bullet gamesblitz- Blitz gamesrapid- Rapid gamesclassical- Classical gameschess960- Chess960 variantkingOfTheHill- King of the HillthreeCheck- Three-checkantichess- Antichessatomic- Atomichorde- HorderacingKings- Racing Kingscrazyhouse- CrazyhouseultraBullet- UltraBulletbot- Bot gamescomputer- Computer games
boolean
default:"false"
Enable backwards compatibility mode. When
true, uses Server-Sent Events format. When false (default), uses newline-delimited JSON (NDJSON).Response Format
NDJSON Format (Default)
By default, the stream returns newline-delimited JSON. Each line is a complete JSON object representing a game event.SSE Format
Whenbc=true, the stream uses Server-Sent Events format with text/event-stream content type.
Example Requests
cURL - Default Channel
curl https://lichess.org/api/tv/feed
cURL - Specific Channel
curl https://lichess.org/api/tv/blitz/feed
cURL - SSE Format
curl https://lichess.org/api/tv/feed?bc=true
JavaScript - NDJSON
const response = await fetch('https://lichess.org/api/tv/bullet/feed');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const lines = decoder.decode(value).split('\n');
for (const line of lines) {
if (line.trim()) {
const event = JSON.parse(line);
console.log('TV Update:', event);
}
}
}
JavaScript - EventSource (SSE)
const eventSource = new EventSource('https://lichess.org/api/tv/feed?bc=true');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('TV Update:', data);
};
eventSource.onerror = (error) => {
console.error('Connection error:', error);
eventSource.close();
};
Python
import requests
import json
url = 'https://lichess.org/api/tv/blitz/feed'
with requests.get(url, stream=True) as response:
for line in response.iter_lines():
if line:
event = json.loads(line)
print('TV Update:', event)
Event Types
The stream emits various game-related events:Featured Game Event
When a new game becomes featured on the channel:{
"t": "featured",
"d": {
"id": "q7ZvsdUF",
"orientation": "white",
"players": [
{
"color": "white",
"user": {
"name": "Magnus",
"id": "magnus",
"title": "GM"
},
"rating": 2847
},
{
"color": "black",
"user": {
"name": "Hikaru",
"id": "hikaru",
"title": "GM"
},
"rating": 2832
}
]
}
}
Move Event
When a move is played:{
"t": "ply",
"d": {
"ply": 23,
"uci": "e2e4",
"san": "e4",
"fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
"clock": {
"white": 600,
"black": 590
}
}
}
Game End Event
When the featured game ends:{
"t": "end",
"d": {
"winner": "white",
"status": "mate"
}
}
Response Fields
string
Event type:
featured, ply, end, etc.object
Event data - structure varies by event type
Show Featured Game Fields
Show Featured Game Fields
Show Move (Ply) Fields
Show Move (Ply) Fields
Connection Management
- The stream is kept alive with periodic keep-alive messages
- When a featured game ends, a new game from the channel will automatically be featured
- Connections should be resilient to temporary disconnections and reconnect automatically
- Games are refreshed based on activity - inactive games are replaced
Rate Limiting
- Persistent connections are allowed but limited per IP address
- Maximum of 4 concurrent streams per IP for standard users
- Do not poll this endpoint - it’s designed to be streamed continuously
Channel Selection Criteria
Each channel selects games based on specific criteria:- Rating filters: Most channels require minimum ratings (e.g., Best: 2150+, Blitz: 2000+)
- Time controls: Speed-based channels filter by game time control
- Variants: Variant channels show only that specific chess variant
- Recency: Games must have recent moves (freshness varies by channel)
- Bot exclusion: Most channels exclude bot games (except the Bot channel)
Notes
- The stream automatically switches to new games when the current one ends or becomes stale
- Not all channels have active games at all times
- The default channel (“best”) typically has the most consistent availability
- Use NDJSON format (default) for simpler parsing in modern applications
Related Endpoints
- Get Current TV Channels - Get current champions for all channels
- Event Stream - Stream personal game and challenge events
Source Code Reference
- Controller:
app/controllers/Tv.scala:92-108 - Routes:
conf/routes:43-44

