{"record":{"id":"a8041a7fe5fbe8f8","repo":"jackwener/OpenCLI","slug":"xiaoyuzhou-api-request-failed-with-http-response","errorCode":null,"errorMessage":"Xiaoyuzhou API request failed with HTTP ${response.status}${bodyText ? `: ${bodyText}` : ''}","messagePattern":"Xiaoyuzhou API request failed with HTTP (.+?)(.+?)` : ''\\}","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/auth.js","lineNumber":224,"sourceCode":"    return response;\n}\n\nexport async function requestXiaoyuzhouJson(endpoint, options = {}, fetchImpl = fetch) {\n    let credentials = options.credentials ?? loadXiaoyuzhouCredentials();\n    if (shouldRefreshXiaoyuzhouCredentials(credentials)) {\n        credentials = await refreshXiaoyuzhouCredentials(credentials, fetchImpl);\n    }\n    let response = await performXiaoyuzhouJsonRequest(endpoint, options, credentials, fetchImpl);\n    if (response.status === 401) {\n        credentials = await refreshXiaoyuzhouCredentials(credentials, fetchImpl);\n        response = await performXiaoyuzhouJsonRequest(endpoint, options, credentials, fetchImpl);\n    }\n    const bodyText = await response.text();\n    if (!response.ok) {\n        if (response.status === 401 || response.status === 403) {\n            throw createXiaoyuzhouAuthError(`Xiaoyuzhou API rejected the credentials with HTTP ${response.status}`);\n        }\n        throw new CommandExecutionError(`Xiaoyuzhou API request failed with HTTP ${response.status}${bodyText ? `: ${bodyText}` : ''}`);\n    }\n    let parsed;\n    try {\n        parsed = JSON.parse(bodyText);\n    }\n    catch (error) {\n        throw new CommandExecutionError(`Xiaoyuzhou API returned invalid JSON: ${getErrorMessage(error)}`);\n    }\n    const serviceCode = parsed?.code;\n    if (serviceCode !== undefined && serviceCode !== null) {\n        const numericCode = Number(serviceCode);\n        if (!Number.isFinite(numericCode)) {\n            throw new CommandExecutionError('Xiaoyuzhou API returned an invalid service code');\n        }\n        if (numericCode === 401 || numericCode === 403) {\n            throw createXiaoyuzhouAuthError(`Xiaoyuzhou API rejected the credentials with service code ${numericCode}`);\n        }\n        if (numericCode !== 0 && numericCode !== 200) {","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/auth.js#L206-L242","documentation":"Thrown by requestXiaoyuzhouJson when the API responds with a non-OK HTTP status other than 401/403 (those become AUTH_REQUIRED errors instead, after one automatic refresh+retry on 401). CommandExecutionError carries the status code and any response body, meaning credentials were accepted/irrelevant but the request itself failed — typically a bad endpoint, invalid parameters, 404, 429 rate limit, or 5xx server error.","triggerScenarios":"requestXiaoyuzhouJson against an endpoint that returns e.g. HTTP 400 (malformed query/body), 404 (wrong episode/endpoint path), 429 (too many requests), or 5xx (Xiaoyuzhou server outage) after the 401-refresh-retry path did not apply.","commonSituations":"Passing an invalid or deleted episode id (404); hard-coded endpoint paths broken by an API version change; scripting that polls the API too aggressively (429); transient Xiaoyuzhou outages (502/503); sending a body the server rejects (400).","solutions":["Read the status code and body in the message: 429 → back off and retry with exponential delay; 5xx → retry later; 400/404 → fix the request.","For 404/400, verify the endpoint path and parameters (e.g. correct eid/pid) against the current API.","For 429, add rate limiting (e.g. delay between requests) or honor Retry-After.","For persistent 5xx, check Xiaoyuzhou service status / retry with backoff via a custom fetchImpl."],"exampleFix":"// before: blind call, 429 kills the script\nconst { data } = await requestXiaoyuzhouJson('/episode/feed', { query: { eid } });\n// after: handle retryable statuses explicitly\nasync function callApi(endpoint, options) {\n  try { return await requestXiaoyuzhouJson(endpoint, options); }\n  catch (e) {\n    const m = /HTTP (\\d{3})/.exec(e.message);\n    const status = m ? Number(m[1]) : 0;\n    if (status === 429 || status >= 500) {\n      await new Promise(r => setTimeout(r, 5000));\n      return requestXiaoyuzhouJson(endpoint, options);\n    }\n    throw e;\n  }\n}","handlingStrategy":"try-catch","validationCode":"// validate inputs before calling the API to avoid 400/404-class failures\nfunction assertEpisodeId(eid) {\n  if (typeof eid !== 'string' || !/^[0-9a-f-]{10,}$/i.test(eid.trim())) {\n    throw new Error(`Invalid episode id '${eid}' — request would fail with HTTP 400/404.`);\n  }\n}","typeGuard":"function isApiHttpError(err) {\n  if (!(err instanceof Error)) return false;\n  const m = /^Xiaoyuzhou API request failed with HTTP (\\d{3})/.exec(err.message);\n  return m !== null ? { status: Number(m[1]) } : false;\n}","tryCatchPattern":"import { CommandExecutionError, CliError } from '@jackwener/opencli/errors';\ntry {\n  const { data } = await requestXiaoyuzhouJson('/episodes/get', { query: { eid } });\n} catch (err) {\n  if (err instanceof CommandExecutionError) {\n    const status = Number(/HTTP (\\d{3})/.exec(err.message)?.[1] ?? 0);\n    if (status === 429 || status >= 500) {\n      // retryable: back off and try again\n      await new Promise(r => setTimeout(r, 5000));\n      return requestXiaoyuzhouJson('/episodes/get', { query: { eid } });\n    }\n    if (status === 400 || status === 404) {\n      console.error('Bad request: verify the endpoint path and parameters.', err.message);\n    }\n  }\n  throw err;\n}","preventionTips":["Distinguish retryable statuses (429, 5xx) from client errors (400, 404) and only retry the former.","Honor rate limits: throttle polling loops and respect Retry-After headers to avoid 429s.","Validate endpoint paths and query parameters (eid/pid) before calling; keep endpoints current with API changes.","Treat 401/403 separately — those arrive as AUTH_REQUIRED CliError, not this CommandExecutionError."],"tags":["http","api","rate-limit","network"],"backgroundTag":"http-error-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}