decolua/9router · error

Google Programmable Search requires both apiKey and cx

Error message

Google Programmable Search requires both apiKey and cx

What it means

buildGooglePseRequest requires two credentials before it can construct a Google Programmable Search (Custom Search JSON API) request: an API key (`params.token`) and the search-engine ID `cx` (read via getProviderSetting from providerOptions/providerSpecificData). If either is missing or empty, the builder throws instead of sending a doomed request — Google PSE cannot work without both.

Source

Thrown at open-sse/handlers/search/callers.js:199

  };
  if (includes.length) body.include_domains = includes;
  if (excludes.length) body.exclude_domains = excludes;
  if (params.country) body.country = params.country;
  return {
    url: resolveBaseUrl(config, params),
    init: {
      method: "POST",
      headers: { "Content-Type": "application/json", Authorization: `Bearer ${params.token}` },
      body: JSON.stringify(body),
    },
  };
}

function buildGooglePseRequest(config, params) {
  const apiKey = params.token;
  const cx = getProviderSetting(params, "cx");
  if (!apiKey || !cx) {
    throw new Error("Google Programmable Search requires both apiKey and cx");
  }
  const qp = new URLSearchParams({
    key: apiKey,
    cx,
    q: params.query,
    num: String(Math.min(params.maxResults, 10)),
  });
  if (params.country) qp.set("gl", params.country.toLowerCase());
  if (params.language) qp.set("hl", params.language);
  if (params.timeRange && params.timeRange !== "any") {
    const dateRestrictMap = { day: "d1", week: "w1", month: "m1", year: "y1" };
    const dateRestrict = dateRestrictMap[params.timeRange];
    if (dateRestrict) qp.set("dateRestrict", dateRestrict);
  }
  if (typeof params.offset === "number" && params.offset > 0) {
    qp.set("start", String(Math.min(params.offset + 1, 91)));
  }
  return {

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Pass the Google API key as the request token (params.token / apiKey field for the provider) and confirm it's not empty
  2. Set cx in providerOptions: { cx: "<search-engine-id-from-programmable-search>" } (or providerSpecificData.cx) — the 17+ char engine ID like "017576662512468239146:omuauf_lfve"
  3. Check the key name is exactly "cx" — other names like engineId are ignored by getProviderSetting
  4. Verify the API key has the Custom Search JSON API enabled in Google Cloud console and belongs to the same project as the search engine

Example fix

// before — missing cx
const params = { query: "rust async", token: process.env.GOOGLE_API_KEY };
// after
const params = {
  query: "rust async",
  token: process.env.GOOGLE_API_KEY,
  providerOptions: { cx: process.env.GOOGLE_PSE_CX },
};
Defensive patterns

Strategy: validation

Validate before calling

function validateGooglePseParams(params) {
  const apiKey = params.token;
  const cx = params.providerOptions?.cx ?? params.providerSpecificData?.cx;
  if (!apiKey || typeof apiKey !== "string") throw new Error("Google PSE: apiKey (token) is required");
  if (!cx || typeof cx !== "string" || !cx.trim()) throw new Error("Google PSE: cx (search engine ID) is required in providerOptions");
}
// call before issuing the search request

Type guard

function hasGooglePseCreds(params) {
  const cx = params?.providerOptions?.cx ?? params?.providerSpecificData?.cx;
  return typeof params?.token === "string" && params.token.length > 0 &&
         typeof cx === "string" && cx.trim().length > 0;
}

Try / catch

try {
  const result = await search({ provider: "google-pse", query, token: GOOGLE_API_KEY, providerOptions: { cx: GOOGLE_CX } });
} catch (err) {
  if (err.message.includes("requires both apiKey and cx")) {
    // configuration error, not transient — fail fast, surface which credential is missing
    // do not retry; check token and providerOptions.cx
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: A search request routed to the google-pse provider where params.token is absent (no apiKey passed) or getProviderSetting(params, "cx") returns undefined — i.e. neither providerOptions.cx nor providerSpecificData.cx contains a non-empty trimmed string.

Common situations: Developer created a Programmable Search Engine but forgot to enable the Custom Search JSON API key; cx configured under the wrong key name (e.g. "engineId" or "searchEngineId" instead of "cx"); token not forwarded by the calling handler for this provider; swapped credentials — putting the API key in cx or vice versa; whitespace-only cx value (trimmed to empty).

Understand the failure class

Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/b8708b3943389843. Report an issue: GitHub.