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
- Pass the Google API key as the request token (params.token / apiKey field for the provider) and confirm it's not empty
- Set cx in providerOptions: { cx: "<search-engine-id-from-programmable-search>" } (or providerSpecificData.cx) — the 17+ char engine ID like "017576662512468239146:omuauf_lfve"
- Check the key name is exactly "cx" — other names like engineId are ignored by getProviderSetting
- 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
- Store both GOOGLE_API_KEY and GOOGLE_PSE_CX together; validate the pair at startup
- Use the exact key name "cx" in providerOptions — other names are silently ignored
- Verify the API key has Custom Search JSON API enabled in Google Cloud before deploying
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
- Invalid baseUrl: ${override}
- Linkup Search requires an API key
- SearchAPI requires an API key
- You.com Search requires an API key
- Xquik requires an API key
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/b8708b3943389843.
Report an issue: GitHub.