{"record":{"id":"f1d64c88a210b74f","repo":"calcom/cal.diy","slug":"signup-is-disabled","errorCode":null,"errorMessage":"Signup is disabled","messagePattern":"Signup is disabled","errorType":"http","errorClass":"HttpError","httpStatus":403,"severity":"warning","filePath":"apps/web/app/api/auth/signup/route.ts","lineNumber":32,"sourceCode":"import { checkCfTurnstileToken } from \"@calcom/lib/server/checkCfTurnstileToken\";\nimport { prisma } from \"@calcom/prisma\";\nimport { signupSchema } from \"@calcom/prisma/zod-utils\";\n\nasync function ensureSignupIsEnabled(body: Record<string, string>) {\n  const { token } = signupSchema\n    .pick({\n      token: true,\n    })\n    .parse(body);\n\n  // Still allow signups if there is a team invite\n  if (token) return;\n\n  const featuresRepository = new FeaturesRepository(prisma);\n  const signupDisabled = await featuresRepository.checkIfFeatureIsEnabledGlobally(\"disable-signup\");\n\n  if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === \"true\" || signupDisabled) {\n    throw new HttpError({\n      statusCode: 403,\n      message: \"Signup is disabled\",\n    });\n  }\n}\n\nasync function handler(req: NextRequest) {\n  const remoteIp = getIP(req);\n  // Use a try catch instead of returning res every time\n  try {\n    // Rate limit: 10 signups per 60 seconds per IP\n    await checkRateLimitAndThrowError({\n      rateLimitingType: \"core\",\n      identifier: `api:signup:${piiHasher.hash(remoteIp)}`,\n    });\n\n    const body = await parseRequestData(req);\n    const query = Object.fromEntries(req.nextUrl.searchParams.entries());","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/web/app/api/auth/signup/route.ts#L14-L50","documentation":"Thrown by the /api/auth/signup route (HttpError, HTTP 403) in ensureEnvironmentForSignup when either NEXT_PUBLIC_DISABLE_SIGNUP env is 'true' or the FeaturesRepository reports the 'disable-signup' feature flag is globally enabled. Team-invite signups (valid token) bypass this gate entirely.","triggerScenarios":"POST /api/auth/signup without a team-invite token while public signups are turned off via env var or feature flag.","commonSituations":"Self-hosted/enterprise deployment that disables public registration, a feature flag flipped in production, an env var misconfigured to 'true' unintentionally.","solutions":["If public signup should be on, unset NEXT_PUBLIC_DISABLE_SIGNUP (or set to 'false') and disable the 'disable-signup' feature flag.","If signup is intentionally disabled, accept invite-only flow: always sign up with a valid team-invite token so ensureEnvironmentForSignup returns early.","Surface a clear 'registration is closed' message on 403 instead of retrying."],"exampleFix":"// before\nawait signup({ username, email }); // fails 403 when disabled\n\n// after\nif (!inviteToken) {\n  showNotice('Public registration is disabled. Use your team invite link.');\n  return;\n}\nawait signup({ username, email, token: inviteToken });","handlingStrategy":"validation","validationCode":"// Detect disabled signup before attempting registration\nif (!inviteToken && (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === 'true')) {\n  showNotice('Public registration is disabled. Use your team invite link.');\n  return;\n}\nawait signup({ username, email, token: inviteToken });","typeGuard":null,"tryCatchPattern":"try {\n  await signup(payload);\n} catch (e) {\n  if (e instanceof HttpError && e.statusCode === 403 && /Signup is disabled/.test(e.message)) {\n    showRegistrationClosedNotice();\n    return;\n  }\n  throw e;\n}","preventionTips":["Always carry a valid team-invite token if public signup is off.","Surface NEXT_PUBLIC_DISABLE_SIGNUP / the feature flag to the signup UI.","Do not retry on 403; it is a policy decision, not a transient failure."],"tags":["signup","auth","feature-flag","env","forbidden","invite-only"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}