{"record":{"id":"fb87e3f55cb65741","repo":"DIYgod/RSSHub","slug":"failed-to-retrieve-manga-meta-from-mangadex-api","errorCode":null,"errorMessage":"Failed to retrieve manga meta from MangaDex API.","messagePattern":"Failed to retrieve manga meta from MangaDex API\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/routes/mangadex/_feed.ts","lineNumber":90,"sourceCode":" */\nexport async function getMangaMetaByIds(ids: string[], needCover: boolean = true, lang?: string | string[]): Promise<Map<string, { id: string; title: string; description: string; cover?: string }>> {\n    const deDuplidatedIds = [...new Set(ids)].toSorted((a, b) => a.localeCompare(b));\n    const includes = needCover ? ['cover_art'] : [];\n\n    const rawMangaMetas = (await cache.tryGet(\n        `mangadex:manga-meta:${md5(deDuplidatedIds.join(''))}`, // shorten the key\n        async () => {\n            const { data } = await got.get(\n                constants.API.MANGA_META.slice(0, -1) +\n                    toQueryString({\n                        ids: deDuplidatedIds,\n                        includes,\n                        limit: deDuplidatedIds.length,\n                    })\n            );\n\n            if (data.result === 'error') {\n                throw new Error('Failed to retrieve manga meta from MangaDex API.');\n            }\n            return data.data;\n        }\n    )) as any[];\n\n    const languages = [...(typeof lang === 'string' ? [lang] : lang || []), ...(await getFilteredLanguages())].filter(Boolean);\n\n    const map = new Map<string, { id: string; title: string; description: string; cover?: string }>();\n    for (const rawMangaMeta of rawMangaMetas) {\n        const id = rawMangaMeta.id;\n\n        const titles = {\n            ...rawMangaMeta.attributes.title,\n            ...Object.fromEntries(rawMangaMeta.attributes.altTitles.flatMap((element) => Object.entries(element))),\n        };\n\n        const title = firstMatch(titles, [...languages, rawMangaMeta.attributes.originalLanguage]) as string;\n","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/mangadex/_feed.ts#L72-L108","documentation":"Thrown when a bulk manga-metadata fetch (GET /manga?ids[]=...&includes[]=...&limit=...) returns data.result === 'error'. Unlike the single-manga path it uses a generic message rather than rethrowing data.errors[0].detail, so the underlying MangaDex error is hidden. Result is cached under a hashed key built from the de-duplicated id list.","triggerScenarios":"Bulk GET https://api.mangadex.org/manga?ids=<list> where MangaDex returns an error result: too many ids in one request (over the endpoint limit), any id is malformed, or rate limiting.","commonSituations":"User follows/lists returned more manga ids than the bulk endpoint accepts; one invalid id in the list poisons the whole batch; transient API error cached for the route expire duration.","solutions":["Reduce the batch size of manga ids sent to getMangaMetaByIds (chunk to <=100 per request).","Filter out non-UUID ids before calling the bulk endpoint.","Re-throw data.errors[0].detail (as the single-id path does) so the real MangaDex error is visible.","Clear the hashed cache key if a transient API error got cached."],"exampleFix":"// before\nif (data.result === 'error') {\n    throw new Error('Failed to retrieve manga meta from MangaDex API.');\n}\n// after\nif (data.result === 'error') {\n    throw new Error(`Failed to retrieve manga meta from MangaDex API: ${data.errors?.[0]?.detail ?? 'unknown error'}`);\n}","handlingStrategy":"validation","validationCode":"const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nfunction sanitizeIdList(ids: string[], max = 100) {\n    const clean = ids.filter((id) => UUID_RE.test(id));\n    if (clean.length > max) {\n        throw new Error(`Bulk manga request exceeds ${max} ids`);\n    }\n    return clean;\n}","typeGuard":"const isMangadexErrorResult = (v: unknown): v is { result: 'error'; errors: Array<{ detail: string }> } =>\n    typeof v === 'object' && v !== null && (v as { result?: string }).result === 'error';","tryCatchPattern":"try {\n    metas = await getMangaMetaByIds(ids);\n} catch (e) {\n    // fall back to per-id fetches so one bad id does not break the batch\n    metas = await Promise.all(ids.map((id) => getMangaMeta(id).catch(() => null)));\n}","preventionTips":["Cap bulk id lists to the MangaDex endpoint limit (~100).","Pre-filter non-UUID ids before the bulk call.","Surface data.errors[0].detail (patch the generic message) to speed diagnosis."],"tags":["mangadex","api","validation","network"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}