{"record":{"id":"b023db9bbaa27b6f","repo":"decolua/9router","slug":"missing-api-key-b023db","errorCode":null,"errorMessage":"Missing API key","messagePattern":"Missing API key","errorType":"validation","errorClass":null,"httpStatus":401,"severity":"error","filePath":"src/sse/handlers/embeddings.js","lineNumber":58,"sourceCode":"\n  const url = new URL(request.url);\n  const modelStr = body.model;\n\n  log.request(\"POST\", `${url.pathname} | ${modelStr}`);\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 (!modelStr) {\n    log.warn(\"EMBEDDINGS\", \"Missing model\");\n    return errorResponse(HTTP_STATUS.BAD_REQUEST, \"Missing model\");\n  }\n\n  if (!body.input) {\n    log.warn(\"EMBEDDINGS\", \"Missing input\");\n    return errorResponse(HTTP_STATUS.BAD_REQUEST, \"Missing required field: input\");\n  }","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/sse/handlers/embeddings.js#L40-L76","documentation":"requireApiKey is enabled in gateway settings but the embeddings request carried no API key. extractApiKey(request) returned nothing, so handleEmbeddings returns 401 'Missing API key' — identical policy to the chat handler.","triggerScenarios":"POST /v1/embeddings with settings.requireApiKey=true and no Authorization/x-api-key header on the request.","commonSituations":"Embedding clients (custom scripts, LangChain embeddings wrappers) configured with baseURL only and no key because embeddings calls were previously unauthenticated; enforcement toggled on after deployment; a reverse proxy stripping the Authorization header.","solutions":["Send Authorization: Bearer <9router-api-key> (or x-api-key) with embeddings requests.","Set the apiKey option in your embedding client/driver, not just baseURL.","Disable requireApiKey in dashboard settings if this is a trusted local-only deployment.","Verify no intermediary strips auth headers before the gateway."],"exampleFix":"// before\nconst embedder = new OpenAIEmbeddings({ baseURL: 'http://localhost:20128/v1' });\n\n// after\nconst embedder = new OpenAIEmbeddings({\n  baseURL: 'http://localhost:20128/v1',\n  apiKey: process.env.NINE_ROUTER_API_KEY\n});","handlingStrategy":"validation","validationCode":"const apiKey = process.env.NINE_ROUTER_API_KEY;\nif (!apiKey) throw new Error('embeddings client requires an API key (requireApiKey is on)');\nopts.headers = { ...opts.headers, Authorization: `Bearer ${apiKey}` };","typeGuard":"function requestHasKey(opts) {\n  const h = opts.headers || {};\n  return Boolean(h['x-api-key']) || /^Bearer\\s+\\S+/.test(h['Authorization'] || h['authorization'] || '');\n}","tryCatchPattern":"const res = await fetch(embedUrl, opts);\nif (res.status === 401 && (await res.text()).includes('Missing API key')) {\n  throw new Error('Add Authorization: Bearer <9router key> to embeddings requests');\n}","preventionTips":["Set apiKey in embedding clients (LangChain/OpenAI SDK), not just baseURL.","Centralize auth-header construction in one request wrapper for all endpoints.","Verify requireApiKey setting whenever cloning environments.","Integration-test one embeddings call per deployment to catch auth regressions."],"tags":["auth","http","unauthorized","api-key","embeddings"],"backgroundTag":"missing-api-key","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}