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
- Read the forwarded message; for input errors, fix the payload per errors 392/393.
- For DB-shaped messages, inspect server logs for the original Prisma error and address the constraint.
- 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
- Validate the input client-side before POST.
- Read the forwarded message to distinguish input errors from DB errors.
- Wrap createPrivateLink callers in try/catch and map known messages to user-facing hints.
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
- Failed to create private link
- Failed to get private links
- Could not add ICS feeds, try using private ics feed.
- Either expiresAt or maxUsageCount must be provided
- Provide only one of expiresAt or maxUsageCount
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/ad4c5aa579db08a6.
Report an issue: GitHub.