yt-dlp/yt-dlp · error · ExtractorError
Ticket has expired
Error message
Ticket has expired
What it means
Raised by the ZAN extractor when the getLiveStatus result reports canPlay != True: the viewer's ticket for the stream is no longer playable, i.e. it expired. ZAN issues per-viewer playback tickets; once expired, the API refuses playback even though the stream itself may still be running. Marked expected=True.
Source
Thrown at yt_dlp/extractor/zan.py:183
self.raise_login_required()
status = self._download_json(
f'{self._BASE_URL}/api/live/{video_id}/getLiveStatus', video_id, headers={
'X-Csrf-Token': csrf_token,
}, data=urlencode_postdata({
'pct': pct,
'token': token,
}))
if not traverse_obj(status, ('isSuccess', {bool})):
raise ExtractorError('Unexpected error')
result = traverse_obj(status, ('result', {dict}))
for key, required, error_message in (
('isFinished', False, 'This video is no longer available'),
('canPlay', True, 'Ticket has expired'),
):
if traverse_obj(result, (key, {bool})) is not required:
raise ExtractorError(error_message, expected=True)
is_live = not traverse_obj(result, ('isVod', {bool}))
release_timestamp = parse_iso8601(self._html_search_meta('open-live-date', webpage))
srv_time = traverse_obj(status, ('srvTime', {int_or_none}), default=0)
if is_live and release_timestamp and srv_time < release_timestamp:
start_time = dt.datetime.fromtimestamp(
release_timestamp, dt.timezone.utc,
).astimezone().strftime('%Y-%m-%d %H:%M:%S %Z')
self.raise_no_formats(
f'This livestream is scheduled to start at {start_time}', expected=True)
return {
'id': video_id,
'live_status': 'is_upcoming',
'release_timestamp': release_timestamp,
}
View on GitHub (pinned to 81ecd58b13)
Solutions
- Re-open the event page in the browser (fresh page load issues a new ticket), re-export cookies, and retry promptly
- If the event requires a purchase/login, re-authenticate first so a fresh ticket is granted
- Retry the extraction immediately after obtaining the fresh session rather than resuming hours later
Example fix
# before (stale session from an earlier page load) yt-dlp --cookies old-cookies.txt "https://live.zan.com.br/..." # after (fresh page visit -> new ticket -> immediate retry) yt-dlp --cookies-from-browser chrome "https://live.zan.com.br/..."
Defensive patterns
Strategy: retry
Try / catch
from yt_dlp.utils import ExtractorError
try:
ydl.download([url])
except ExtractorError as e:
if 'Ticket has expired' in str(e):
refresh_browser_cookies() # new page visit issues a fresh ticket
ydl.download([url]) # retry immediately with the new session
else:
raise Prevention
- Always load the event page right before extraction so the ticket is young
- Start long ZAN downloads promptly after opening the page
- Keep cookies tied to one playback session; don't reuse them across machines
When it happens
Trigger: Loading the page, waiting (or resuming an interrupted download), then calling the API with a ticket whose validity window elapsed; sharing/duplicating a page session whose ticket was already consumed; the check finds canPlay is not True and raises.
Common situations: Restarting a download much later using stale session cookies; long-running downloads that re-request playback info after a ticket lapse; ticket tied to a single playback session that was already used.
Related errors
- Unexpected error
- This video is no longer available
- Cannot find on-air {video_id} channel.
- The channel is not currently live
- {api_message}
AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22).
Data as JSON: /api/errors/1a9438bfc55a38a2.
Report an issue: GitHub.