{"record":{"id":"97d99b06c78f819d","repo":"thedotmack/claude-mem","slug":"unauthorized-97d99b","errorCode":"Unauthorized","errorMessage":"Missing API key (Authorization: Bearer <key> or X-Api-Key: <key>)","messagePattern":"Missing API key \\(Authorization: Bearer <key> or X-Api-Key: <key>\\)","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"src/server/middleware/postgres-auth.ts","lineNumber":91,"sourceCode":"    && hasLoopbackHostHeader(req)\n    && !hasForwardedClientHeaders(req)\n  ) {\n    const ctx: AuthContext = {\n      userId: null,\n      organizationId: null,\n      teamId: options.localDevTeamId ?? null,\n      projectId: null,\n      scopes: ['local-dev'],\n      apiKeyId: null,\n      mode: 'local-dev',\n    };\n    req.authContext = ctx;\n    next();\n    return;\n  }\n\n  if (!rawKey) {\n    res.status(401).json({\n      error: 'Unauthorized',\n      message: 'Missing API key (Authorization: Bearer <key> or X-Api-Key: <key>)',\n    });\n    return;\n  }\n\n  const verified = await verifyPostgresApiKey(pool, rawKey, options.requiredScopes ?? []);\n  if (!verified) {\n    res.status(403).json({ error: 'Forbidden', message: 'Invalid API key or insufficient scope' });\n    return;\n  }\n\n  const ctx: AuthContext = {\n    userId: null,\n    organizationId: null,\n    teamId: verified.teamId,\n    projectId: verified.projectId,\n    scopes: verified.scopes,","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/server/middleware/postgres-auth.ts#L73-L109","documentation":"401 from the Postgres auth middleware when no API key is present on the request. Same contract as the SQLite variant: the key must arrive as Authorization: Bearer <key> or X-Api-Key: <key>; when local-dev is off (or localDevTeamId unset and dev mode inactive) and rawKey is empty, the middleware rejects with 401 before touching Postgres.","triggerScenarios":"Any request to a postgres-backed route (e.g. /v1/events) with neither auth header; a gateway or sidecar that drops the Authorization header; client configured with an unset key env var so it sends nothing.","commonSituations":"Deploying against Postgres for the first time and reusing scripts written for local-dev mode where no header was needed; .env missing API_KEY in the container; ingress stripping Authorization unless explicitly forwarded.","solutions":["Attach the key: Authorization: Bearer <key> or X-Api-Key: <key> on every request.","Verify the client's key env var is set in the runtime that actually issues requests.","Ensure proxies/ingress forward the Authorization header to the app.","Use local-dev mode only for local runs, and configure localDevTeamId if you need unauthenticated local requests."],"exampleFix":"// before\nconst res = await fetch(`${base}/v1/events`, { method: 'POST', body }); // 401\n\n// after\nconst res = await fetch(`${base}/v1/events`, {\n  method: 'POST',\n  headers: { 'X-Api-Key': process.env.API_KEY!, 'Content-Type': 'application/json' },\n  body,\n});","handlingStrategy":"validation","validationCode":"const apiKey = process.env.API_KEY;\nif (!apiKey) {\n  throw new Error('API_KEY unset; postgres-auth will reject with 401 before hitting the database');\n}\nconst headers = { Authorization: `Bearer ${apiKey}` };","typeGuard":"interface UnauthorizedBody { error: string; message: string }\nfunction isMissingKey401(res: Response, body: unknown): boolean {\n  return res.status === 401 && typeof body === 'object' && body !== null &&\n    (body as UnauthorizedBody).error === 'Unauthorized';\n}","tryCatchPattern":null,"preventionTips":["Validate required env vars in a startup checklist.","Ensure ingress/proxy forwards Authorization to the app.","Use one shared HTTP client so headers cannot be forgotten per call."],"tags":["auth","http-401","api-key","postgres","middleware"],"backgroundTag":"missing-api-key","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-08-29T22:17:34.462Z"}