{"record":{"id":"e0a67096eced689a","repo":"jackwener/OpenCLI","slug":"api-error-e0a670","errorCode":"API_ERROR","errorMessage":"toErrorMessage(payload, fallback)","messagePattern":"toErrorMessage\\(payload, fallback\\)","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/paperreview/utils.js","lineNumber":120,"sourceCode":"    if (rawText) {\n        try {\n            payload = JSON.parse(rawText);\n        }\n        catch {\n            payload = rawText;\n        }\n    }\n    return { response, payload };\n}\nexport function ensureSuccess(response, payload, fallback, hint) {\n    if (!response.ok) {\n        const code = response.status === 404 ? 'NOT_FOUND' : 'API_ERROR';\n        throw new CliError(code, toErrorMessage(payload, fallback), hint);\n    }\n}\nexport function ensureApiSuccess(payload, fallback, hint) {\n    if (!payload || typeof payload !== 'object' || payload.success !== true) {\n        throw new CliError('API_ERROR', toErrorMessage(payload, fallback), hint);\n    }\n}\nexport function createUploadForm(urlData, pdfFile) {\n    const form = new FormData();\n    for (const [key, value] of Object.entries(urlData.presigned_fields ?? {})) {\n        form.append(key, value);\n    }\n    form.append('file', new Blob([new Uint8Array(pdfFile.buffer)], { type: 'application/pdf' }), pdfFile.fileName);\n    return form;\n}\nexport async function uploadPresignedPdf(presignedUrl, pdfFile, urlData) {\n    let response;\n    try {\n        response = await fetch(presignedUrl, {\n            method: 'POST',\n            body: createUploadForm(urlData, pdfFile),\n        });\n    }","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/paperreview/utils.js#L102-L138","documentation":"ensureApiSuccess() validates that a JSON response payload from paperreview.ai has success === true. If the payload is missing, not an object, or has success !== true, it throws API_ERROR with the message derived from the payload (detail/message/error field, text) or the fallback. This is the application-level success check, distinct from HTTP status checks.","triggerScenarios":"Calling an endpoint whose 200-OK JSON body contains success:false or an unexpected shape — e.g. the confirm/status endpoints returning { success: false, detail: '...' }, a non-JSON body parsed as a string, or an empty payload from an endpoint change.","commonSituations":"Server-side business-rule rejection despite HTTP 200 (e.g. invalid state, quota exhausted); API contract change where success field was renamed; the server returned an HTML error page captured as a plain string payload.","solutions":["Read the thrown message — it surfaces the server's detail/message/error explaining the rejection","Confirm the request body/params are valid for the endpoint (e.g. correct s3Key, email format)","Retry if the failure looks transient (quota, rate limits), otherwise fix the request","Check for an API version change if the payload shape looks unfamiliar"],"exampleFix":"// before\nensureApiSuccess(payload, 'Submission failed'); // generic failure, cause unknown\n// after\nif (payload && typeof payload === 'object' && payload.success !== true) {\n    console.error('Server said:', payload.detail ?? payload.message ?? payload.error);\n}\nensureApiSuccess(payload, 'Submission failed');","handlingStrategy":"type-guard","validationCode":"function isApiSuccessPayload(payload) {\n    return payload !== null && typeof payload === 'object' && payload.success === true;\n}\n// run before relying on the response data (ensureApiSuccess does this check internally)","typeGuard":"function isApiSuccessPayload(payload) {\n    return typeof payload === 'object' && payload !== null && 'success' in payload && payload.success === true;\n}","tryCatchPattern":"try {\n    ensureApiSuccess(payload, 'Submission failed');\n} catch (e) {\n    if (e?.code === 'API_ERROR') {\n        const detail = payload && typeof payload === 'object' ? (payload.detail ?? payload.message ?? payload.error) : payload;\n        console.error('Server rejected the operation:', detail ?? e.message);\n    } else throw e;\n}","preventionTips":["Always inspect the server's detail/message/error fields surfaced in the message","Validate request bodies against the documented API schema","Treat success:false as a business-rule rejection, not a transport bug","Pin/monitor API versions; alert if payload shape changes"],"tags":["api","validation","response-shape","error-handling"],"backgroundTag":"api-response-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}