DIYgod/RSSHub · error · Error

API request failed: ${error}

Error message

API request failed: ${error}

What it means

Thrown by the Mercari fetch helper when the underlying ofetch call rejects for any reason — network error, non-2xx HTTP status, TLS failure, or a body-parse error. The helper wraps the original error in a new Error with message 'API request failed: <error>' and attaches the original via { cause }. The <error> is the stringified error object, so it often includes the HTTP response details.

Source

Thrown at lib/routes/mercari/util.tsx:288

    const headers = new Headers({
        DPOP,
        'X-Platform': 'web',
        'Accept-Encoding': 'gzip, deflate',
        'Content-Type': 'application/json; charset=utf-8',
    });

    const options = {
        method,
        headers,
        body: method === 'POST' ? JSON.stringify(data) : undefined,
        query: method === 'GET' ? data : undefined,
    };

    try {
        return await ofetch<T>(url, options);
    } catch (error) {
        throw new Error(`API request failed: ${error}`, { cause: error });
    }
};

const pageToPageToken = (page: number): string => {
    if (page === 0) {
        return '';
    }
    return `v1:${page}`;
};

interface SearchOptions {
    categoryId?: number[];
    brandId?: number[];
    priceMin?: number;
    priceMax?: number;
    itemConditionId?: number[];
    excludeKeyword?: string;
    itemTypes?: string[];

View on GitHub (pinned to bed535e087)

Solutions

  1. Inspect error.cause for the HTTP status code — 401/403 means the DPOP signing flow needs updating, 429 means back off, 5xx means retry later.
  2. If Mercari changed its API, update generateDPOP and the endpoint URLs in lib/routes/mercari/util.tsx.
  3. Add retry logic with exponential backoff for transient (5xx, network) failures.
  4. For geo-blocking, route the request through a Japan-region proxy.

Example fix

// before
try {
    return await ofetch<T>(url, options);
} catch (error) {
    throw new Error(`API request failed: ${error}`, { cause: error });
}

// after — surface status code, retry transient failures
try {
    return await ofetch<T>(url, options);
} catch (error: any) {
    const status = error?.response?.status ?? error?.response?.statusCode;
    throw new Error(`Mercari API request to ${url} failed (status ${status ?? 'n/a'}): ${error.message ?? error}`, { cause: error });
}
Defensive patterns

Strategy: retry

Type guard

function isHttpError(e: unknown): e is { response?: { status?: number; statusCode?: number }; message?: string } {
    return typeof e === 'object' && e !== null && 'response' in e;
}

Try / catch

async function fetchWithRetry<T>(url: string, data: any, method: 'POST' | 'GET', retries = 2): Promise<T> {
    for (let attempt = 0; attempt <= retries; attempt++) {
        try {
            return await fetchFromMercari<T>(url, data, method);
        } catch (error: any) {
            const status = error?.response?.status ?? error?.response?.statusCode;
            const transient = !status || status >= 500 || status === 429;
            if (!transient || attempt === retries) {
                throw new Error(`Mercari API failed (status ${status ?? 'n/a'}): ${error.message ?? error}`, { cause: error });
            }
            await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
        }
    }
    throw new Error('unreachable');
}

Prevention

When it happens

Trigger: Mercari's DPOP/JWT signature is rejected (auth failure → 401/403). Rate limiting (429). Mercari API maintenance or outage (5xx). Network connectivity issues from the RSSHub server. The request URL/query is malformed. Mercari changed its API version or endpoint path.

Common situations: The DPOP key/certificate used for signing is expired or rotated by Mercari. The server's IP is geo-blocked (Mercari is Japan-only). Transient network blips. Mercari deploys a breaking API change and all requests start failing.

Related errors


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