{"record":{"id":"9ca0e2bb2de0e776","repo":"decolua/9router","slug":"invalid-api-key-9ca0e2","errorCode":null,"errorMessage":"Invalid API key","messagePattern":"Invalid API key","errorType":"validation","errorClass":null,"httpStatus":401,"severity":"error","filePath":"src/sse/handlers/search.js","lineNumber":56,"sourceCode":"\n  // Log API key (masked)\n  const apiKey = extractApiKey(request);\n  if (apiKey) {\n    log.debug(\"AUTH\", `API Key: ${log.maskKey(apiKey)}`);\n  } else {\n    log.debug(\"AUTH\", \"No API key provided (local mode)\");\n  }\n\n  // Enforce API key if enabled in settings\n  const settings = await getSettings();\n  if (settings.requireApiKey) {\n    if (!apiKey) {\n      log.warn(\"AUTH\", \"Missing API key (requireApiKey=true)\");\n      return errorResponse(HTTP_STATUS.UNAUTHORIZED, \"Missing API key\");\n    }\n    const valid = await isValidApiKey(apiKey);\n    if (!valid) {\n      log.warn(\"AUTH\", \"Invalid API key (requireApiKey=true)\");\n      return errorResponse(HTTP_STATUS.UNAUTHORIZED, \"Invalid API key\");\n    }\n  }\n\n  if (!providerInput || typeof providerInput !== \"string\") {\n    log.warn(\"SEARCH\", \"Missing provider/model\");\n    return errorResponse(HTTP_STATUS.BAD_REQUEST, \"Missing required field: provider (or model)\");\n  }\n\n  if (!query || typeof query !== \"string\" || !query.trim()) {\n    log.warn(\"SEARCH\", \"Missing query\");\n    return errorResponse(HTTP_STATUS.BAD_REQUEST, \"Missing required field: query\");\n  }\n\n  // Combo expansion: providerInput may be a combo name → run fallback/round-robin across providers\n  const combos = await getCombos();\n  const comboModels = getComboModelsFromData(providerInput, combos);\n  if (comboModels) {","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/sse/handlers/search.js#L38-L74","documentation":"Second stage of the API-key guard in handleSearch (src/sse/handlers/search.js:56): a key was present on the request, but await isValidApiKey(apiKey) returned false, so the handler returns HTTP 401 'Invalid API key'. The gateway validates the supplied key against keys issued in its dashboard/settings store; an unrecognized or revoked key is rejected even though requireApiKey enforcement is satisfied in form.","triggerScenarios":"POST to the /v1 search endpoint with requireApiKey=true and an API key header present whose value fails isValidApiKey: key was regenerated/rotated in the dashboard, key belongs to a different 9Router instance, whitespace or 'Bearer ' prefix handling mismatch, or a placeholder env var value was sent.","commonSituations":"Keys rotated after a security incident without updating client env vars; copying a key from a staging instance to production; JWT_SECRET or API_KEY_SECRET changed so previously valid derived keys no longer validate; trailing newline/space in the .env value; using an upstream provider key instead of the 9Router-issued gateway key.","solutions":["Copy a currently valid key from the 9Router dashboard API-keys page and update the client config.","Trim whitespace and ensure the header format is exactly 'Authorization: Bearer <key>' with no duplicate 'Bearer Bearer'.","If the key store was invalidated by a secret change (JWT_SECRET/API_KEY_SECRET), issue new keys and redeploy clients.","Confirm you are pointing at the correct 9Router instance/environment that issued the key.","Check the server log line 'Invalid API key (requireApiKey=true)' timing against key rotations to confirm which value was rejected."],"exampleFix":"// before: stale/rotated key or wrong env\nheaders: { Authorization: `Bearer ${OLD_KEY}` }\n// after: reload a fresh key, trimmed\nconst key = (process.env.ROUTER_API_KEY || '').trim();\nif (!key) throw new Error('ROUTER_API_KEY is not set');\nheaders: { Authorization: `Bearer ${key}` }","handlingStrategy":"validation","validationCode":"const key = (process.env.ROUTER_API_KEY || '').trim();\nif (!key || key === 'changeme' || key.length < 16) throw new Error('ROUTER_API_KEY looks like a placeholder or rotated-out key');\nconst probe = await fetch(base + '/v1/models', { headers: { Authorization: `Bearer ${key}` } });\nif (probe.status === 401) throw new Error('Key rejected by gateway — re-issue from dashboard');","typeGuard":"function looksLikeRouterKey(k) {\n  return typeof k === 'string' && k.trim().length >= 16 && !/\\s/.test(k);\n}","tryCatchPattern":"const res = await doSearch();\nif (res.status === 401 && (await res.clone().text()).includes('Invalid API key')) {\n  throw new Error('API key rejected: fetch a fresh key from the 9Router dashboard and update env');\n}","preventionTips":["Rotate keys centrally: update the shared secret store, then all clients, in one deployment step.","Strip whitespace/newlines when loading keys from .env files.","Never reuse an upstream provider key as the gateway key — use keys issued by 9Router.","Add a startup health-check call (/v1/models) that fails fast on 401 so bad keys surface immediately."],"tags":["auth","api-key","http-401","invalid-credentials"],"backgroundTag":"invalid-api-key","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}