{"record":{"id":"8ba4f2574b02e508","repo":"thedotmack/claude-mem","slug":"badrequest","errorCode":"BadRequest","errorMessage":"Legacy /api/sessions/observations requires a project-scoped API key","messagePattern":"Legacy /api/sessions/observations requires a project-scoped API key","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"src/server/compat/SessionsObservationsAdapter.ts","lineNumber":80,"sourceCode":"    });\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),\n          contentSessionId: parsed.data.contentSessionId,\n        });\n        res.status(500).json({ stored: false, reason: 'internal_error' });\n      }\n    }));\n  }\n","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/server/compat/SessionsObservationsAdapter.ts#L62-L98","documentation":"The legacy /api/sessions/observations compat route returns HTTP 400 BadRequest when the authenticated API key has no projectId in its auth context. The legacy Claude Code payload does not carry a Server beta projectId, so without a project-scoped key the adapter cannot place the observation row in a tenant-scoped table and rejects the request up front.","triggerScenarios":"POST /api/sessions/observations with a valid API key that is team-scoped but not project-scoped (req.authContext.projectId is null).","commonSituations":"Operators minting legacy team-level API keys for older Claude Code clients after migrating to the multi-tenant Server beta; env/config where keys are provisioned without project binding; partial migration where the key rotation step was skipped.","solutions":["Create/use an API key scoped to a specific project instead of a team-wide key","Verify the key provisioning flow sets projectId in the auth context (check verifyServerApiKey / key metadata)","If this is a single-tenant deployment, bind the key to the default project","Update client tooling or docs so new keys are always project-scoped"],"exampleFix":"// before\nconst key = createApiKey({ teamId: 'team_123' });\n// after\nconst key = createApiKey({ teamId: 'team_123', projectId: 'proj_456' });","handlingStrategy":"validation","validationCode":"// before calling the endpoint, assert the key is project-scoped\nfunction assertProjectScopedKey(authContext) {\n  if (!authContext?.projectId) throw new Error('API key must be project-scoped for legacy compat routes');\n}\nassertProjectScopedKey(authContext);","typeGuard":"const isProjectScoped = (ctx) => typeof ctx?.projectId === 'string' && ctx.projectId.length > 0;","tryCatchPattern":null,"preventionTips":["Always provision compat-route keys with an explicit projectId","Add a startup check that validates configured keys carry both teamId and projectId","Document that legacy routes require project-scoped keys in the migration guide"],"tags":["http","api","authentication","multi-tenancy"],"backgroundTag":"missing-credentials","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"}