{"record":{"id":"dc383247c73413f1","repo":"jackwener/OpenCLI","slug":"ctrip-flight-api-returned-http-status-complete","errorCode":null,"errorMessage":"Ctrip flight API returned HTTP ${status}; complete any verification in the browser and retry","messagePattern":"Ctrip flight API returned HTTP (.+?); complete any verification in the browser and retry","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/flight.js","lineNumber":69,"sourceCode":"function cabinLabel(value) {\n    const labels = { Y: '经济舱', S: '超级经济舱', C: '公务舱', F: '头等舱' };\n    const codes = [...new Set(cleanString(value).toUpperCase().match(/[YSCF]/g) || [])];\n    return codes.length > 0 ? codes.map((code) => labels[code]).join('/') : (cleanString(value) || null);\n}\n\nfunction parseBatchSearchCaptures(entries) {\n    if (!Array.isArray(entries)) {\n        throw new CommandExecutionError('Ctrip flight network capture returned malformed entries');\n    }\n    const captured = entries.filter((entry) => String(entry?.url || '').includes(CAPTURE_PATTERN));\n    if (captured.length === 0) return null;\n\n    const byId = new Map();\n    let finished = false;\n    for (const entry of captured) {\n        const status = Number(entry?.responseStatus || 0);\n        if (status === 401 || status === 403) {\n            throw new AuthRequiredError('flights.ctrip.com', `Ctrip flight API returned HTTP ${status}; complete any verification in the browser and retry`);\n        }\n        if (status !== 200) {\n            throw new CommandExecutionError(`Ctrip flight API returned HTTP ${status || 'unknown'}`);\n        }\n        if (entry?.responseBodyTruncated === true) {\n            throw new CommandExecutionError('Ctrip flight API response exceeded the browser capture limit');\n        }\n        if (typeof entry?.responsePreview !== 'string') {\n            throw new CommandExecutionError('Ctrip flight API response body was unavailable');\n        }\n        let payload;\n        try {\n            payload = JSON.parse(entry.responsePreview);\n        }\n        catch {\n            throw new CommandExecutionError('Ctrip flight API returned invalid JSON');\n        }\n        if (payload?.status !== 0) {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/flight.js#L51-L87","documentation":"This AuthRequiredError is thrown when a captured Ctrip batchSearch response comes back with HTTP 401 or 403, meaning flights.ctrip.com rejected the request as unauthenticated or forbidden. The library drives a real browser and captures the API response via CDP; Ctrip's risk-control layer (login state, device fingerprint, anti-bot checks) decides who may call the endpoint, so a 401/403 means the session needs human verification rather than a code fix. The library throws this instead of a generic error so callers know to complete any CAPTCHA/verification in the browser and retry.","triggerScenarios":"Any itineraries search run where the captured /international/search/api/search/batchSearch response has responseStatus 401 or 403 — e.g. an expired or missing Ctrip login session, a flagged bot-like session, or Ctrip's risk control serving a challenge that the automated flow did not complete.","commonSituations":"Running the CLI after long gaps so the Ctrip session cookie expired; headless-browser fingerprints tripping Ctrip's anti-bot; running from datacenter IPs or high request volume; the in-page CAPTCHA (验证码/安全验证) appearing mid-flow.","solutions":["Open the browser session used by the CLI, complete any CAPTCHA or login/verification prompt on flights.ctrip.com, then re-run the itineraries command.","Re-authenticate (log into Ctrip in the automation browser) so the session has valid credentials before retrying.","Slow down request rate or rotate to a residential IP if Ctrip risk control keeps blocking the session.","Catch AuthRequiredError in your wrapper and surface an interactive-retry step to the user instead of auto-retrying."],"exampleFix":"// before: blind retry loop\nwhile (tries--) { await cli.itineraries(args); }\n// after: catch auth error and prompt for human verification\ntry {\n  const rows = await cli.itineraries(args);\n} catch (err) {\n  if (err instanceof AuthRequiredError) {\n    console.error('Complete verification in the browser, then rerun.');\n    await promptUserToVerifyBrowser(); // then retry once\n  } else throw err;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const rows = await cli.itineraries(args);\n} catch (err) {\n  if (err instanceof AuthRequiredError) {\n    // surface to user: open browser, complete CAPTCHA/login on flights.ctrip.com, retry once\n    await interactiveVerifyAndRetry();\n  } else { throw err; }\n}","preventionTips":["Keep a logged-in, warmed-up Ctrip session in the automation browser.","Complete any in-page verification promptly; do not let challenges linger.","Rate-limit searches and avoid datacenter IPs to stay under risk-control thresholds.","Catch AuthRequiredError explicitly and implement a human-in-the-loop retry."],"tags":["auth","http-403","anti-bot","captcha","browser-automation"],"backgroundTag":"http-401-403-auth-required","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}