jackwener/OpenCLI · error · CliError

API_ERROR

API_ERROR

Error message

toErrorMessage(payload, fallback)

What it means

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.

Source

Thrown at clis/paperreview/utils.js:120

    if (rawText) {
        try {
            payload = JSON.parse(rawText);
        }
        catch {
            payload = rawText;
        }
    }
    return { response, payload };
}
export function ensureSuccess(response, payload, fallback, hint) {
    if (!response.ok) {
        const code = response.status === 404 ? 'NOT_FOUND' : 'API_ERROR';
        throw new CliError(code, toErrorMessage(payload, fallback), hint);
    }
}
export function ensureApiSuccess(payload, fallback, hint) {
    if (!payload || typeof payload !== 'object' || payload.success !== true) {
        throw new CliError('API_ERROR', toErrorMessage(payload, fallback), hint);
    }
}
export function createUploadForm(urlData, pdfFile) {
    const form = new FormData();
    for (const [key, value] of Object.entries(urlData.presigned_fields ?? {})) {
        form.append(key, value);
    }
    form.append('file', new Blob([new Uint8Array(pdfFile.buffer)], { type: 'application/pdf' }), pdfFile.fileName);
    return form;
}
export async function uploadPresignedPdf(presignedUrl, pdfFile, urlData) {
    let response;
    try {
        response = await fetch(presignedUrl, {
            method: 'POST',
            body: createUploadForm(urlData, pdfFile),
        });
    }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the thrown message — it surfaces the server's detail/message/error explaining the rejection
  2. Confirm the request body/params are valid for the endpoint (e.g. correct s3Key, email format)
  3. Retry if the failure looks transient (quota, rate limits), otherwise fix the request
  4. Check for an API version change if the payload shape looks unfamiliar

Example fix

// before
ensureApiSuccess(payload, 'Submission failed'); // generic failure, cause unknown
// after
if (payload && typeof payload === 'object' && payload.success !== true) {
    console.error('Server said:', payload.detail ?? payload.message ?? payload.error);
}
ensureApiSuccess(payload, 'Submission failed');
Defensive patterns

Strategy: type-guard

Validate before calling

function isApiSuccessPayload(payload) {
    return payload !== null && typeof payload === 'object' && payload.success === true;
}
// run before relying on the response data (ensureApiSuccess does this check internally)

Type guard

function isApiSuccessPayload(payload) {
    return typeof payload === 'object' && payload !== null && 'success' in payload && payload.success === true;
}

Try / catch

try {
    ensureApiSuccess(payload, 'Submission failed');
} catch (e) {
    if (e?.code === 'API_ERROR') {
        const detail = payload && typeof payload === 'object' ? (payload.detail ?? payload.message ?? payload.error) : payload;
        console.error('Server rejected the operation:', detail ?? e.message);
    } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/e0a67096eced689a. Report an issue: GitHub.