santifer/career-ops · error · Error

wttj: unexpected Algolia api key shape

Error message

wttj: unexpected Algolia api key shape

What it means

The parsed PUBLIC_ALGOLIA_API_KEY_CLIENT must be non-empty and 16–500 chars. The bounds are deliberately wide (the comment notes WTTJ may rotate to longer/base64/secured keys) since the key is only ever sent as a request header, never used to build a host. Throws when the key is missing/renamed or outside the length bounds.

Source

Thrown at providers/wttj.mjs:72

  const start = text.indexOf('{');
  const end = text.lastIndexOf('}');
  if (start === -1 || end <= start) throw new Error('wttj: /api/env payload has no JSON object');
  let env;
  try {
    env = JSON.parse(text.slice(start, end + 1));
  } catch {
    throw new Error('wttj: /api/env payload is not valid JSON');
  }
  const appId = typeof env.PUBLIC_ALGOLIA_APPLICATION_ID === 'string' ? env.PUBLIC_ALGOLIA_APPLICATION_ID.trim() : '';
  const apiKey = typeof env.PUBLIC_ALGOLIA_API_KEY_CLIENT === 'string' ? env.PUBLIC_ALGOLIA_API_KEY_CLIENT.trim() : '';
  // App ids are short alphanumerics; validating keeps the derived Algolia
  // hostname from being attacker-shaped if the env payload ever changes.
  if (!/^[A-Z0-9]{6,16}$/i.test(appId)) throw new Error(`wttj: unexpected Algolia app id "${appId}"`);
  // The key is only ever sent as a request header (never used to build a
  // host), so don't over-constrain its format — WTTJ may rotate to a longer
  // or non-hex (e.g. secured/base64) client key. Length bounds only.
  if (!apiKey || apiKey.length < 16 || apiKey.length > 500) {
    throw new Error('wttj: unexpected Algolia api key shape');
  }
  return { appId, apiKey };
}

/**
 * Normalize a single Algolia hit. Exported for tests.
 *
 * Field mapping → normalized Job shape:
 *   - title:    `name`
 *   - url:      /en/companies/{organization.slug}/jobs/{slug} on the WTTJ site
 *   - company:  `organization.name`
 *   - location: offices[0] city+country, with ", Remote" appended when the
 *               posting allows fulltime remote
 *   - postedAt: `published_at_timestamp` (epoch seconds → ms)
 *   - salary:   {min, max, currency} from salary_yearly_minimum/salary_maximum
 *
 * @param {any} h
 * @returns {{ title: string, url: string, company: string, location: string, postedAt?: number, salary?: {min: number, max: number, currency: string} } | null}

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Fetch /api/env and verify PUBLIC_ALGOLIA_API_KEY_CLIENT is present and non-empty.
  2. If the key was renamed, update the field read in parseEnvPayload.
  3. If the key genuinely grew past 500 chars, raise the upper bound after confirming.
  4. If /api/env was truncated, retry the fetch.

Example fix

// if the env key is renamed:
// before
const apiKey = typeof env.PUBLIC_ALGOLIA_API_KEY_CLIENT === "string" ? env.PUBLIC_ALGOLIA_API_KEY_CLIENT.trim() : "";
// after
const apiKey = typeof env.PUBLIC_ALGOLIA_SEARCH_KEY === "string" ? env.PUBLIC_ALGOLIA_SEARCH_KEY.trim() : "";
Defensive patterns

Strategy: validation

Validate before calling

if (typeof parsedApiKey !== "string" || parsedApiKey.length < 16 || parsedApiKey.length > 500)
  console.warn("wttj api key shape changed — length:", parsedApiKey?.length);

Type guard

const isValidAlgoliaApiKey = (s) => typeof s === "string" && s.length >= 16 && s.length <= 500;

Try / catch

try { parseEnvPayload(envText); }
catch (err) {
  if (/Algolia api key shape/.test(err.message)) { logUpstreamChange("wttj", err.message); }
  throw err;
}

Prevention

When it happens

Trigger: /api/env no longer contains PUBLIC_ALGOLIA_API_KEY_CLIENT; the field was renamed; WTTJ rotated to a key shorter than 16 or longer than 500 chars (extremely unlikely); a truncated /api/env response cut off the key.

Common situations: WTTJ renamed the env key; the env object parsed but the key field is absent; a truncated response.

Related errors


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