{"record":{"id":"3971bc3e34314d81","repo":"thedotmack/claude-mem","slug":"forbidden","errorCode":"Forbidden","errorMessage":"API key is not bound to a team","messagePattern":"API key is not bound to a team","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"src/server/compat/SessionsObservationsAdapter.ts","lineNumber":73,"sourceCode":"  constructor(private readonly options: SessionsObservationsAdapterOptions) {}\n\n  setupRoutes(app: Application): void {\n    const writeAuth = requirePostgresServerAuth(this.options.pool, {\n      authMode: this.options.authMode,\n      allowLocalDevBypass: this.options.allowLocalDevBypass,\n      requiredScopes: ['memories:write'],\n    });\n\n    app.post('/api/sessions/observations', writeAuth, this.asyncHandler(async (req, res) => {\n      const parsed = observationsSchema.safeParse(req.body);\n      if (!parsed.success) {\n        res.status(400).json({ error: 'ValidationError', issues: parsed.error.issues });\n        return;\n      }\n      const teamId = req.authContext?.teamId ?? null;\n      const projectId = req.authContext?.projectId ?? null;\n      if (!teamId) {\n        res.status(403).json({ error: 'Forbidden', message: 'API key is not bound to a team' });\n        return;\n      }\n      if (!projectId) {\n        // Compat mode requires a project-scoped key — the legacy payload does\n        // not carry a Server beta projectId, so without scope we cannot place\n        // the row in a tenant-scoped table.\n        res.status(400).json({\n          error: 'BadRequest',\n          message: 'Legacy /api/sessions/observations requires a project-scoped API key',\n        });\n        return;\n      }\n\n      try {\n        await this.ingestCompatObservation(req, res, parsed.data, teamId, projectId);\n      } catch (error) {\n        logger.error('SYSTEM', 'compat observations adapter failed', {\n          error: error instanceof Error ? error.message : String(error),","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/server/compat/SessionsObservationsAdapter.ts#L55-L91","documentation":"After validation passes, POST /api/sessions/observations requires an auth context bound to a team. If req.authContext.teamId is null, the adapter responds 403 with error 'Forbidden' and message 'API key is not bound to a team'. The API key used is valid but lacks team scoping.","triggerScenarios":"Calling the endpoint with an API key created without a team binding; auth middleware populated authContext but teamId was absent; using a global/admin key instead of a team-scoped key.","commonSituations":"Provisioning keys via scripts that skip team assignment; rotating to a new key with a different scope; using a service key in a multi-tenant deployment.","solutions":["Create/reissue the API key scoped to the intended team","Verify the auth middleware attaches teamId to req.authContext for this key","Check the Authorization key is the team-scoped one, not a global key","If using project keys, ensure the project belongs to the team so teamId resolves"],"exampleFix":"// before: key without team scope\nAuthorization: Bearer <global-key>  // -> 403 Forbidden\n// after: team-scoped key\nAuthorization: Bearer <team-scoped-key>","handlingStrategy":"validation","validationCode":"if (!authContext?.teamId) {\n  throw new Error('API key is not team-scoped; /api/sessions/observations requires a team');\n}","typeGuard":"function isTeamScoped(ctx?: AuthContext): ctx is AuthContext & { teamId: string } {\n  return !!ctx?.teamId;\n}","tryCatchPattern":"const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` }, ... });\nif (res.status === 403) {\n  const body = await res.json();\n  throw new Error(`${body.error}: ${body.message}`);\n}","preventionTips":["Provision API keys with team scope for compat endpoints","Assert authContext.teamId exists in integration tests","Avoid using global keys for tenant-scoped routes","Verify key scopes after rotation"],"tags":["auth","http","permissions","api"],"backgroundTag":"permission-denied","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"}