{"record":{"id":"93770f2a6121d40b","repo":"decolua/9router","slug":"missing-api-key-93770f","errorCode":null,"errorMessage":"Missing API key","messagePattern":"Missing API key","errorType":"validation","errorClass":null,"httpStatus":401,"severity":"error","filePath":"src/sse/handlers/search.js","lineNumber":51,"sourceCode":"  // Accept either `provider` or `model` (UI sends `model` since provider IS the model for webSearch)\n  const providerInput = body.provider || body.model;\n  const query = body.query;\n\n  log.request(\"POST\", `${url.pathname} | ${providerInput}`);\n\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  }","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/sse/handlers/search.js#L33-L69","documentation":"Auth guard in handleSearch (src/sse/handlers/search.js:51): when the server setting requireApiKey is enabled and extractApiKey(request) finds no API key on the request, the handler returns HTTP 401 'Missing API key'. 9Router exposes an OpenAI-compatible gateway; once API-key enforcement is switched on in dashboard settings, every programmatic call must carry the key even from localhost.","triggerScenarios":"POST to the /v1 search endpoint while settings.requireApiKey === true and the request carries no API key: no Authorization: Bearer header, no x-api-key header, and no other header extractApiKey recognizes.","commonSituations":"Local dev scripts that worked before API-key enforcement was enabled in the dashboard; a team member toggled requireApiKey on; a reverse proxy strips the Authorization header; the client sends the key under a custom header name the gateway's extractApiKey does not read; environment changed from no-auth local mode to a shared deployment.","solutions":["Attach the key as Authorization: Bearer <your-9router-api-key> on the request.","Alternatively send it in the x-api-key header (whichever extractApiKey supports).","Verify the key matches one generated in the dashboard (API keys section) and that requireApiKey is intentionally on.","If this is a trusted local-only setup, disable requireApiKey in dashboard settings.","Check the reverse proxy/middleware config isn't stripping Authorization before it reaches 9Router."],"exampleFix":"// before\nawait fetch(base + '/v1/search', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });\n// after\nawait fetch(base + '/v1/search', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.ROUTER_API_KEY}` },\n  body: JSON.stringify(payload)\n});","handlingStrategy":"validation","validationCode":"const key = process.env.ROUTER_API_KEY;\nif (!key) throw new Error('ROUTER_API_KEY is required: the gateway has requireApiKey enabled');\n// optional pre-check\nconst probe = await fetch(base + '/v1/models', { headers: { Authorization: `Bearer ${key}` } });\nif (probe.status === 401) throw new Error('Gateway rejects the configured API key');","typeGuard":null,"tryCatchPattern":"const res = await doSearch();\nif (res.status === 401) {\n  const msg = await res.text();\n  if (msg.includes('Missing API key')) throw new Error('Attach Authorization: Bearer <key> — requireApiKey is enabled');\n  throw new Error('Auth failed: ' + msg);\n}","preventionTips":["Load the key from env/config in every client that talks to the gateway; never hardcode or omit it.","Keep requireApiKey state documented for your deployment so local scripts are updated when it flips on.","Verify reverse proxies preserve the Authorization header.","Send the key in the header format extractApiKey recognizes (Authorization: Bearer or x-api-key)."],"tags":["auth","api-key","http-401","unauthorized"],"backgroundTag":"missing-api-key","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}