thedotmack/claude-mem · error
BadRequest
BadRequest
Error message
Legacy /api/sessions/observations requires a project-scoped API key
What it means
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.
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
Example fix
// before
const key = createApiKey({ teamId: 'team_123' });
// after
const key = createApiKey({ teamId: 'team_123', projectId: 'proj_456' }); Defensive patterns
Strategy: validation
Validate before calling
// before calling the endpoint, assert the key is project-scoped
function assertProjectScopedKey(authContext) {
if (!authContext?.projectId) throw new Error('API key must be project-scoped for legacy compat routes');
}
assertProjectScopedKey(authContext); Type guard
const isProjectScoped = (ctx) => typeof ctx?.projectId === 'string' && ctx.projectId.length > 0;
Prevention
- 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
When it happens
Trigger: POST /api/sessions/observations with a valid API key that is team-scoped but not project-scoped (req.authContext.projectId is null).
Common situations: 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.
Related errors
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/8ba4f2574b02e508.
Report an issue: GitHub.
Appendix: source
Thrown at src/server/compat/SessionsObservationsAdapter.ts:80
});
app.post('/api/sessions/observations', writeAuth, this.asyncHandler(async (req, res) => {
const parsed = observationsSchema.safeParse(req.body);
if (!parsed.success) {
res.status(400).json({ error: 'ValidationError', issues: parsed.error.issues });
return;
}
const teamId = req.authContext?.teamId ?? null;
const projectId = req.authContext?.projectId ?? null;
if (!teamId) {
res.status(403).json({ error: 'Forbidden', message: 'API key is not bound to a team' });
return;
}
if (!projectId) {
// Compat mode requires a project-scoped key — the legacy payload does
// not carry a Server beta projectId, so without scope we cannot place
// the row in a tenant-scoped table.
res.status(400).json({
error: 'BadRequest',
message: 'Legacy /api/sessions/observations requires a project-scoped API key',
});
return;
}
try {
await this.ingestCompatObservation(req, res, parsed.data, teamId, projectId);
} catch (error) {
logger.error('SYSTEM', 'compat observations adapter failed', {
error: error instanceof Error ? error.message : String(error),
contentSessionId: parsed.data.contentSessionId,
});
res.status(500).json({ stored: false, reason: 'internal_error' });
}
}));
}
View on GitHub (pinned to d8bc9755e7)