{"record":{"id":"fd09629e69607f5b","repo":"santifer/career-ops","slug":"glints-unexpected-api-response-json-stringify","errorCode":null,"errorMessage":"glints: unexpected API response — ${JSON.stringify(json).slice(0, 200)}","messagePattern":"glints: unexpected API response — (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/glints.mjs","lineNumber":229,"sourceCode":"          CountryCode: country,\n          includeExternalJobs: true,\n          pageSize: pageSize,\n          page: page,\n        },\n      };\n\n      let json;\n      try {\n        json = /** @type {any} */ (await graphqlPage(apiUrl, query, variables, ctx));\n      } catch (err) {\n        if (page === 1) throw err;\n        console.error(`glints: page ${page} fetch failed — ${err.message}`);\n        break;\n      }\n\n      const jobsInPage = json?.data?.searchJobsV3?.jobsInPage;\n      if (!Array.isArray(jobsInPage)) {\n        if (page === 1) throw new Error(`glints: unexpected API response — ${JSON.stringify(json).slice(0, 200)}`);\n        break;\n      }\n\n      if (jobsInPage.length === 0) break;\n\n      for (const item of jobsInPage) {\n        const job = parseGlintsItem(item, baseUrl, fallbackCompany);\n        if (job) allJobs.push(job);\n      }\n\n      // Stop if no more pages\n      if (json?.data?.searchJobsV3?.hasMore === false) break;\n      if (jobsInPage.length < pageSize) break;\n\n      // Rate-limit courtesy delay\n      await new Promise(resolve => setTimeout(resolve, 300));\n    }\n","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/glints.mjs#L211-L247","documentation":"glints.mjs throws this on page 1 of fetch() when json.data.searchJobsV3.jobsInPage is not an Array. The Glints GraphQL contract is { data: { searchJobsV3: { jobsInPage: [...], hasMore, expInfo } } }, so a missing/reshaped searchJobsV3 means the schema moved or the query failed to select the right fields. (On later pages this condition breaks the loop instead of throwing, so this specific throw is page-1 only.) The message embeds the first 200 chars of the response for diagnosis.","triggerScenarios":"Glints renamed searchJobsV3 or jobsInPage in a schema bump; the query was overridden via entry.graphqlQuery with an operation that returns a different shape; the server returned a top-level errors[] (no data) for an invalid query; a partial/empty 200 with data:null.","commonSituations":"Glints ships the reverse-engineered schema change the file header warns about; operator sets graphqlQuery to a custom string that does not select jobsInPage; a variables mismatch (bad CountryCode/SearchTerm) yields a degenerate response.","solutions":["Inspect the 200-char JSON snippet in the message — if it shows a top-level errors[] or a renamed field, that is the cause.","If entry.graphqlQuery is set, ensure it selects data.searchJobsV3.jobsInPage; otherwise remove graphqlQuery to use the maintained DEFAULT_GRAPHQL_QUERY.","Re-capture the live searchJobsV3 query from glints.com and update DEFAULT_GRAPHQL_QUERY (and the jobsInPage/hasMore extraction) to match.","Verify countryCode/searchKeywords are valid; an invalid CountryCode can return an unexpected envelope."],"exampleFix":"# before (custom query selects wrong field)\n- name: Glints (ID)\n  provider: glints\n  graphqlQuery: \"query { opportunities { jobs { id title } } }\"\n\n# after (omit to use maintained default, or select jobsInPage)\n- name: Glints (ID)\n  provider: glints\n  # graphqlQuery removed -> uses DEFAULT_GRAPHQL_QUERY","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// Narrow a Glints GraphQL response to the expected searchJobsV3.jobsInPage shape.\nfunction isGlintsJobsPage(json) {\n  return !!json && typeof json === 'object'\n    && json.data && typeof json.data === 'object'\n    && json.data.searchJobsV3 && typeof json.data.searchJobsV3 === 'object'\n    && Array.isArray(json.data.searchJobsV3.jobsInPage);\n}","tryCatchPattern":"// On page 1 throw (schema regression); on later pages, stop paginating.\ntry {\n  json = await graphqlPage(apiUrl, query, variables, ctx);\n} catch (err) {\n  if (page === 1) throw err;\n  console.error(`glints: page ${page} failed — ${err.message}`);\n  break;\n}\nif (!isGlintsJobsPage(json)) {\n  if (page === 1) throw new Error(`glints: unexpected API response — ${JSON.stringify(json).slice(0, 200)}`);\n  break;\n}","preventionTips":["Avoid overriding graphqlQuery unless you also select data.searchJobsV3.jobsInPage.","Re-capture the live searchJobsV3 query when Glints ships a schema change and update DEFAULT_GRAPHQL_QUERY promptly.","Log the 200-char response snippet on shape failures so regressions are diagnosed from one run."],"tags":["graphql","api-response-shape","glints","runtime","third-party-api"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}