{"record":{"id":"f4a4cbaffe5e3e6e","repo":"DIYgod/RSSHub","slug":"youtube-rss-is-disabled-due-to-the-lack-of-a-href-f4a4cb","errorCode":null,"errorMessage":"YouTube RSS is disabled due to the lack of <a href=\"https://docs.rsshub.app/deploy/config#route-specific-configurations\">relevant config</a>","messagePattern":"YouTube RSS is disabled due to the lack of <a href=\"https://docs\\.rsshub\\.app/deploy/config#route-specific-configurations\">relevant config</a>","errorType":"exception","errorClass":"ConfigNotFoundError","httpStatus":503,"severity":"error","filePath":"lib/routes/youtube/subscriptions.ts","lineNumber":50,"sourceCode":"                description: '',\n            },\n        ],\n    },\n    radar: [\n        {\n            source: ['www.youtube.com/feed/subscriptions', 'www.youtube.com/feed/channels'],\n            target: '/subscriptions',\n        },\n    ],\n    name: 'Subscriptions',\n    maintainers: ['TonyRL'],\n    handler,\n    url: 'www.youtube.com/feed/subscriptions',\n};\n\nasync function handler(ctx) {\n    if (!config.youtube || !config.youtube.key || !config.youtube.clientId || !config.youtube.clientSecret || !config.youtube.refreshToken) {\n        throw new ConfigNotFoundError('YouTube RSS is disabled due to the lack of <a href=\"https://docs.rsshub.app/deploy/config#route-specific-configurations\">relevant config</a>');\n    }\n    const embed = !ctx.req.param('embed');\n\n    const channelIds = (await getSubscriptions('snippet', cache)).data.items.map((item) => item.snippet.resourceId.channelId);\n\n    const playlistIds = await pMap(channelIds, async (channelId) => (await getChannelWithId(channelId, 'contentDetails', cache)).data.items?.[0].contentDetails.relatedPlaylists.uploads, { concurrency: 30 });\n\n    let items = await pMap(playlistIds.filter(Boolean), async (playlistId) => (await getPlaylistItems(playlistId, 'snippet', cache))?.data.items, { concurrency: 30 });\n\n    items = items.flat();\n\n    items = items\n        .filter((i) => i && !i.error && i.snippet.title !== 'Private video' && i.snippet.title !== 'Deleted video')\n        .map((item) => {\n            const snippet = item.snippet;\n            const videoId = snippet.resourceId.videoId;\n            const img = getThumbnail(snippet.thumbnails);\n            return {","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/youtube/subscriptions.ts#L32-L68","documentation":"ConfigNotFoundError thrown by the YouTube subscriptions handler. Unlike /c/ and /live, this route needs the full OAuth stack — config.youtube.key AND clientId AND clientSecret AND refreshToken — because it reads the authenticated user's own subscription feed via the YouTube Data API on their behalf. The guard checks all four and fails if any is missing.","triggerScenarios":"Any request to /youtube/subscriptions when at least one of YOUTUBE_KEY, YOUTUBE_CLIENT_ID, YOUTUBE_CLIENT_SECRET, YOUTUBE_REFRESH_TOKEN is unset. The guard short-circuits before getSubscriptions is called.","commonSituations":"Operator set only YOUTUBE_KEY and assumed subscriptions would work; OAuth credentials not created in Google Cloud; refresh token never generated via the OAuth playground; env vars named inconsistently.","solutions":["Create OAuth credentials (Desktop app type) in Google Cloud Console and set YOUTUBE_CLIENT_ID and YOUTUBE_CLIENT_SECRET.","Use the Google OAuth Playground (redirect URI https://developers.google.com/oauthplayground) with the youtube.readonly scope to obtain a YOUTUBE_REFRESH_TOKEN.","Set YOUTUBE_KEY as well (the route needs both API key and OAuth).","Restart RSSHub after setting all four env vars."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { config } from '@/config';\nfunction requireYouTubeOAuth() {\n    const c = config.youtube;\n    const missing = [];\n    if (!c?.key) missing.push('YOUTUBE_KEY');\n    if (!c?.clientId) missing.push('YOUTUBE_CLIENT_ID');\n    if (!c?.clientSecret) missing.push('YOUTUBE_CLIENT_SECRET');\n    if (!c?.refreshToken) missing.push('YOUTUBE_REFRESH_TOKEN');\n    if (missing.length) {\n        throw new ConfigNotFoundError(`YouTube subscriptions needs OAuth config. Missing: ${missing.join(', ')}. Use redirect URI https://developers.google.com/oauthplayground with scope youtube.readonly.`);\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Create OAuth credentials (Desktop app) in Google Cloud and set YOUTUBE_CLIENT_ID / YOUTUBE_CLIENT_SECRET.","Generate YOUTUBE_REFRESH_TOKEN via the OAuth Playground with youtube.readonly scope and the playground redirect URI.","Also set YOUTUBE_KEY — subscriptions need both API key and OAuth.","Restart RSSHub after setting all four env vars."],"tags":["youtube","config","oauth","api-key","google-api"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}