{"record":{"id":"53c77c85164d552e","repo":"DIYgod/RSSHub","slug":"techflow-api-returned-an-invalid-article-list","errorCode":null,"errorMessage":"TechFlow API returned an invalid article list.","messagePattern":"TechFlow API returned an invalid article list\\.","errorType":"exception","errorClass":"TypeError","httpStatus":503,"severity":"error","filePath":"lib/routes/techflowpost/utils.ts","lineNumber":154,"sourceCode":"        updated: newsflash.updated_at ? parseDate(newsflash.updated_at) : undefined,\n        description: content || newsflash.abstract,\n    };\n}\n\nasync function getArticleItems({ category, limit }: { category?: string; limit: string | number }) {\n    const link = `${rootUrl}/${locale}/article`;\n    const searchParams: Record<string, string | number> = {\n        page: 1,\n        page_size: limit,\n    };\n\n    if (category) {\n        searchParams.category_id = category;\n    }\n\n    const response = await requestApi<ApiListResponse<Article>>('/client/articles', link, searchParams);\n    if (!Array.isArray(response.data)) {\n        throw new TypeError('TechFlow API returned an invalid article list.');\n    }\n\n    return await Promise.all(\n        response.data.map((article) =>\n            cache.tryGet(`techflowpost:article:${article.id}`, async () => {\n                const itemLink = `${rootUrl}/${locale}/article/${article.id}`;\n                const detail = await requestApi<ArticleDetailResponse>(`/client/articles/${article.id}`, itemLink);\n\n                return getArticleItem(article, detail.article?.content);\n            })\n        )\n    );\n}\n\nasync function getNewsflashItems(limit: string | number) {\n    const link = `${rootUrl}/${locale}/newsletter`;\n    const response = await requestApi<ApiListResponse<Newsflash>>('/client/newsflashes', link, {\n        page: 1,","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/techflowpost/utils.ts#L136-L172","documentation":"Thrown by the article-list fetcher when requestApi<ApiListResponse<Article>> succeeds (no anti-crawler string) but response.data is not an Array. The contract with GET /client/articles is that .data is the list of articles; a non-array means the API returned an error object, a paginated wrapper, or a shape change rather than a list.","triggerScenarios":"Calling GET {apiRoot}/client/articles with page=1 and page_size=limit (optionally category_id) where the server replies with {code, msg, ...} or {data: {...}} instead of {data: [...]}. Also fires if the endpoint starts returning an object due to a category_id that resolves to a non-list response.","commonSituations":"TechFlow ships an API version bump that wraps lists differently; an invalid/deleted category_id makes the API return an error envelope that still HTTP-200s; ofetch/got auto-parses JSON so a temporary maintenance page served as JSON also lands here.","solutions":["Inspect the actual response body (log response) to see whether the API now nests the array (e.g. response.data.list or response.data.items) and update the guard/parse accordingly.","Confirm category_id is valid before sending it; an invalid category often yields an error object instead of a list.","If the API genuinely returned an empty-but-valid list shape, check for a different list field name and update the ApiListResponse type plus the Array.isArray target.","Add a defensive log of the raw response when Array.isArray fails so future shape changes are diagnosable instead of opaque."],"exampleFix":"// before\nif (!Array.isArray(response.data)) {\n    throw new TypeError('TechFlow API returned an invalid article list.');\n}\n\n// after: also accept a nested list and log the unexpected shape\nconst list = Array.isArray(response.data) ? response.data : response.data?.list;\nif (!Array.isArray(list)) {\n    throw new TypeError(`TechFlow API returned an invalid article list: ${JSON.stringify(response).slice(0, 200)}`);\n}","handlingStrategy":"type-guard","validationCode":"function isArticleList(r: unknown): r is { data: unknown[] } {\n    return !!r && typeof r === 'object' && Array.isArray((r as any).data);\n}\n// const response = await requestApi<ApiListResponse<Article>>(...);\n// if (!isArticleList(response)) { /* handle */ }","typeGuard":"function isArticleListResponse(r: unknown): r is { data: Article[] } {\n    return typeof r === 'object' && r !== null && Array.isArray((r as { data?: unknown }).data);\n}","tryCatchPattern":"let response;\ntry {\n    response = await requestApi<ApiListResponse<Article>>('/client/articles', link, params);\n} catch (e) {\n    if (e instanceof TypeError && /invalid article list/.test(e.message)) {\n        // log and degrade to empty feed instead of failing the route\n        return [];\n    }\n    throw e;\n}\nif (!isArticleListResponse(response)) { return []; }","preventionTips":["Treat the API envelope as unstable: validate with a type guard before use.","Log raw responses when the shape check fails so contract changes are caught early.","Pin the API version (or document the assumed endpoint) so an upstream rename is noticed.","Validate category_id before sending it, since invalid ids often yield non-list responses."],"tags":["api-contract","validation","typescript","parsing"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}