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

  1. Inspect the actual response body (the error logs the top-level keys) and compare against the expected { data: [...] } shape.
  2. If justjoin.it renamed the envelope, update parseJustJoinResponse to read the new key.
  3. 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

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


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/98c70ef33c6b4bc2. Report an issue: GitHub.