{"record":{"id":"35ef40e2e27d2a2f","repo":"thedotmack/claude-mem","slug":"forbidden-35ef40","errorCode":"Forbidden","errorMessage":"Invalid API key or insufficient scope","messagePattern":"Invalid API key or insufficient scope","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"src/server/middleware/postgres-auth.ts","lineNumber":100,"sourceCode":"      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,\n    apiKeyId: verified.apiKeyId,\n    mode: 'api-key',\n  };\n  req.authContext = ctx;\n  next();\n}\n\ninterface VerifiedPostgresApiKey {\n  apiKeyId: string;","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/server/middleware/postgres-auth.ts#L82-L118","documentation":"authenticatePostgresRequest returns 403 Forbidden when verifyPostgresApiKey fails, meaning either the presented Postgres API key does not exist/verify against the database, or it exists but lacks one of the scopes required for the request (options.requiredScopes). Authentication itself succeeded at the transport level; the key is the problem.","triggerScenarios":"Any request to Postgres-backed routes where the raw key passed to verifyPostgresApiKey (pool, rawKey, requiredScopes) returns false: unknown key, revoked key, or a key whose stored scopes do not include a required scope (e.g. write endpoints requiring a write scope with a read-only key).","commonSituations":"Developer set CLAUDE_MEM/Postgres API key env var to a stale or placeholder value; key was rotated server-side; using a read-only ingestion key against a write route; copy-paste dropped a character or included quotes/whitespace.","solutions":["Verify the API key value sent (header/query) matches a valid, non-revoked key in the Postgres api_keys table.","Check the key's scopes in the database include the requiredScopes for the endpoint being called (e.g. write scope for POST routes).","Regenerate a new API key with the needed scopes and update the client's env/config.","Confirm no whitespace, quotes, or truncation in the configured key string."],"exampleFix":"// before\ncurl -H 'Authorization: Bearer old-readonly-key' -X POST .../v1/events\n// after\n# issue a key with write scope, then\ncurl -H 'Authorization: Bearer <new-key-with-write-scope>' -X POST .../v1/events","handlingStrategy":"validation","validationCode":"function isValidApiKey(key) {\n  return typeof key === 'string' && key.trim().length > 0 && !/\\s/.test(key.trim());\n}","typeGuard":"function hasApiKey(cfg): cfg is typeof cfg & { apiKey: string } {\n  return typeof (cfg as { apiKey?: unknown }).apiKey === 'string' && cfg.apiKey.length > 0;\n}","tryCatchPattern":"if (res.status === 403) {\n  const body = await res.json();\n  console.error('Auth rejected:', body.message, '— check API key and its scopes');\n}","preventionTips":["Store the API key in a single env var and validate it non-empty at client startup.","Track key scopes: use a write-scoped key for POST routes, read-scoped only for GETs.","Rotate keys through a documented process and update all consumers at once.","Trim and de-quote the key when loading from config files."],"tags":["authentication","api-key","authorization","middleware"],"backgroundTag":"invalid-api-key","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}