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

  1. Inspect the listed keys in the error message and check whether the real postings live under one of them.
  2. Update the provider to unwrap the new envelope shape (add a branch for the observed key) after confirming Gem's current API docs.
  3. Verify the resolved REST URL is Gem's documented job-board endpoint (resolveRestApiUrl), not a different resource.
  4. 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

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


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)