calcom/cal.diy · error · BadRequestException

ApiKeysService -Cannot set both apiKeyDaysValid and apiKeyNe

Error message

ApiKeysService -Cannot set both apiKeyDaysValid and apiKeyNeverExpires. It has to be either or none of them.

What it means

Thrown by ApiKeysService.createApiKey when both createApiKeyInput.apiKeyDaysValid and createApiKeyInput.apiKeyNeverExpires are truthy simultaneously. The API enforces mutual exclusivity: a key either expires after a specified number of days or never expires. Setting both is logically contradictory and rejected with BadRequestException (HTTP 400) before any database operation occurs.

Source

Thrown at apps/api/v2/src/modules/api-keys/services/api-keys.service.ts:35

    private readonly config: ConfigService
  ) {}

  async getRequestApiKey(request: ApiAuthGuardRequest) {
    if (request.authMethod !== AuthMethods.API_KEY) {
      throw new UnauthorizedException(
        "ApiKeysService - This endpoint can only be accessed using an API key by providing 'Authorization: Bearer <apiKey>' header"
      );
    }
    const apiKey = request.get("Authorization")?.replace("Bearer ", "");
    if (!apiKey) {
      throw new UnauthorizedException("ApiKeysService - No API key provided");
    }
    return apiKey;
  }

  async createApiKey(authUserId: number, createApiKeyInput: CreateApiKeyInput) {
    if (createApiKeyInput.apiKeyDaysValid && createApiKeyInput.apiKeyNeverExpires) {
      throw new BadRequestException(
        "ApiKeysService -Cannot set both apiKeyDaysValid and apiKeyNeverExpires. It has to be either or none of them."
      );
    }

    const defaultApiKeyDaysValid = 30;
    const apiKeyExpiresAfterDays = createApiKeyInput.apiKeyDaysValid
      ? createApiKeyInput.apiKeyDaysValid
      : defaultApiKeyDaysValid;
    const apiKeyExpiresAt = DateTime.utc().plus({ days: apiKeyExpiresAfterDays }).toJSDate();
    const apiKey = await createApiKeyHandler({
      ctx: {
        user: {
          id: authUserId,
        },
      },
      input: {
        note: createApiKeyInput.note,
        neverExpires: !!createApiKeyInput.apiKeyNeverExpires,

View on GitHub (pinned to 176037d0af)

Solutions

  1. Remove one of the two fields from the request body: use either apiKeyDaysValid OR apiKeyNeverExpires, never both.
  2. Add client-side validation to disable or clear one field when the other is set.
  3. If neither is provided, the service defaults to 30 days expiry (defaultApiKeyDaysValid at line 40).

Example fix

// before: both flags set
const body = {
  note: 'CI key',
  apiKeyDaysValid: 90,
  apiKeyNeverExpires: true
};

// after: mutually exclusive
const body = {
  note: 'CI key',
  apiKeyDaysValid: 90
  // OR: apiKeyNeverExpires: true
};
Defensive patterns

Strategy: validation

Validate before calling

// Enforce mutual exclusivity before making the request
const validateCreateApiKeyInput = (input: {
  apiKeyDaysValid?: number;
  apiKeyNeverExpires?: boolean;
}): void => {
  if (input.apiKeyDaysValid && input.apiKeyNeverExpires) {
    throw new Error(
      'Cannot set both apiKeyDaysValid and apiKeyNeverExpires. Choose one or neither (defaults to 30 days).'
    );
  }
  if (input.apiKeyDaysValid !== undefined && input.apiKeyDaysValid <= 0) {
    throw new Error('apiKeyDaysValid must be a positive number');
  }
};

Type guard

type ValidCreateApiKeyInput =
  | { apiKeyDaysValid: number; apiKeyNeverExpires?: false }
  | { apiKeyDaysValid?: number; apiKeyNeverExpires: true }
  | { apiKeyDaysValid?: undefined; apiKeyNeverExpires?: undefined };

const isMutuallyExclusive = (i: {
  apiKeyDaysValid?: number;
  apiKeyNeverExpires?: boolean;
}): i is ValidCreateApiKeyInput =>
  !(i.apiKeyDaysValid && i.apiKeyNeverExpires);

Prevention

When it happens

Trigger: A POST /v2/api-keys request body includes both { "apiKeyDaysValid": 90, "apiKeyNeverExpires": true }. A client form that allows selecting both options without disabling the other. A payload constructed programmatically where both fields default to truthy values.

Common situations: Frontend form validation not enforcing mutual exclusivity before submission. API client SDK with optional fields where the caller sets both inadvertently. Copy-pasting a request body and forgetting to remove one field. Default values in a test fixture that set both fields.

Related errors


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