santifer/career-ops · error · Error
manfred: unexpected API response — expected a JSON array of
Error message
manfred: unexpected API response — expected a JSON array of offers, got ${json === null ? 'null' : typeof json} What it means
Thrown by the manfred provider's fetch() after ctx.fetchJson() returns successfully but the parsed body is not a JSON array. The Manfred job feed API (getmanfred.com) is contractually expected to return a top-level array of offer objects; any other shape (object, string, number, null) is treated as a contract break or upstream API change. This is a response-shape validator, not a network error — the HTTP request succeeded.
Source
Thrown at providers/manfred.mjs:196
return job;
}
/** @type {Provider} */
export default {
id: 'manfred',
detect(entry) {
return entry?.provider === 'manfred' ? { url: buildFeedUrl(entry) } : null;
},
async fetch(entry, ctx) {
// Validate the URL actually fetched (not just a constant) so the host pin
// is meaningful, then redirect:'error' blocks SSRF via server-side
// redirects — together they keep the request on getmanfred.com.
const url = assertManfredUrl(buildFeedUrl(entry));
const json = /** @type {any} */ (await ctx.fetchJson(url, { redirect: 'error' }));
if (!Array.isArray(json)) {
throw new Error(
`manfred: unexpected API response — expected a JSON array of offers, got ${json === null ? 'null' : typeof json}`,
);
}
const fallbackCompany = entry?.name;
const out = [];
for (const offer of json) {
const normalized = normalizeManfredOffer(offer, fallbackCompany);
if (normalized) out.push(normalized);
}
return out;
},
};
View on GitHub (pinned to 9b17a8ac97)
Solutions
- Check what the endpoint actually returns now: curl -sL <feed-url> | head -c 500 — if it's an envelope like {data:[...]}, the provider needs updating to unwrap it.
- Verify the entry in portals.yml points to the correct Manfred feed URL (buildFeedUrl output) and not a stale or wrong endpoint.
- If Manfred changed their API shape, update normalizeManfordOffer and the Array.isArray check in providers/manfred.mjs:196 to unwrap the new envelope.
- If the response is a JSON error object with HTTP 200, add status-code awareness to ctx.fetchJson or check for known error keys before the array assertion.
Example fix
// before
const json = await ctx.fetchJson(url, { redirect: 'error' });
if (!Array.isArray(json)) {
throw new Error(`manfred: unexpected API response ...`);
}
// after — tolerate a known envelope shape
const json = await ctx.fetchJson(url, { redirect: 'error' });
const offers = Array.isArray(json) ? json : (Array.isArray(json?.offers) ? json.offers : null);
if (!offers) {
throw new Error(`manfred: unexpected API response — expected array or {offers:[]}, got ${json === null ? 'null' : typeof json}`);
} Defensive patterns
Strategy: type-guard
Validate before calling
// Before calling the provider, verify the Manfred feed returns an array
// by probing the endpoint (or trust the provider's own guard and catch).
// Pre-flight check is impractical for a remote API — the type guard is
// the runtime check itself, best handled in a catch boundary.
if (process.env.MANFRED_STRICT === '1') {
const probe = await fetch(feedUrl).then(r => r.json());
if (!Array.isArray(probe)) {
console.warn('manfred feed shape changed — expected array, got', typeof probe);
}
} Type guard
/** @param {unknown} json @returns {json is any[]} */
function isOfferArray(json) {
return Array.isArray(json);
}
// usage in caller:
const json = await ctx.fetchJson(url, { redirect: 'error' });
if (!isOfferArray(json)) {
// log and skip, or unwrap a known envelope
return [];
} Try / catch
try {
const jobs = await manfredProvider.fetch(entry, ctx);
} catch (err) {
if (String(err.message).startsWith('manfred: unexpected API response')) {
// API contract drift — log for investigation, don't crash the batch
console.error(`manfred API shape changed for ${entry.name}:`, err.message);
continue; // skip this provider, keep scanning others
}
throw err; // re-throw unrelated errors
} Prevention
- Wrap each provider call in a try-catch that skips the failed provider rather than aborting the entire scan batch.
- Log the raw API response body when shape validation fails so contract drift can be diagnosed without reproducing.
- Pin the Manfred API version in the feed URL if versioned endpoints are available.
- Monitor for Manfred API changelog announcements when using this provider in production.
When it happens
Trigger: The URL passed assertManfredUrl() and the fetch completed with redirect:'error', but the JSON body is not an array. Specific triggers: (1) Manfred changes their API to return a wrapper object like {offers:[...]} or {data:[...]}; (2) the endpoint returns an error object like {error:'rate limited', message:'...'} with HTTP 200; (3) an HTML error page is served with Content-Type: application/json and parsed as a string; (4) the feed URL points to a page that returns a single JSON object (e.g. a 301-moved JSON envelope if redirect handling changed).
Common situations: The most common real-world hit is an unannounced Manfred API version bump that wraps results in an envelope. Other situations: rate-limiting responses returned as JSON objects with status 200, or a misconfigured entry.api pointing to a non-feed Manfred endpoint (e.g. a single-offer detail endpoint). Transient upstream issues where a CDN returns a JSON error object instead of the feed also trigger this.
Related errors
- nofluffjobs: unexpected API response — expected { postings:
- 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/0687c18bf9826a18.
Report an issue: GitHub.