santifer/career-ops · error · Error
nofluffjobs: unexpected API response — expected { postings:
Error message
nofluffjobs: unexpected API response — expected { postings: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}] What it means
Thrown by parseNoFluffJobsResponse() when the JSON response is falsy or lacks a 'postings' property that is an array. The NoFluffJobs search API (POST /api/search/posting) is contractually expected to return {postings: [...], totalCount: N}. Any deviation — null body, a different wrapper key, or postings as a non-array — is treated as an API contract break.
Source
Thrown at providers/nofluffjobs.mjs:109
applicationStatus: [],
province: [],
company: [],
id: [],
category: [],
keyword: [],
jobLanguage: [],
seniority: [],
},
pageSize: Number(entry.page_size || PAGE_SIZE),
withSalaryMatch: true,
};
return { url: apiUrl.href, body };
}
export function parseNoFluffJobsResponse(json) {
if (!json || !Array.isArray(json.postings)) {
throw new Error(`nofluffjobs: unexpected API response — expected { postings: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`);
}
return json.postings
.filter(posting => posting && typeof posting === 'object')
.map(posting => {
const title = String(posting.title || '').trim();
const company = String(posting.name || '').trim();
const slug = String(posting.url || posting.id || '').trim();
if (!title || !slug) return null;
return {
title,
url: `${JOB_BASE}${slug}`,
company,
location: normalizeLocation(posting),
postedAt: postedAtMillis(posting.posted),
};
})
.filter(Boolean);
}View on GitHub (pinned to 9b17a8ac97)
Solutions
- Inspect the raw API response: curl -X POST https://nofluffjobs.com/api/search/posting -H 'content-type: application/json' -d '{...}' to see the actual shape.
- If the key was renamed, update parseNoFluffJobsResponse to read the new key (e.g. json.jobs instead of json.postings).
- If the response is an error envelope with HTTP 200, add status-code checking in the fetch loop or detect known error keys before shape validation.
- If buildRequest() sends an outdated body schema, update the criteria/keyword filter structure to match the current API.
Example fix
// before
export function parseNoFluffJobsResponse(json) {
if (!json || !Array.isArray(json.postings)) {
throw new Error(`nofluffjobs: unexpected API response ...`);
}
return json.postings.filter(...);
}
// after — tolerate a renamed key and give a clearer error
export function parseNoFluffJobsResponse(json) {
const list = Array.isArray(json?.postings) ? json.postings
: Array.isArray(json?.jobs) ? json.jobs
: null;
if (!list) {
throw new Error(`nofluffjobs: unexpected API response — expected {postings:[]}, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`);
}
return list.filter(Boolean);
} Defensive patterns
Strategy: type-guard
Validate before calling
// Pre-flight: probe the API to verify it still returns {postings:[]}
const probe = await fetch('https://nofluffjobs.com/api/search/posting', {
method: 'POST', body: JSON.stringify(criteria), headers: { 'content-type': 'application/json' }
}).then(r => r.json());
if (!probe || !Array.isArray(probe.postings)) {
console.warn('nofluffjobs API shape changed — keys:', probe ? Object.keys(probe) : 'null');
} Type guard
/** @param {unknown} json @returns {json is {postings: any[]}} */
function isNoFluffResponse(json) {
return json != null
&& typeof json === 'object'
&& Array.isArray(json.postings);
}
// usage:
const json = await ctx.fetchJson(url, opts);
if (!isNoFluffResponse(json)) {
return []; // or unwrap alternate key
} Try / catch
try {
await nofluffProvider.fetch(entry, ctx);
} catch (err) {
if (String(err.message).startsWith('nofluffjobs: unexpected API response')) {
console.error(`nofluffjobs API drift for ${entry.name}:`, err.message);
continue; // skip, keep scanning other providers
}
throw err;
} Prevention
- Wrap provider calls in try-catch to isolate API-drift failures from batch scans.
- Log raw response keys when shape validation fails for diagnosis.
- Monitor NoFluffJobs API changelog for breaking changes.
- Update buildRequest body schema if filter parameters change.
When it happens
Trigger: The POST to /api/search/posting returned HTTP 200 but the body is: (1) null or undefined; (2) an object without a 'postings' key (e.g. {jobs:[...]} after an API rename); (3) an error envelope like {errors:[...]} returned with status 200; (4) {postings: null} or {postings: {}} where postings isn't an array.
Common situations: NoFluffJobs ships an unannounced API change renaming 'postings' to 'jobs' or wrapping in a 'data' envelope. The API silently returns an error object with HTTP 200 (common in some API gateways). A malformed request body (wrong filter shape) causes the API to return an empty/error response object rather than the expected feed.
Related errors
- manfred: unexpected API response — expected a JSON array of
- landingjobs: unexpected API response — expected a JSON array
- local parser JSON must be an array or contain jobs[]/results
- wttj: /api/env payload is not valid JSON
- payload must include at least one of: cv, articleDigest
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/3df3f9dcb12d8a07.
Report an issue: GitHub.