{"record":{"id":"d2b001282f7964d4","repo":"DIYgod/RSSHub","slug":"api-request-failed-error","errorCode":null,"errorMessage":"API request failed: ${error}","messagePattern":"API request failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/routes/mercari/util.tsx","lineNumber":288,"sourceCode":"\n    const headers = new Headers({\n        DPOP,\n        'X-Platform': 'web',\n        'Accept-Encoding': 'gzip, deflate',\n        'Content-Type': 'application/json; charset=utf-8',\n    });\n\n    const options = {\n        method,\n        headers,\n        body: method === 'POST' ? JSON.stringify(data) : undefined,\n        query: method === 'GET' ? data : undefined,\n    };\n\n    try {\n        return await ofetch<T>(url, options);\n    } catch (error) {\n        throw new Error(`API request failed: ${error}`, { cause: error });\n    }\n};\n\nconst pageToPageToken = (page: number): string => {\n    if (page === 0) {\n        return '';\n    }\n    return `v1:${page}`;\n};\n\ninterface SearchOptions {\n    categoryId?: number[];\n    brandId?: number[];\n    priceMin?: number;\n    priceMax?: number;\n    itemConditionId?: number[];\n    excludeKeyword?: string;\n    itemTypes?: string[];","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/mercari/util.tsx#L270-L306","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","If Mercari changed its API, update generateDPOP and the endpoint URLs in lib/routes/mercari/util.tsx.","Add retry logic with exponential backoff for transient (5xx, network) failures.","For geo-blocking, route the request through a Japan-region proxy."],"exampleFix":"// before\ntry {\n    return await ofetch<T>(url, options);\n} catch (error) {\n    throw new Error(`API request failed: ${error}`, { cause: error });\n}\n\n// after — surface status code, retry transient failures\ntry {\n    return await ofetch<T>(url, options);\n} catch (error: any) {\n    const status = error?.response?.status ?? error?.response?.statusCode;\n    throw new Error(`Mercari API request to ${url} failed (status ${status ?? 'n/a'}): ${error.message ?? error}`, { cause: error });\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isHttpError(e: unknown): e is { response?: { status?: number; statusCode?: number }; message?: string } {\n    return typeof e === 'object' && e !== null && 'response' in e;\n}","tryCatchPattern":"async function fetchWithRetry<T>(url: string, data: any, method: 'POST' | 'GET', retries = 2): Promise<T> {\n    for (let attempt = 0; attempt <= retries; attempt++) {\n        try {\n            return await fetchFromMercari<T>(url, data, method);\n        } catch (error: any) {\n            const status = error?.response?.status ?? error?.response?.statusCode;\n            const transient = !status || status >= 500 || status === 429;\n            if (!transient || attempt === retries) {\n                throw new Error(`Mercari API failed (status ${status ?? 'n/a'}): ${error.message ?? error}`, { cause: error });\n            }\n            await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));\n        }\n    }\n    throw new Error('unreachable');\n}","preventionTips":["Inspect error.cause.response.status to classify the failure (auth, rate-limit, server, network).","Retry only transient failures (5xx, 429, network) with exponential backoff.","Keep the DPOP signing keys current — Mercari rotates them.","For geo-blocked deployments, route requests through a Japan egress."],"tags":["network","api","mercari","error-wrapping","authentication"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}