{"record":{"id":"0e6c5b38cb5fb5b0","repo":"thedotmack/claude-mem","slug":"forbidden-0e6c5b","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/auth.ts","lineNumber":80,"sourceCode":"        scopes: ['local-dev'],\n        apiKeyId: null,\n        mode: 'local-dev',\n      };\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 = verifyServerApiKey(getDatabase(), 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    req.authContext = {\n      userId: null,\n      organizationId: null,\n      teamId: verified.teamId,\n      projectId: verified.projectId,\n      scopes: verified.scopes,\n      apiKeyId: verified.record.id,\n      mode: 'api-key',\n    };\n    next();\n  };\n}\n","sourceCodeStart":62,"sourceCodeEnd":96,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/server/middleware/auth.ts#L62-L96","documentation":"403 from the SQLite auth middleware: a key was presented but verifyServerApiKey returned null, meaning the key does not exist, is revoked/expired, or lacks one of the route's requiredScopes (e.g. memories:write on the compat summarize route). Note the distinction from 401 — 401 means no key, 403 means the key failed verification or scope.","triggerScenarios":"Request with a mistyped or truncated key; a key deleted or rotated server-side while clients still hold the old value; a read-only key (no memories:write) hitting POST /api/sessions/summarize whose writeAuth requires memories:write; key exists in a different database/environment than the one serving the request.","commonSituations":"Key rotation deployed to server but clients cached the old key; copy-paste lost characters or gained whitespace/newline; dev pointed at prod with a dev-database key; scope set at mint time omitted the write scope the endpoint needs.","solutions":["Re-copy the full key with no trailing whitespace/newline and retry.","Confirm the key exists and is active in the server's database (keys table / key management endpoint).","Check the route's requiredScopes and mint a key that includes them (e.g. memories:write for summarize).","If keys were rotated, update the client's stored credential."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Verify key validity + scopes before real work (cheapest probe: an authenticated read)\nconst probe = await fetch(`${base}/v1/keys`, { headers: { 'X-Api-Key': key } });\nif (probe.status === 403) {\n  throw new Error('Key invalid or missing required scope — remint before continuing');\n}","typeGuard":"function isInvalidKeyResponse(res: Response): boolean {\n  return res.status === 403;\n}\ninterface ForbiddenBody { error: string; message: string }\nfunction isInvalidKeyBody(body: unknown): body is ForbiddenBody {\n  return typeof body === 'object' && body !== null &&\n    (body as ForbiddenBody).error === 'Forbidden' &&\n    (body as ForbiddenBody).message === 'Invalid API key or insufficient scope';\n}","tryCatchPattern":null,"preventionTips":["Never hardcode scopes per call site — derive them from a shared per-endpoint map.","Rotate keys atomically: mint new, switch clients, then revoke old.","Trim whitespace when loading keys from env/files."],"tags":["auth","http-403","api-key","scopes","middleware"],"backgroundTag":"invalid-api-key","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}