{"record":{"id":"d504d195c7a2fb47","repo":"langfuse/langfuse","slug":"access-denied-need-to-use-basic-auth-with-secret","errorCode":null,"errorMessage":"Access denied - need to use basic auth with secret key to ${req.method} prompts","messagePattern":"Access denied - need to use basic auth with secret key to (.+?) prompts","errorType":"exception","errorClass":"ForbiddenError","httpStatus":403,"severity":"error","filePath":"web/src/features/prompts/server/utils/authorizePromptRequest.ts","lineNumber":17,"sourceCode":"import { ApiAuthService } from \"@/src/features/public-api/server/apiAuth\";\nimport { type NextApiRequest } from \"next\";\nimport { UnauthorizedError, ForbiddenError } from \"@langfuse/shared\";\nimport { prisma } from \"@langfuse/shared/src/db\";\nimport {\n  type AuthHeaderValidVerificationResult,\n  redis,\n} from \"@langfuse/shared/src/server\";\n\nexport async function authorizePromptRequestOrThrow(req: NextApiRequest) {\n  const authCheck = await new ApiAuthService(\n    prisma,\n    redis,\n  ).verifyAuthHeaderAndReturnScope(req.headers.authorization);\n  if (!authCheck.validKey) throw new UnauthorizedError(authCheck.error);\n  if (authCheck.scope.accessLevel !== \"project\")\n    throw new ForbiddenError(\n      `Access denied - need to use basic auth with secret key to ${req.method} prompts`,\n    );\n  if (!authCheck.scope.projectId) {\n    throw new ForbiddenError(`No valid projectId found for auth token`);\n  }\n  return authCheck as AuthHeaderValidVerificationResult & {\n    scope: { projectId: string; accessLevel: \"project\" };\n  };\n}\n","sourceCodeStart":1,"sourceCodeEnd":27,"githubUrl":"https://github.com/langfuse/langfuse/blob/59d92c7cf365150d10b753b5a0d1708902a2ed60/web/src/features/prompts/server/utils/authorizePromptRequest.ts#L1-L27","documentation":"ForbiddenError thrown when the auth key's scope.accessLevel is not 'project': prompt API operations require a project-scoped secret key (basic auth), not a public or organization-scoped key.","triggerScenarios":"Calling GET/POST/etc. on /api/public/prompts with a public key (publicApiKey access level) or any non-project scope, where req.method is reflected in the message.","commonSituations":"Using a public-only key (e.g., tracing ingestion key) for prompt management; mixing up pk-/sk- prefixed keys; scripts built for ingestion reused for prompt CRUD.","solutions":["Use the project's secret key (sk-) with Basic auth for prompt endpoints","Confirm accessLevel is 'project' in the key's settings","Separate ingestion keys from management keys in your configuration"],"exampleFix":"// before\nAuthorization: Basic base64(publicApiKey:)\n// after\nAuthorization: Basic base64(publicKey:secretKey)","handlingStrategy":"validation","validationCode":"// Only use project secret keys for prompt management\nif (!secretKey.startsWith('sk-')) throw new Error('Prompt APIs need a project secret key');","typeGuard":"const isProjectSecretKey = (k: string) => k.startsWith('sk-');","tryCatchPattern":"try {\n  await api.prompts.create(body);\n} catch (e) {\n  if (e.status === 403 && /basic auth with secret key/.test(e.message)) {\n    // switch from public/bearer to project secret key\n  }\n}","preventionTips":["Separate ingestion keys from management keys","Default prompt SDK auth to Basic with sk- secret keys"],"tags":["auth","forbidden","scope","public-api"],"backgroundTag":"insufficient-api-key-scope","analyzedSha":"59d92c7cf365150d10b753b5a0d1708902a2ed60","analyzedAt":"2026-08-27T22:22:00.402Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}