ytdl-org/youtube-dl · error · ExtractorError

%s returned error: %s

Error message

%s returned error: %s

What it means

Raised by the Globo extractor when the security-hash endpoint (security.video.globo.com/videos/<id>/hash) returns JSON with no 'hash' but a 'message' — the API's explicit rejection (e.g. resource not entitled, signature expired). Marked expected=True; if there is no message the code just skips the resource.

Source

Thrown at youtube_dl/extractor/globo.py:131

            if resource_type == 'subtitle':
                subtitles.setdefault(resource.get('language') or 'por', []).append({
                    'url': resource_url,
                })
                continue

            security = self._download_json(
                'http://security.video.globo.com/videos/%s/hash' % video_id,
                video_id, 'Downloading security hash for %s' % resource_id, query={
                    'player': 'desktop',
                    'version': '5.19.1',
                    'resource_id': resource_id,
                })

            security_hash = security.get('hash')
            if not security_hash:
                message = security.get('message')
                if message:
                    raise ExtractorError(
                        '%s returned error: %s' % (self.IE_NAME, message), expected=True)
                continue

            hash_code = security_hash[:2]
            padding = '%010d' % random.randint(1, 10000000000)
            if hash_code in ('04', '14'):
                received_time = security_hash[3:13]
                received_md5 = security_hash[24:]
                hash_prefix = security_hash[:23]
            elif hash_code in ('02', '12', '03', '13'):
                received_time = security_hash[2:12]
                received_md5 = security_hash[22:]
                padding += '1'
                hash_prefix = '05' + security_hash[:22]

            padded_sign_time = compat_str(int(received_time) + 86400) + padding
            md5_data = (received_md5 + padded_sign_time + '0xAC10FD').encode()
            signed_md5 = base64.urlsafe_b64encode(hashlib.md5(md5_data).digest()).decode().strip('=')

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Log in again with valid credentials so a fresh GLBID cookie is set
  2. Verify your Globoplay subscription covers the specific video/resource
  3. Update youtube-dl/yt-dlp in case the hash endpoint parameters changed
  4. Try a different quality/resource variant of the same video if available
Defensive patterns

Strategy: try-catch

Validate before calling

import requests
s = requests.get(f'http://security.video.globo.com/videos/{vid}/hash', params={'player': 'desktop', 'version': '5.19.1', 'resource_id': rid}).json()
if not s.get('hash'):
    print('Hash refused:', s.get('message', '(no message)'))

Type guard

def hash_issued(security):
    return isinstance(security, dict) and isinstance(security.get('hash'), str) and len(security['hash']) >= 22

Try / catch

try:
    ydl.extract_info(url)
except DownloadError as e:
    if 'returned error' in str(e) and 'glb' in str(e).lower():
        reauth_and_retry(url)  # usually entitlement/session
    else:
        raise

Prevention

When it happens

Trigger: Requesting a playback hash for a specific resource_id where Globo refuses: unentitled account for that media resource, expired GLBID cookie, or resource removed from the CDN.

Common situations: Subscription-tier mismatch (account lacks the package for that resource), stale login cookie after password change, or Globo tightening hash issuance for older videos.

Related errors


AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14). Data as JSON: /api/errors/a32e28c437cd43ca. Report an issue: GitHub.