jackwener/OpenCLI · error · Error
invalid JSON from
Error message
invalid JSON from ${requestUrl}: ${error.message} What it means
The fetch helper JSON-parses the response body and, if JSON.parse throws, re-throws an Error naming the URL and the parser's message. It guards against TikTok returning HTML (login walls, captchas, error pages) or truncated bodies where JSON was expected.
Solutions
- Check the JSON.parse message for the exact syntax error position — '<' at position 0 means HTML was returned
- Refresh TikTok session cookies; an HTML body usually means the session is no longer valid
- Log the raw body once to see what is actually being served
- Verify the URL wasn't redirected (check response.url) to a login page
Example fix
// before
return JSON.parse(text);
// after
if (text.trimStart().startsWith('<')) {
throw new Error('received HTML instead of JSON (likely auth wall) from ' + requestUrl);
}
return JSON.parse(text); Defensive patterns
Strategy: type-guard
Validate before calling
const res = await fetch(url, { credentials: 'include' });
const text = await res.text();
if (text.trimStart().startsWith('<')) throw new Error('got HTML (auth wall?) — refresh session'); Type guard
function isJsonString(s) { if (typeof s !== 'string') return false; try { JSON.parse(s); return true; } catch { return false; } } Try / catch
try { return JSON.parse(text); } catch (e) {
if (text.trimStart().startsWith('<')) throw new AuthRequiredError('tiktok.com', 'HTML served instead of JSON');
throw e;
} Prevention
- Inspect Content-Type before parsing
- Treat '<'-leading bodies as auth walls and refresh cookies
- Log one raw response when parsing fails
- Handle empty bodies explicitly ({} case already exists)
When it happens
Trigger: The endpoint returned 200 but a non-JSON body — typically an HTML login/captcha page, an empty-but-whitespace-only body is fine (returns {}), but any other non-JSON payload fails JSON.parse.
Common situations: Expired session redirects the API to the login HTML page; WAF serves a challenge page with 200; CDN error page returned instead of JSON; response cut off mid-body by proxy.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- archive search returned malformed JSON
- archive wayback returned malformed JSON
- hf models returned malformed JSON
- returned malformed JSON
- returned malformed JSON
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/6dcd69751cb5bb87.
Report an issue: GitHub.
Appendix: source
Thrown at clis/tiktok/utils.js:244
}
return '';
}
async function fetchJson(url) {
const requestUrl = new URL(url, ${JSON.stringify(TIKTOK_HOST)}).toString();
const res = await fetch(requestUrl, {
credentials: 'include',
headers: { accept: 'application/json,text/plain,*/*' },
});
const text = await res.text();
if (!res.ok) {
throw new Error('HTTP ' + res.status + ' from ' + requestUrl + ': ' + text.slice(0, 160));
}
if (!text.trim()) return {};
try {
return JSON.parse(text);
} catch (error) {
throw new Error('invalid JSON from ' + requestUrl + ': ' + (error instanceof Error ? error.message : String(error)));
}
}
function assertTikTokApiSuccess(data, label) {
if (!data || typeof data !== 'object') return;
const code = data.status_code ?? data.statusCode;
if (code === undefined || code === null || Number(code) === 0) return;
const message = cleanText(data.status_msg ?? data.statusMsg ?? data.message ?? data.msg ?? code, 240);
if (Number(code) === 8 || /auth|captcha|login|permission|unauthori[sz]ed|forbidden/i.test(message)) {
throw new Error('AUTH_REQUIRED: ' + label + ' API failed: ' + message);
}
throw new Error(label + ' API failed: ' + message);
}
function findUniversalData() {
const scripts = Array.from(document.querySelectorAll('script'));
for (const script of scripts) {
const text = script.textContent || '';View on GitHub (pinned to 49907e53dc)