{"record":{"id":"b1f6ac4d932841be","repo":"DIYgod/RSSHub","slug":"failed-to-get-token","errorCode":null,"errorMessage":"Failed to get token","messagePattern":"Failed to get token","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/routes/denonbu/news.ts","lineNumber":127,"sourceCode":"    'X-API-KEY': 'FVpHcMLqyf7v2EubqiLxznC9gVMqBDFFMt4zvkS2',\n};\nconst PRIMARY_CATEGORIES = new Set(['news', 'event', 'goods', 'comic', 'movie', 'music', 'livearchives']);\nconst CACHE_TOKEN_KEY = 'denonbu-news';\n\nasync function getToken(): Promise<string> {\n    const cacheToken = await cache.get(CACHE_TOKEN_KEY, false);\n    if (cacheToken) {\n        return cacheToken;\n    }\n\n    const payload = (\n        await ofetch(new URL('auths/token/get', BASE_URL).href, {\n            headers: COMMON_HEADERS,\n        })\n    ).payload;\n    const { token, expires } = payload;\n    if (!token) {\n        throw new Error('Failed to get token');\n    }\n    cache.set(CACHE_TOKEN_KEY, token, expires ? expires - Number(Date.now()) / 1000 - 1 : 3600);\n    return token;\n}\n\nfunction buildLink(body: any): string | null {\n    switch (body.source_type) {\n        case 'main':\n        case 'deep-okubo':\n        case 'shinsaibashi':\n        case 'neotokyo': {\n            const { sid, uid } = body;\n            if (sid && uid) {\n                return `https://denonbu.jp/detail/${sid}/${uid}`;\n            }\n            return null;\n        }\n        case 'tw': {","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/denonbu/news.ts#L109-L145","documentation":"The denonbu (電音部) news route obtains an auth token from the backend API at /auths/token/get using a hardcoded X-API-KEY. If the response payload lacks a token field, the route throws 'Failed to get token'. This token is required for all subsequent API calls to fetch news, events, goods, etc. The token is cached but must be obtained fresh on cache miss.","triggerScenarios":"The ofetch call to BASE_URL/auths/token/get returns a payload object, but payload.token is undefined/null/falsy. This can occur when the hardcoded X-API-KEY is invalid or expired, when the API endpoint changed, or when the server returns an error response that ofetch does not throw on (e.g., 200 with error body).","commonSituations":"The X-API-KEY 'FVpHcMLqyf7v2EubqiLxznC9gVMqBDFFMt4zvkS2' was rotated or revoked by denonbu.jp; the auth endpoint URL or response format changed; the server is under maintenance and returns a fallback response; rate limiting returns a 429-like body without proper HTTP status.","solutions":["Inspect the full response from the auth endpoint — log the payload object to see what was returned.","Check if the X-API-KEY is still valid by testing the endpoint directly (e.g., via curl).","If the key is expired, find the new key by inspecting network requests on denonbu.jp in a browser.","Verify the auths/token/get endpoint path is still correct.","Add error handling for non-200 HTTP responses from ofetch."],"exampleFix":"// before\nconst payload = (\n    await ofetch(new URL('auths/token/get', BASE_URL).href, {\n        headers: COMMON_HEADERS,\n    })\n).payload;\nconst { token, expires } = payload;\nif (!token) {\n    throw new Error('Failed to get token');\n}\n\n// after — surface the actual response for debugging\nconst res = await ofetch(new URL('auths/token/get', BASE_URL).href, {\n    headers: COMMON_HEADERS,\n});\nconst payload = res.payload;\nif (!payload?.token) {\n    throw new Error(`Failed to get token. Response: ${JSON.stringify(res).slice(0, 200)}`);\n}","handlingStrategy":"retry","validationCode":"// Check cache first, then validate API response shape\nconst cacheToken = await cache.get(CACHE_TOKEN_KEY, false);\nif (cacheToken) return cacheToken;\n\nconst res = await ofetch(new URL('auths/token/get', BASE_URL).href, { headers: COMMON_HEADERS });\nif (!res?.payload) {\n    throw new Error(`Denonbu auth endpoint returned unexpected shape: ${JSON.stringify(res).slice(0, 200)}`);\n}","typeGuard":"function hasTokenPayload(res: any): res is { payload: { token: string; expires?: number } } {\n    return res?.payload?.token != null && typeof res.payload.token === 'string';\n}","tryCatchPattern":"try {\n    const res = await ofetch(authUrl, { headers: COMMON_HEADERS });\n    if (!res.payload?.token) throw new Error('No token in response');\n    return res.payload.token;\n} catch (e) {\n    // Clear potentially stale cache and retry once\n    cache.set(CACHE_TOKEN_KEY, null, 0);\n    throw new Error(`Failed to obtain denonbu auth token: ${(e as Error).message}`);\n}","preventionTips":["Monitor the hardcoded X-API-KEY for expiration — extract it dynamically if possible.","Log the full auth response when token is missing to detect API changes.","Ensure cache TTL for the token is shorter than the actual expiry to avoid stale tokens."],"tags":["denonbu","authentication","api-key","token","api-change"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}