DIYgod/RSSHub · error · Error

Failed to fetch episode list from Castbox

Error message

Failed to fetch episode list from Castbox

What it means

Thrown by the Castbox channel route when the episode-list endpoint `https://everest.castbox.fm/data/episode_list/v2` returns a body whose `.data.episode_list` is missing or falsy. Distinct from the channel-data guard (it fires after channel data is confirmed). Plain `Error`.

Solutions

  1. Confirm the channel actually has episodes on castbox.fm.
  2. Retry once to rule out transient API hiccup.
  3. If all channels fail, the `getNonce` params (`{ cid, limit, r: 1, raw: 1, web: 1 }`) likely no longer match Castbox's expected signature — update them.
  4. Dump the raw episode_list response body to see whether Castbox returns an error code/message.
Defensive patterns

Strategy: try-catch

Type guard

function hasEpisodeList(r: unknown): r is { data: { episode_list: unknown[] } } {
    return Boolean(r && typeof r === 'object' && (r as any).data?.episode_list);
}

Try / catch

try {
    const epData = await ofetch(episodeUrl);
    if (!epData?.data?.episode_list) {
    // retry once, then throw with the upstream body for diagnosis
    }
} catch (e) {
    throw new Error(`Castbox episode API unreachable: ${(e as Error).message}`);
}

Prevention

When it happens

Trigger: Channel exists but has zero episodes and Castbox omits the `episode_list` key entirely; API version bump removes `episode_list`; nonce rejection on the second call; `limit` query param out of accepted range causes Castbox to return an error envelope.

Common situations: Newly created channels with no published episodes, channels that are video-only on a different endpoint, or a breaking change in Castbox's `/episode_list/v2` response schema.

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/1485fe49c588a0ff. Report an issue: GitHub.

Appendix: source

Thrown at lib/routes/castbox/channel.ts:77

        const channelParams = { cid, r: 1, raw: 1, web: 1 };
        const { m: cm, n: cn, queryStr: cQuery } = getNonce(channelParams);

        const channelData = await ofetch(`https://everest.castbox.fm/data/channel/v3?${cQuery}&m=${cm}&n=${cn}`);

        if (!channelData?.data) {
            throw new Error('Failed to fetch channel data from Castbox');
        }

        const chData = channelData.data;
        const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit') as string) : 50;

        const epParams = { cid, limit, r: 1, raw: 1, web: 1 };
        const { m: em, n: en, queryStr: eQuery } = getNonce(epParams);

        const epData = await ofetch(`https://everest.castbox.fm/data/episode_list/v2?${eQuery}&m=${em}&n=${en}`);

        if (!epData?.data?.episode_list) {
            throw new Error('Failed to fetch episode list from Castbox');
        }

        const episodes = epData.data.episode_list;

        const items = episodes.map((ep: any) => {
            let enclosure_type = 'audio/mpeg';
            if (ep.video === 1 || ep.url?.includes('.mp4')) {
                enclosure_type = 'video/mp4';
            } else if (ep.url?.includes('.m4a')) {
                enclosure_type = 'audio/mp4';
            }

            return {
                title: ep.title,
                description: ep.description,
                pubDate: parseDate(ep.release_date),
                link: `https://castbox.fm/episode/${encodeURIComponent(ep.title)}-id${cid}-id${ep.eid}`,
                enclosure_url: ep.url,

View on GitHub (pinned to bed535e087)