calcom/cal.diy · error · BadRequestException

${error.message}

Error message

${error.message}

What it means

In PrivateLinksService.createPrivateLink's catch block: if the underlying failure (transformCreateInput validation, generateHashedLink, repo.create, or the mapping step) is an instance of Error, its .message is re-thrown inside a BadRequestException. So the literal text is whatever the inner code raised: most commonly 'Either expiresAt or maxUsageCount must be provided' or 'Provide only one of expiresAt or maxUsageCount' from the input service, but it could also be a Prisma/DB error message.

Source

Thrown at apps/api/v2/src/platform/event-types-private-links/services/private-links.service.ts:44

      const transformedInput = this.inputService.transformCreateInput(input);
      const created = await this.repo.create(eventTypeId, {
        link: generateHashedLink(userId),
        expiresAt: transformedInput.expiresAt ?? null,
        maxUsageCount: transformedInput.maxUsageCount ?? null,
      });
      const mapped: PrivateLinkData = {
        id: created.link,
        eventTypeId,
        isExpired: isLinkExpired(created as any),
        bookingUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL || "https://cal.com"}/d/${created.link}`,
        expiresAt: created.expiresAt ?? null,
        maxUsageCount: (created as any).maxUsageCount ?? null,
        usageCount: (created as any).usageCount ?? 0,
      };
      return this.outputService.transformToOutput(mapped);
    } catch (error) {
      if (error instanceof Error) {
        throw new BadRequestException(error.message);
      }
      throw new BadRequestException("Failed to create private link");
    }
  }

  async getPrivateLinks(eventTypeId: number): Promise<PrivateLinkOutput[]> {
    try {
      const links = await this.repo.listByEventTypeId(eventTypeId);
      const mapped: PrivateLinkData[] = links.map((l) => ({
        id: l.link,
        eventTypeId,
        isExpired: isLinkExpired(l as any),
        bookingUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL || "https://cal.com"}/d/${l.link}`,
        expiresAt: l.expiresAt ?? null,
        maxUsageCount: l.maxUsageCount ?? null,
        usageCount: l.usageCount ?? 0,
      }));
      return this.outputService.transformArrayToOutput(mapped);

View on GitHub (pinned to 176037d0af)

Solutions

  1. Read the forwarded message; for input errors, fix the payload per errors 392/393.
  2. For DB-shaped messages, inspect server logs for the original Prisma error and address the constraint.
  3. Validate the input client-side before posting (see defense strategies) to avoid the round-trip.
Defensive patterns

Strategy: try-catch

Validate before calling

try {
  inputService.transformCreateInput(input);
} catch (e) {
  // surface e.message to the user (e.g. 'Either expiresAt or maxUsageCount must be provided')
}

Try / catch

try {
  await privateLinksService.createPrivateLink(eventTypeId, userId, input);
} catch (e) {
  if (e instanceof BadRequestException) {
    // e.message is the inner Error.message; handle known input-validation strings
  } else throw e;
}

Prevention

When it happens

Trigger: Calling POST /v2/event-types/{id}/private-links with input that fails transformCreateInput (errors 392/393); a DB constraint failure during repo.create (e.g. unique violation on the generated link); generateHashedLink throwing because userId is invalid; an unexpected Prisma error whose message leaks through.

Common situations: Invalid input payload; collision on the hashed link (rare); DB connection issue mid-insert; an unhandled Prisma validation error whose message is forwarded raw.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/ad4c5aa579db08a6. Report an issue: GitHub.