thedotmack/claude-mem · error
BadRequest
BadRequest
Error message
Legacy /api/sessions/summarize requires a project-scoped API key
What it means
Returned by the legacy POST /api/sessions/summarize compat route when the key is team-bound but has no project binding. Legacy semantics pin every summarize call to one project, so req.authContext.projectId must be non-null; a team-wide key fails this check with 400 even though it passed the team check.
Source
Thrown at src/server/compat/SessionsSummarizeAdapter.ts:63
authMode: this.options.authMode,
allowLocalDevBypass: this.options.allowLocalDevBypass,
requiredScopes: ['memories:write'],
});
app.post('/api/sessions/summarize', writeAuth, this.asyncHandler(async (req, res) => {
const parsed = summarizeSchema.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) {
res.status(400).json({
error: 'BadRequest',
message: 'Legacy /api/sessions/summarize requires a project-scoped API key',
});
return;
}
// Subagent contexts in legacy code emit summarize calls but the worker
// skipped them. We preserve the legacy semantics so existing clients
// see the same response shape.
if (parsed.data.agentId) {
res.json({ status: 'skipped', reason: 'subagent_context' });
return;
}
try {
await this.summarizeSession(req, res, parsed.data, teamId, projectId);
} catch (error) {
logger.error('SYSTEM', 'compat summarize adapter failed', {View on GitHub (pinned to e2d1df569a)
Solutions
- Use a project-scoped API key: create one via POST /v1/keys with both the team and the target projectId.
- If the key should cover one project, rebind it so its projectId is set.
- Confirm the key's projectId via key introspection before switching the client to the legacy endpoint.
- Move the client to the modern route that accepts projectId in the payload instead of relying on key scope.
Example fix
// before: team-scoped key -> 400 BadRequest
const res = await fetch(`${base}/api/sessions/summarize`, {
method: 'POST',
headers: { Authorization: `Bearer ${TEAM_KEY}` },
body: JSON.stringify(payload),
});
// after: mint a project-scoped key for the legacy route
// POST /v1/keys { teamId, projectId, scopes: ['memories:write'] }
const res = await fetch(`${base}/api/sessions/summarize`, {
method: 'POST',
headers: { Authorization: `Bearer ${PROJECT_SCOPED_KEY}` },
body: JSON.stringify(payload),
}); Defensive patterns
Strategy: validation
Validate before calling
function assertProjectScopedKey(keyMeta: { teamId: string | null; projectId: string | null }) {
if (!keyMeta.teamId || !keyMeta.projectId) {
throw new Error('Legacy /api/sessions/summarize needs a project-scoped (team+project bound) API key');
}
} Type guard
interface CompatBadRequest { error: string; message: string }
function isProjectScopeRequired(body: unknown): body is CompatBadRequest {
return (
typeof body === 'object' && body !== null &&
(body as CompatBadRequest).error === 'BadRequest' &&
(body as CompatBadRequest).message?.includes('project-scoped API key')
);
} Try / catch
if (res.status === 400) {
const body = await res.json();
if (isProjectScopeRequired(body)) {
// key scope problem, not payload: remint key, never resend same request
}
} Prevention
- Document which endpoints need project-scoped keys and label stored keys with their scope.
- Automate key minting with the exact scopes each client needs (memories:write + projectId).
When it happens
Trigger: Calling /api/sessions/summarize with a team-scoped API key (no projectId on the key record); reusing a key minted for team-level read endpoints on the legacy compat route; upgrading a deployment to project-scoped keys while the client still holds an old team-only key.
Common situations: The same key works on /v1/events (project in body) but fails on the legacy route; ops minted a broad team key for convenience and legacy Claude Code clients then break with 400.
Related errors
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/0f8aa3fa1787397f.
Report an issue: GitHub.