calcom/cal.diy · error · Error
Either expiresAt or maxUsageCount must be provided
Error message
Either expiresAt or maxUsageCount must be provided
What it means
Thrown by PrivateLinksInputService.transformCreateInput. A private link must be constrained by either a time bound (expiresAt) or a usage bound (maxUsageCount): exactly one of the two. If neither is present in the CreatePrivateLinkInput, a plain Error('Either expiresAt or maxUsageCount must be provided') is thrown; this then surfaces through the service's catch block as a 400. The guard enforces that no private link can be both unbounded and eternal.
Source
Thrown at apps/api/v2/src/platform/event-types-private-links/services/private-links-input.service.ts:14
import { Injectable } from "@nestjs/common";
import { CreatePrivateLinkInput, UpdatePrivateLinkInput } from "@calcom/platform-types";
@Injectable()
export class PrivateLinksInputService {
constructor() {}
transformCreateInput(input: CreatePrivateLinkInput): CreatePrivateLinkInput {
const hasExpires = input.expiresAt !== undefined && input.expiresAt !== null;
const hasMaxCount = typeof input.maxUsageCount === "number";
if (!hasExpires && !hasMaxCount) {
throw new Error("Either expiresAt or maxUsageCount must be provided");
}
if (hasExpires && hasMaxCount) {
throw new Error("Provide only one of expiresAt or maxUsageCount");
}
return {
expiresAt: input.expiresAt,
maxUsageCount: input.maxUsageCount ?? (hasMaxCount ? input.maxUsageCount : undefined),
};
}
transformUpdateInput(input: UpdatePrivateLinkInput): UpdatePrivateLinkInput {
return {
linkId: input.linkId,
expiresAt: input.expiresAt,
maxUsageCount: input.maxUsageCount,
};View on GitHub (pinned to 176037d0af)
Solutions
- Send exactly one of { expiresAt: <iso8601|null> } or { maxUsageCount: <number> } in the request body.
- If you want a usage cap, ensure maxUsageCount is a JSON number, not a string.
- On the client, disable submit until one of the two fields is filled.
Example fix
// before
await api.post(`/v2/event-types/${id}/private-links`, {});
// after
await api.post(`/v2/event-types/${id}/private-links`, { maxUsageCount: 5 });
// or
await api.post(`/v2/event-types/${id}/private-links`, { expiresAt: '2025-12-31T23:59:59Z' }); Defensive patterns
Strategy: validation
Validate before calling
function validateCreateInput(input: { expiresAt?: string | null; maxUsageCount?: number }) {
const hasExpires = input.expiresAt != null;
const hasMax = typeof input.maxUsageCount === 'number';
if (!hasExpires && !hasMax) throw new Error('Provide expiresAt or maxUsageCount');
if (hasExpires && hasMax) throw new Error('Provide only one');
return input;
} Type guard
function isValidCreateInput(i: unknown): i is { expiresAt?: string } | { maxUsageCount: number } {
if (typeof i !== 'object' || i === null) return false;
const o = i as Record<string, unknown>;
const hasExpires = o.expiresAt != null;
const hasMax = typeof o.maxUsageCount === 'number';
return (hasExpires && !hasMax) || (!hasExpires && hasMax);
} Try / catch
try {
await privateLinksService.createPrivateLink(eventTypeId, userId, input);
} catch (e) {
if (e instanceof BadRequestException && /expiresAt|maxUsageCount/.test(e.message)) {
// fix the payload: send exactly one of the two
} else throw e;
} Prevention
- Make the two fields mutually exclusive in the UI (radio group).
- Send maxUsageCount as a JSON number, not a string.
- Disable submit until exactly one field is set.
When it happens
Trigger: POST /v2/event-types/{id}/private-links with a body like {} or {expiresAt: null, maxUsageCount: undefined}; a client that constructs the input from optional form fields where both were left blank; maxUsageCount passed as a string (typeof !== 'number' so hasMaxCount is false) alongside a null expiresAt.
Common situations: Frontend 'create private link' form submitted with both fields empty; maxUsageCount sent as a string '5' rather than the number 5 (typeof '5' === 'string', so the guard doesn't see it); a default value was removed during a refactor leaving both undefined.
Related errors
- Provide only one of expiresAt or maxUsageCount
- ApiKeysService -Cannot set both apiKeyDaysValid and apiKeyNe
- teamId is required for team events, please provide a valid t
- username is required for non-team events, please provide a v
- 'to' must not be before 'from'
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/f9ef4d139d16014d.
Report an issue: GitHub.