DIYgod/RSSHub · error · Error

Fortnite API returned non-JSON response with content-type ${

Error message

Fortnite API returned non-JSON response with content-type ${contentType ?? 'unknown'}

What it means

Even when the HTTP status is OK, the response may not be JSON. The route inspects the content-type header and requires it to contain 'application/json'; otherwise the body is assumed to be an HTML error page, interstitial, or CAPTCHA, and the route throws with the observed content-type. This guards against silently feeding HTML into JSON parsing downstream.

Source

Thrown at lib/routes/fortnite/news.ts:69

    // log manually (necessary for Playwright)
    logger.http(`Requesting ${apiUrl}`);
    let data;
    try {
        const response = await page.goto(apiUrl, {
            waitUntil: 'networkidle',
        });
        if (!response) {
            throw new Error(`No response received from ${apiUrl}`);
        }
        if (!response.ok()) {
            const statusText = response.statusText();
            const statusMessage = [response.status(), statusText].filter(Boolean).join(' ');
            throw new Error(`Fortnite API responded with ${statusMessage} for ${apiUrl}`);
        }
        const contentType = response.headers()['content-type'];
        if (!contentType?.includes('application/json')) {
            throw new Error(`Fortnite API returned non-JSON response with content-type ${contentType ?? 'unknown'}`);
        }

        data = await response.json();
    } finally {
        await page.close();
        await context.close();
    }

    const { blogList: list } = data;

    const items = await Promise.all(
        list.map((item) =>
            cache.tryGet(item.link, () =>
                Promise.resolve({
                    title: item.title,
                    link: `${rootUrl}/${path}/${item.slug}?lang=${language}`,
                    pubDate: parseDate(item.date),
                    author: item.author,

View on GitHub (pinned to bed535e087)

Solutions

  1. Log or return the first 500 bytes of the body to identify whether it is a CAPTCHA, maintenance page, or redirect.
  2. Set an explicit Accept: application/json header in the Playwright request context / interceptor.
  3. Check whether a WAF challenge is involved and whether the route needs the Playwright context to solve it before the API call.
  4. Update the content-type check if the API now serves application/vnd.api+json or similar.

Example fix

// before
const contentType = response.headers()['content-type'];
if (!contentType?.includes('application/json')) {
    throw new Error(`Fortnite API returned non-JSON response with content-type ${contentType ?? 'unknown'}`);
}

// after
const contentType = response.headers()['content-type'] ?? '';
if (!contentType.includes('json')) {
    const bodySnippet = (await response.text()).slice(0, 200);
    throw new Error(`Fortnite API returned non-JSON (${contentType || 'unknown'}). Body preview: ${bodySnippet}`);
}
Defensive patterns

Strategy: type-guard

Validate before calling

// set an explicit Accept header in the Playwright context
await context.setExtraHTTPHeaders({ Accept: 'application/json' });

Type guard

const isJsonContentType = (ct: string | undefined): boolean => !!ct && ct.includes('json');

Prevention

When it happens

Trigger: Fortnite's infrastructure returns an HTML maintenance/CAPTCHA page with a 200 status, a CDN/WAF serves an HTML challenge page (Cloudflare, Akamai), the endpoint was replaced with an HTML redirect page, or the API now returns JSON-LD or a different content-type token. The contentType variable holds whatever was sent (or 'unknown' if the header was absent).

Common situations: Anti-bot/WAF challenge pages served before the real API responds; Epic Games rotating to a GraphQL or different content-type; CDN caching an old HTML version; the browser sending an Accept header that triggers HTML negotiation.

Related errors


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