{"record":{"id":"f845c32630969b2b","repo":"datawhalechina/hello-agents","slug":"tmdb-access-token-api-key","errorCode":null,"errorMessage":"TMDB 鉴权失败：请检查 Access Token / API Key","messagePattern":"TMDB 鉴权失败：请检查 Access Token / API Key","errorType":"http","errorClass":"MovieServiceError","httpStatus":401,"severity":"error","filePath":"Co-creation-projects/aatanxiao12-beep-YingQian/backend/app/services/movie_service.py","lineNumber":123,"sourceCode":"\n    def _get(self, path: str, extra_params: Optional[Dict[str, Any]] = None) -> dict:\n        \"\"\"对 TMDB 发 GET，统一处理超时、网络错误与非 2xx / 非 JSON。\"\"\"\n        headers, params = self._auth_headers_and_params()\n        if extra_params:\n            for k, v in extra_params.items():\n                if v is not None and v != \"\":\n                    params[k] = v\n\n        url = f\"{TMDB_API_BASE}{path}\"\n        try:\n            resp = self._client.get(url, headers=headers, params=params)\n        except httpx.TimeoutException as e:\n            raise MovieServiceError(f\"TMDB 请求超时: {e}\") from e\n        except httpx.HTTPError as e:\n            raise MovieServiceError(f\"TMDB 网络错误: {e}\") from e\n\n        if resp.status_code == 401:\n            raise MovieServiceError(\n                \"TMDB 鉴权失败：请检查 Access Token / API Key\",\n                status_code=401,\n            )\n        if resp.status_code == 404:\n            raise MovieServiceError(\"影片不存在或已下架\", status_code=404)\n        if resp.status_code >= 400:\n            raise MovieServiceError(\n                f\"TMDB 请求失败: HTTP {resp.status_code}, {resp.text[:200]}\"\n            )\n\n        try:\n            return resp.json()\n        except ValueError as e:\n            raise MovieServiceError(\"TMDB 返回非 JSON\") from e\n\n    def _poster_url(self, poster_path: Optional[str]) -> Optional[str]:\n        \"\"\"把 TMDB 相对 poster_path 拼成可访问的完整图片 URL。\"\"\"\n        if not poster_path:","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/aatanxiao12-beep-YingQian/backend/app/services/movie_service.py#L105-L141","documentation":"MovieServiceError with status_code=401 raised when TMDB responds HTTP 401 to an authenticated request. This means credentials were present (they passed the _auth_headers_and_params guard) but are wrong or malformed: an expired/revoked v4 access token, a v3 api_key that is invalid, or an API-key-only key being sent where a Bearer token is required (TMDB v3 endpoints accept api_key query param, but a malformed token string yields 401).","triggerScenarios":"TMDB_ACCESS_TOKEN expired or regenerated in the TMDB dashboard (old token invalidated); a truncated copy-paste token (missing chars); sending a v3 api_key value in TMDB_ACCESS_TOKEN (it is not a JWT, server rejects); API key suspended for terms-of-service violations.","commonSituations":"Token rotated by another team member; token pasted with leading/trailing whitespace or quotes in .env; free-tier key disabled; key from a different TMDB account after config drift.","solutions":["Regenerate the API key / access token in TMDB account settings and update .env, then restart the service","Strip whitespace/quotes from the value in .env (pydantic-settings keeps them verbatim)","Verify the credential directly: curl -H 'Authorization: Bearer $TMDB_ACCESS_TOKEN' https://api.themoviedb.org/3/account — 200 means the token is good","Ensure v4 token (JWT starting with eyJ) goes in TMDB_ACCESS_TOKEN and the 32-char v3 key goes in TMDB_API_KEY — do not swap them"],"exampleFix":"# before (mixed-up credentials in .env)\n# TMDB_ACCESS_TOKEN=7a3f...v3_style_32_char_key\n\n# after\n# .env — v4 bearer token (JWT) in ACCESS_TOKEN, v3 key in API_KEY\nTMDB_ACCESS_TOKEN=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOi...\\\nTMDB_API_KEY=7a3f9c...(unused if token present)\n\n# sanity check before deploying:\n# curl -s -o /dev/null -w '%{http_code}' \\\n#   -H \"Authorization: Bearer $TMDB_ACCESS_TOKEN\" \\\n#   https://api.themoviedb.org/3/account   # expect 200","handlingStrategy":"try-catch","validationCode":"# cheap local sanity: v4 tokens are long JWTs\nfrom app.config import get_settings\n\ndef tmdb_token_plausible() -> bool:\n    token, key = get_settings().resolve_tmdb_credentials()\n    if token:\n        return token.startswith('eyJ') and len(token) > 100\n    return bool(key) and len(key) == 32","typeGuard":null,"tryCatchPattern":"except MovieServiceError as e:\n    if e.status_code == 401:\n        page_env_setup_notice('TMDB credentials rejected — regenerate token')\n        return degrade()\n    raise","preventionTips":["Smoke-test credentials once at startup with a cheap endpoint (/3/configuration)","Strip whitespace in .env values and quote JWTs (they contain no spaces but pasted newlines break them)","Put v4 JWT in TMDB_ACCESS_TOKEN and 32-char v3 key in TMDB_API_KEY — never swapped"],"tags":["tmdb","authentication","http-401","api-key","python"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}