jackwener/OpenCLI · error
${label} returned invalid JSON
Error message
${label} returned invalid JSON What it means
readInstagramJson in the in-page resolver wraps response.json(); if the body is not valid JSON it throws '<label> returned invalid JSON'. This surfaces when Instagram's web_profile_info or feed-by-username endpoints return non-JSON bodies (HTML login/challenge pages, empty or truncated bodies) with an HTTP status that passed the earlier ok checks.
Source
Thrown at clis/instagram/_shared/user-id.js:20
* In-page snippet that resolves `username` to a numeric user id in `userId`.
*
* `web_profile_info` answers HTTP 400 for business and professional accounts,
* so the commands that need an id fall back to feed-by-username. Its root
* `user.pk` is the profile owner; `items[0].user.pk` can be a pinned collab
* author. Callers must already have `username` and `opts` in scope.
*/
export function buildResolveInstagramUserIdJs() {
return `
function normalizeInstagramUserId(value, label) {
const id = typeof value === 'number' ? String(value) : (typeof value === 'string' ? value.trim() : '');
if (!/^\\d+$/.test(id)) throw new Error(label);
return id;
}
async function readInstagramJson(response, label) {
try {
return await response.json();
} catch {
throw new Error(label + ' returned invalid JSON');
}
}
function throwInstagramHttpError(response, label, username) {
if (response.status === 404) throw new Error('User not found: ' + username);
if (response.status === 401 || response.status === 403) {
throw new Error('HTTP ' + response.status + ' - make sure you are logged in to Instagram');
}
throw new Error(label + ' failed: HTTP ' + response.status);
}
const r1 = await fetch('https://www.instagram.com/api/v1/users/web_profile_info/?username=' + encodeURIComponent(username), opts);
if (r1.status === 404) throw new Error('User not found: ' + username);
if (!r1.ok && r1.status !== 400) throwInstagramHttpError(r1, 'Instagram web_profile_info', username);
let userId = r1.ok ? normalizeInstagramUserId((await readInstagramJson(r1, 'Instagram web_profile_info'))?.data?.user?.id, 'Instagram web_profile_info returned no valid user id for: ' + username) : '';
if (!userId) {
const r1b = await fetch('https://www.instagram.com/api/v1/feed/user/' + encodeURIComponent(username) + '/username/?count=1', opts);
if (!r1b.ok) throwInstagramHttpError(r1b, 'Instagram feed-by-username', username);
userId = normalizeInstagramUserId((await readInstagramJson(r1b, 'Instagram feed-by-username'))?.user?.pk, 'Instagram feed returned no valid profile owner for: ' + username);
}`;View on GitHub (pinned to 49907e53dc)
Solutions
- Re-login / refresh session cookies — HTML responses at 200 almost always mean the session is gone.
- Log response status and the first bytes of the body to confirm an HTML challenge page.
- Retry after backoff if it is a transient rate-limit interstitial.
- Change IP / use a residential proxy if Instagram is challenge-blocking the current IP.
- Ensure request opts include proper headers/credentials so the API returns JSON.
Example fix
// before
const userId = await page.evaluate(buildResolveInstagramUserIdJs());
// after
try {
return await page.evaluate(buildResolveInstagramUserIdJs());
} catch (e) {
if (String(e.message).includes('returned invalid JSON')) {
await refreshInstagramSession(page);
return page.evaluate(buildResolveInstagramUserIdJs());
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
const cookies = await page.context().cookies('https://www.instagram.com');
if (!cookies.some(c => c.name === 'sessionid')) throw new Error('Session expired — user-id resolution would get HTML'); Type guard
function looksLikeJson(text) {
return /^\s*[{[]/.test(text);
} Try / catch
try {
return await page.evaluate(buildResolveInstagramUserIdJs());
} catch (e) {
if (String(e.message).includes('returned invalid JSON')) {
await refreshInstagramSession(page);
return page.evaluate(buildResolveInstagramUserIdJs()); // retry once
}
throw e;
} Prevention
- Keep the browser session logged in; HTML-at-200 means login is required.
- Avoid heavily rate-limited IPs; Instagram serves interstitials there.
- Sniff response content when debugging to detect HTML vs JSON.
- Retry once after session refresh before giving up.
When it happens
Trigger: response.json() rejects inside readInstagramJson for either fetch — typically because the body is HTML (login redirect, challenge page) or empty despite an otherwise acceptable HTTP status.
Common situations: Expired session cookies so Instagram serves the login HTML page with 200; IP rate-limited with an HTML interstitial; proxy truncating the body; content-type changed by Instagram.
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
- Instagram private publish configure_sidecar returned invalid
- ${label} returned invalid JSON
- Instagram private route could not derive CSRF token from bro
- Instagram story publish could not derive current user id fro
- HTTP ' + res.status + ' - make sure you are logged in to Ins
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/d540e979724c0a3e.
Report an issue: GitHub.