santifer/career-ops · error

glints: unexpected API response

Error message

glints: unexpected API response — ${JSON.stringify(json).slice(0, 200)}

What it means

The Glints provider pages through a GraphQL searchJobsV3 endpoint and expects each response to contain data.searchJobsV3.jobsInPage as an array. When page 1 already fails to have that shape, the provider throws this error with a 200-char dump of the JSON to aid diagnosis. It guards against silently scanning zero jobs when the API contract changes or a proxy/CDN returns non-GraphQL payloads.

Solutions

  1. Read the 200-char JSON snippet in the message to identify what the API actually returned (errors array? challenge page? empty object?).
  2. Re-run the scan later — transient upstream errors often clear; the fetch loop itself breaks (not throws) for page > 1.
  3. Update the extraction path in providers/glints.mjs if Glints changed the GraphQL schema (e.g. searchJobsV3 renamed).
  4. Check for network middleboxes/proxies that intercept the HTTPS request and return their own JSON (captive portal, corporate proxy).

Example fix

// before
const jobsInPage = json?.data?.searchJobsV3?.jobsInPage;
// after (tolerate a renamed/shifted payload)
const jobsInPage = json?.data?.searchJobsV3?.jobsInPage ?? json?.data?.searchJobs?.jobsInPage;
Defensive patterns

Strategy: type-guard

Validate before calling

function looksLikeGlintsPayload(json) {
  return Array.isArray(json?.data?.searchJobsV3?.jobsInPage);
}
if (!looksLikeGlintsPayload(await res.json())) throw new Error('glints: payload shape changed');

Type guard

const isGlintsPage = (v) => Array.isArray(v?.data?.searchJobsV3?.jobsInPage);

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).startsWith('glints: unexpected API response')) {
    console.error(`glints schema drift for ${entry.name}:`, err.message);
    return; // skip entry, keep scanning other boards
  }
  throw err;
}

Prevention

When it happens

Trigger: First page of a fetch() call where json.data.searchJobsV3.jobsInPage is not an array — e.g. the API returns an errors array, an HTML error page parsed as JSON, a rate-limit JSON body, or Glints renames/versions the field (searchJobsV3 -> v4).

Common situations: Glints schema/GraphQL version change, blocked or rate-limited requests returning a challenge JSON, misconfigured query variables producing an errors payload, or an intermittent upstream outage on the very first request.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/fd09629e69607f5b. Report an issue: GitHub.

Appendix: source

Thrown at providers/glints.mjs:229

          CountryCode: country,
          includeExternalJobs: true,
          pageSize: pageSize,
          page: page,
        },
      };

      let json;
      try {
        json = /** @type {any} */ (await graphqlPage(apiUrl, query, variables, ctx));
      } catch (err) {
        if (page === 1) throw err;
        console.error(`glints: page ${page} fetch failed — ${err.message}`);
        break;
      }

      const jobsInPage = json?.data?.searchJobsV3?.jobsInPage;
      if (!Array.isArray(jobsInPage)) {
        if (page === 1) throw new Error(`glints: unexpected API response — ${JSON.stringify(json).slice(0, 200)}`);
        break;
      }

      if (jobsInPage.length === 0) break;

      for (const item of jobsInPage) {
        const job = parseGlintsItem(item, baseUrl, fallbackCompany);
        if (job) allJobs.push(job);
      }

      // Stop if no more pages
      if (json?.data?.searchJobsV3?.hasMore === false) break;
      if (jobsInPage.length < pageSize) break;

      // Rate-limit courtesy delay
      await new Promise(resolve => setTimeout(resolve, 300));
    }

View on GitHub (pinned to aac998c7ed)