{"record":{"id":"783a067670025e8b","repo":"unslothai/unsloth","slug":"that-is-not-a-youtube-video-link-783a06","errorCode":null,"errorMessage":"That is not a YouTube video link.","messagePattern":"That is not a YouTube video link\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"studio/backend/routes/youtube.py","lineNumber":57,"sourceCode":"    videoId: str\n    url: str\n    title: str\n    author: str\n    lengthSeconds: int\n    language: str\n    languageCode: str\n    isGenerated: bool\n    text: str\n    truncated: bool\n\n\n@router.post(\"/transcript\", response_model = TranscriptResponse)\nasync def get_transcript(\n    request: TranscriptRequest, current_subject: str = Depends(get_current_subject)\n) -> TranscriptResponse:\n    video_id = extract_video_id(request.url)\n    if video_id is None:\n        raise HTTPException(status_code = 400, detail = \"That is not a YouTube video link.\")\n\n    try:\n        transcript = await fetch_transcript(video_id, request.languages)\n    except TranscriptUnavailable as error:\n        raise HTTPException(status_code = 422, detail = str(error)) from error\n    except httpx.HTTPError as error:\n        logger.warning(f\"YouTube transcript fetch failed for {video_id}: {error}\")\n        raise HTTPException(\n            status_code = 502,\n            detail = \"Could not reach YouTube.\",\n        ) from error\n\n    return TranscriptResponse(\n        videoId = transcript.video_id,\n        url = watch_url(transcript.video_id),\n        title = transcript.title,\n        author = transcript.author,\n        lengthSeconds = transcript.length_seconds,","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/routes/youtube.py#L39-L75","documentation":"400 from POST /api/youtube/transcript: extract_video_id(request.url) returned None, so the submitted URL does not contain a recognizable YouTube video id (watch?v=, youtu.be/<id>, shorts, embed forms). The parser is strict about the hosts and query shapes it accepts.","triggerScenarios":"Posting a channel or playlist URL, a bare video id without a URL, a clipboard artifact ('https://youtube.com/watch?v=abc '), a different service's link (vimeo/tiktok), or a URL with the v param stripped by a chat client's link unfurler.","commonSituations":"Paste-from-chat mangling; users pasting a search-results URL; frontend not validating before submit.","solutions":["Use a canonical URL: https://www.youtube.com/watch?v=<11-char-id> or https://youtu.be/<id>.","Validate client-side before posting (regex for the 11-char id pattern).","If a legitimate YouTube URL shape fails, extract the id yourself and build the canonical form before sending."],"exampleFix":"// before\nawait api.post('/youtube/transcript', { url: rawPaste });\n\n// after\nconst m = rawPaste.match(/(?:v=|youtu\\.be\\/|shorts\\/)([\\w-]{11})/);\nif (!m) return showError('Paste a YouTube video link');\nawait api.post('/youtube/transcript', { url: `https://www.youtube.com/watch?v=${m[1]}` });","handlingStrategy":"validation","validationCode":"const YT = /(?:youtube\\.com\\/(?:watch\\?v=|shorts\\/|embed\\/)|youtu\\.be\\/)([\\w-]{11})/;\nfunction extractOrReject(url) {\n  const m = url?.match(YT);\n  if (!m) throw new ValidationError('That is not a YouTube video link.');\n  return m[1];\n}","typeGuard":"function isYouTubeUrl(u: string): boolean {\n  try { const h = new URL(u).hostname; return h === 'youtu.be' || h.endsWith('youtube.com'); }\n  catch { return false; }\n}","tryCatchPattern":"try { await postTranscript(url); }\ncatch (e) { if (e?.status === 400) showFieldError('Paste a YouTube video link (watch?v=… or youtu.be/…)'); else throw e; }","preventionTips":["Validate the URL client-side and show which video was parsed (thumbnail/id) before submit","Normalize to canonical watch?v= form before posting","Reject playlist/channel/search URLs early with a targeted message"],"tags":["http-400","youtube","url-parsing","validation"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}