santifer/career-ops · error · Error
justjoin: unexpected API response — expected { data: [...] }
Error message
justjoin: unexpected API response — expected { data: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}] What it means
Thrown by parseJustJoinResponse() when the parsed JSON from the justjoin.it API is null/falsy or lacks a top-level data array. The provider expects the response shape { data: [...offers], meta: {...} }; a different shape (e.g. a bare array, an error envelope, a { results: [...] } wrapper) means the API contract changed or a non-JSON-success body was parsed, and the provider fails rather than silently emitting zero jobs. The error includes the actual top-level keys to aid diagnosis.
Source
Thrown at providers/justjoin.mjs:82
const parsed = assertJustJoinUrl(apiUrl);
if (parsed.pathname !== '/api/candidate-api/offers') {
parsed.pathname = '/api/candidate-api/offers';
parsed.search = '';
}
parsed.searchParams.set('from', String(from));
parsed.searchParams.set('itemsCount', String(Number(entry.page_size || PAGE_SIZE)));
parsed.searchParams.set('cityRadius', String(Number(entry.city_radius || 30)));
parsed.searchParams.set('currency', String(entry.currency || 'pln').toLowerCase());
parsed.searchParams.set('orderBy', 'descending');
parsed.searchParams.set('sortBy', 'publishedAt');
parsed.searchParams.set('keywordType', 'any');
parsed.searchParams.set('isPromoted', 'true');
return parsed.href;
}
export function parseJustJoinResponse(json) {
if (!json || !Array.isArray(json.data)) {
throw new Error(`justjoin: unexpected API response — expected { data: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`);
}
return json.data
.filter(offer => offer && typeof offer === 'object')
.map(offer => {
const slug = String(offer.slug || '').trim();
const title = String(offer.title || '').trim();
if (!slug || !title) return null;
return {
title,
url: `${JOB_BASE}${slug}`,
company: String(offer.companyName || '').trim(),
location: normalizeLocation(offer),
postedAt: postedAtMillis(offer.publishedAt),
};
})
.filter(Boolean);
}
View on GitHub (pinned to 9b17a8ac97)
Solutions
- Inspect the actual response body (the error logs the top-level keys) and compare against the expected { data: [...] } shape.
- If justjoin.it renamed the envelope, update parseJustJoinResponse to read the new key.
- If the body is an error envelope, address the upstream cause (rate limit, auth, region) before retrying.
Example fix
// before — expects data, API now returns offers
export function parseJustJoinResponse(json) {
if (!json || !Array.isArray(json.data)) throw new Error(...);
return json.data.map(...);
}
// after — accept renamed envelope
export function parseJustJoinResponse(json) {
const list = json?.data ?? json?.offers ?? json?.results;
if (!Array.isArray(list)) throw new Error(`justjoin: unexpected API response — got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`);
return list.map(...);
} Defensive patterns
Strategy: type-guard
Validate before calling
function justJoinResponseOk(json) {
return Boolean(json && Array.isArray(json.data));
} Type guard
/** @param {any} json @returns {boolean} */
function isJustJoinOffersEnvelope(json) {
return Boolean(json && Array.isArray(json.data));
} Try / catch
try {
parsed = parseJustJoinResponse(json);
} catch (err) {
if (/unexpected API response/.test(err.message)) {
console.error(`justjoin API contract changed — keys: ${Object.keys(json || {}).join(', ')}`);
}
throw err;
} Prevention
- Pin a fixture of the expected { data: [...] } response in tests so an envelope change breaks CI.
- Log the top-level keys whenever the guard trips to accelerate diagnosis.
- Distinguish a renamed envelope (structure change) from an error body (upstream fault) before retrying.
When it happens
Trigger: justjoin.it changed its API response envelope (renamed data to results, wrapped it, or versioned the shape); the API returned an error object (e.g. { error: '...' }) with a 200 status; a gateway/proxy rewrote the response; an HTML error page was JSON-parsed into an unexpected object.
Common situations: An API version bump on justjoin.it; rate-limiting that returns a JSON error body instead of the offers list; a transient gateway response with a different envelope.
Related errors
- arbeitnow: unexpected API response on page ${page} — expecte
- payload must include at least one of: cv, articleDigest
- echojobs: unexpected API response on page ${page} — expected
- join: __NEXT_DATA__ not found or unexpected structure
- joinup: ${entry.name} page is missing __NEXT_DATA__ (structu
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/98c70ef33c6b4bc2.
Report an issue: GitHub.