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 an object with keys: ${Object.keys(json).join(', ')} What it means
Gem's REST job-board API may return either a plain array of postings or an envelope object {job_posts: [...]}. extractRestRows accepts those shapes (plus null/undefined and empty objects, treated as zero postings) and throws for any other object shape, listing its keys to aid debugging.
Solutions
- Inspect the listed keys in the error message and check whether the real postings live under one of them.
- Update the provider to unwrap the new envelope shape (add a branch for the observed key) after confirming Gem's current API docs.
- Verify the resolved REST URL is Gem's documented job-board endpoint (resolveRestApiUrl), not a different resource.
- If the body is actually an error object, handle/log it as an API error instead of treating it as a postings envelope.
Example fix
// before if (Array.isArray(json.job_posts)) return json.job_posts; // after if (Array.isArray(json.job_posts)) return json.job_posts; if (Array.isArray(json.data)) return json.data; // new Gem envelope
Defensive patterns
Strategy: type-guard
Validate before calling
const looksLikeEnvelope = (j) => Array.isArray(j) || (j && typeof j === 'object' && (Array.isArray(j.job_posts) || Object.keys(j).length === 0));
Type guard
const isPostingsEnvelope = (j) => Array.isArray(j) || (j !== null && typeof j === 'object' && Array.isArray(j.job_posts));
Try / catch
try { rows = extractRestRows(json); } catch (e) { if (e.message.includes('unsupported REST response envelope')) console.error('Gem envelope changed; keys seen:', e.message.split('keys: ')[1]); throw e; } Prevention
- Pin/monitor Gem's REST API version and changelog
- Log unrecognized response bodies (sampled) to catch envelope drift early
- Check for embedded error objects in 200 responses before parsing as postings
- Add a contract test asserting the current envelope shape
When it happens
Trigger: The REST endpoint returned an unrecognized JSON object envelope — e.g. {data: [...]}, {results: [...]}, {jobs: [...]}, a paginated envelope {items, next_page}, or an error object {error: ...} returned with HTTP 200.
Common situations: Gem changed or versioned their REST response shape; hitting a different endpoint than the documented job-board API; a proxy or WAF returning its own JSON error body; a board whose API path resolves to a non-postings 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/34f09f4ce6d893b4.
Report an issue: GitHub.
Appendix: source
Thrown at providers/gem.mjs:285
};
/**
* Extract the row array from Gem's documented GET response. The endpoint has
* 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);View on GitHub (pinned to aac998c7ed)