santifer/career-ops · error
gem: unsupported REST response envelope — expected an array…
Error message
gem: unsupported REST response envelope — expected an array or {job_posts: [...]}, got ${typeof json} What it means
The non-object branch of extractRestRows: after ruling out arrays, null/undefined, and objects, the parsed JSON body was some other JSON type (string, number, boolean). The provider cannot treat that as a postings collection and throws the same envelope message with the actual typeof value.
Solutions
- Check typeof in the error message: a string usually means the wrong URL was hit — verify the REST endpoint path.
- Ensure fetchJson is pointed at the documented job-board postings endpoint.
- If Gem now wraps postings as a JSON-encoded string, JSON.parse the inner string before extractRestRows.
- Log the raw response body once to confirm what the server actually returns.
Example fix
// before const rows = extractRestRows(await ctx.fetchJson(url)); // after let body = await ctx.fetchJson(url); if (typeof body === 'string') body = JSON.parse(body); const rows = extractRestRows(body);
Defensive patterns
Strategy: type-guard
Validate before calling
if (typeof body !== 'object' && typeof body !== 'undefined') throw new Error(`Gem REST returned ${typeof body}, expected object/array`); Type guard
const isJsonContainer = (v) => v === null || v === undefined || typeof v === 'object';
Try / catch
try { rows = extractRestRows(json); } catch (e) { if (/got (string|number|boolean)/.test(e.message)) console.error('Non-object JSON body — likely wrong endpoint URL'); throw e; } Prevention
- Verify the api URL points at the postings endpoint, not an index/health route
- Log the raw body once per board when the shape is unexpected
- Ensure fetchJson (not a plain text fetch) decodes the response
- Add an integration test per board asserting a container-shaped response
When it happens
Trigger: The endpoint returned a JSON scalar — e.g. a bare string body ("OK"), a quoted error message, or an API version returning a count/number instead of a list; or ctx.fetchJson returning a pre-decoded primitive.
Common situations: Hitting a health-check or index endpoint instead of the postings endpoint; Gem returning a JSON-encoded string wrapper; a misconfigured api URL pointing at a non-API resource.
Related errors
- gem: unsupported REST response envelope — expected an array…
- 4dayweek: unexpected API response on page
- a16z-speedrun-talent: unexpected API response on page
- agentic-jobs: unexpected API response shape on page
- arbeitnow: unexpected API response on page
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/a5fda50d0e9ea42e.
Report an issue: GitHub.
Appendix: source
Thrown at providers/gem.mjs:289
* appeared both as a bare array and wrapped in `job_posts`; accepting both
* keeps the provider tolerant. `[]`/`{}`/`null` are legitimately contentless
* (an empty board), so they resolve to no rows — but any OTHER nonempty
* object shape is undocumented and gets rejected loudly rather than silently
* read as "zero jobs," which would make a changed Gem response look like an
* empty board and drop every posting without a trace.
* @param {any} json
*/
function extractRestRows(json) {
if (Array.isArray(json)) return json;
if (json === null || json === undefined) return [];
if (typeof json === 'object') {
if (Array.isArray(json.job_posts)) return json.job_posts;
if (Object.keys(json).length === 0) return [];
throw new Error(
`gem: unsupported REST response envelope — expected an array or {job_posts: [...]}, got an object with keys: ${Object.keys(json).join(', ')}`
);
}
throw new Error(`gem: unsupported REST response envelope — expected an array or {job_posts: [...]}, got ${typeof json}`);
}
/**
* Parse Gem's documented GET response.
* @param {any} json
* @param {string} companyName
*/
export function parseRestResponse(json, companyName) {
const rows = extractRestRows(json);
return rows.filter(j => j && typeof j.title === 'string' && j.title.trim() && typeof j.absolute_url === 'string')
.map(j => {
let url;
try {
const parsed = new URL(j.absolute_url);
if (parsed.protocol !== 'https:' || parsed.hostname !== 'jobs.gem.com' || !GEM_POSTING_PATH_RE.test(parsed.pathname)) return null;
url = parsed.href;
} catch {
return null;View on GitHub (pinned to aac998c7ed)