santifer/career-ops · error · Error
wttj: /api/env payload is not valid JSON
Error message
wttj: /api/env payload is not valid JSON
What it means
A {...} span was found in /api/env but JSON.parse rejected it. The brace-matching is greedy (first { to last }), so any stray brace elsewhere on the page (inline JS, analytics) can make the span invalid JSON.
Source
Thrown at providers/wttj.mjs:61
}
return url;
}
/**
* Parse the `window.env = {...}` payload served by /api/env and extract the
* Algolia application id + client search key.
* @param {string} text
* @returns {{ appId: string, apiKey: string }}
*/
export function parseEnvPayload(text) {
const start = text.indexOf('{');
const end = text.lastIndexOf('}');
if (start === -1 || end <= start) throw new Error('wttj: /api/env payload has no JSON object');
let env;
try {
env = JSON.parse(text.slice(start, end + 1));
} catch {
throw new Error('wttj: /api/env payload is not valid JSON');
}
const appId = typeof env.PUBLIC_ALGOLIA_APPLICATION_ID === 'string' ? env.PUBLIC_ALGOLIA_APPLICATION_ID.trim() : '';
const apiKey = typeof env.PUBLIC_ALGOLIA_API_KEY_CLIENT === 'string' ? env.PUBLIC_ALGOLIA_API_KEY_CLIENT.trim() : '';
// App ids are short alphanumerics; validating keeps the derived Algolia
// hostname from being attacker-shaped if the env payload ever changes.
if (!/^[A-Z0-9]{6,16}$/i.test(appId)) throw new Error(`wttj: unexpected Algolia app id "${appId}"`);
// The key is only ever sent as a request header (never used to build a
// host), so don't over-constrain its format — WTTJ may rotate to a longer
// or non-hex (e.g. secured/base64) client key. Length bounds only.
if (!apiKey || apiKey.length < 16 || apiKey.length > 500) {
throw new Error('wttj: unexpected Algolia api key shape');
}
return { appId, apiKey };
}
/**
* Normalize a single Algolia hit. Exported for tests.
*View on GitHub (pinned to 9b17a8ac97)
Solutions
- Inspect the raw /api/env body and locate the real window.env = {...} block.
- Tighten extraction to the window.env assignment instead of greedy first-brace-to-last-brace.
- If WTTJ now emits a JS object literal (unquoted keys, trailing commas), switch to a tolerant parser or a brace-balanced extractor.
Example fix
// before — greedy slice can capture stray braces
const start = text.indexOf("{");
const end = text.lastIndexOf("}");
env = JSON.parse(text.slice(start, end + 1));
// after — scope to window.env and balance braces
const assign = text.indexOf("window.env");
const firstBrace = text.indexOf("{", assign);
const obj = balanceBraces(text, firstBrace); // returns the matched {...}
env = JSON.parse(obj); Defensive patterns
Strategy: try-catch
Validate before calling
const slice = envText.slice(envText.indexOf("{"), envText.lastIndexOf("}") + 1);
try { JSON.parse(slice); }
catch { console.warn("wttj env slice is not strict JSON — extraction may need updating"); } Try / catch
try { parseEnvPayload(envText); }
catch (err) {
if (/not valid JSON/.test(err.message)) { logUpstreamChange("wttj", err.message); }
throw err;
} Prevention
- Prefer a scoped window.env = {...} extraction over greedy brace matching.
- Alert on this error as a likely WTTJ markup change.
- Smoke-test parseEnvPayload against a captured real payload in CI.
When it happens
Trigger: The page has additional {/} outside the env object so the greedy slice captures invalid JSON; WTTJ added inline scripts with brace characters; the env object itself now contains unquoted keys or trailing commas that strict JSON rejects; the response was truncated mid-object.
Common situations: WTTJ added inline JS with braces; a partial response truncating the object; a CDN injecting a script tag with braces.
Related errors
- wttj: /api/env payload has no JSON object
- wttj: unexpected Algolia app id "${appId}"
- wttj: unexpected Algolia api key shape
- wttj: unexpected Algolia response for query "${query}" — exp
- echojobs: unexpected API response on page ${page} — expected
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/83fcc3f1d7601e9d.
Report an issue: GitHub.