jackwener/OpenCLI · error · Error

invalid JSON from ${requestUrl}: ${error.message}

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.

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)

Solutions

  1. Check the JSON.parse message for the exact syntax error position — '<' at position 0 means HTML was returned
  2. Refresh TikTok session cookies; an HTML body usually means the session is no longer valid
  3. Log the raw body once to see what is actually being served
  4. 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

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.

Related errors


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