{"record":{"id":"9f9d50f2ca49a89a","repo":"thedotmack/claude-mem","slug":"forbidden-9f9d50","errorCode":"Forbidden","errorMessage":"Project-scoped API keys cannot create projects","messagePattern":"Project-scoped API keys cannot create projects","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"src/server/routes/v1/ServerV1Routes.ts","lineNumber":97,"sourceCode":"        name: 'claude-mem-server',\n        version: BUILT_IN_VERSION,\n        ...(this.options.runtime ? { runtime: this.options.runtime } : {}),\n        authMode: this.options.authMode ?? process.env.CLAUDE_MEM_AUTH_MODE ?? 'api-key',\n      });\n    });\n\n    app.get('/v1/projects', readAuth, (req, res) => {\n      const repo = new ProjectsRepository(this.options.getDatabase());\n      const projects = req.authContext?.projectId\n        ? [repo.getById(req.authContext.projectId)].filter(project => project !== null)\n        : repo.list();\n      res.json({ projects });\n      this.audit(req, 'projects.list');\n    });\n\n    app.post('/v1/projects', writeAuth, this.handleCreate(CreateProjectSchema, (req, res, body) => {\n      if (req.authContext?.projectId) {\n        res.status(403).json({ error: 'Forbidden', message: 'Project-scoped API keys cannot create projects' });\n        return;\n      }\n      const project = new ProjectsRepository(this.options.getDatabase()).create(body);\n      this.audit(req, 'project.create', project.id);\n      res.status(201).json({ project });\n    }));\n\n    app.get('/v1/projects/:id', readAuth, (req, res) => {\n      const id = this.routeParam(req.params.id);\n      if (!this.ensureProjectAllowed(req, res, id)) return;\n      const project = new ProjectsRepository(this.options.getDatabase()).getById(id);\n      if (!project) {\n        res.status(404).json({ error: 'NotFound', message: 'Project not found' });\n        return;\n      }\n      this.audit(req, 'project.read', project.id);\n      res.json({ project });\n    });","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/server/routes/v1/ServerV1Routes.ts#L79-L115","documentation":"POST /v1/projects is guarded by writeAuth; if the authenticated API key carries a projectId scope (req.authContext.projectId is set), the route refuses with 403 Forbidden before any repository write. A project-scoped key is deliberately barred from minting new projects; only a team-level (unscoped) key may create them.","triggerScenarios":"Calling POST /v1/projects with an API key that was created with a projectId constraint. The scope check runs before the ProjectsRepository.create call, so no partial state is written.","commonSituations":"CI or an SDK configured with the ingestion key instead of an admin key; deployment scripts reusing a project-scoped key across environments; key rotation accidentally selecting a scoped key.","solutions":["Use a team-scoped API key (one without a projectId) for project creation","If you did not intend to create projects, remove that call from the client","Confirm the key's scope first: GET /v1/projects returns exactly one project for a project-scoped key"],"exampleFix":"// before — project-scoped key\ncreateProject(teamKeyScopedToProjectA, { name: 'new-project' }); // 403\n\n// after — unscoped team key\ncreateProject(teamAdminKey, { name: 'new-project' }); // 201","handlingStrategy":"validation","validationCode":"async function isProjectScopedKey(base: string, key: string): Promise<boolean> {\n  const res = await fetch(`${base}/v1/projects`, { headers: { Authorization: `Bearer ${key}` } });\n  const { projects } = await res.json();\n  return projects.length === 1; // project-scoped keys see exactly their own project\n}\n\nif (await isProjectScopedKey(base, key)) throw new Error('need a team-level key to create projects');","typeGuard":null,"tryCatchPattern":"const res = await fetch(`${base}/v1/projects`, { method: 'POST', headers: jsonHeaders, body: JSON.stringify({ name }) });\nif (res.status === 403) {\n  const body = await res.json();\n  if (body.message?.includes('Project-scoped')) throw new Error(`Key ${keyName} is project-scoped; supply a team-level key`);\n  throw new Error(`forbidden: ${body.message}`);\n}","preventionTips":["Name API keys after their scope (team-admin vs project-ingest) in your secret store","Keep separate env vars for admin operations vs data ingestion","Never reuse an ingestion key for provisioning scripts"],"tags":["authorization","api-key","http-403","projects","multi-tenant"],"backgroundTag":"insufficient-permissions","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}