calcom/cal.diy · error · UnauthorizedException

ApiKeysService - This endpoint can only be accessed using an

Error message

ApiKeysService - This endpoint can only be accessed using an API key by providing 'Authorization: Bearer <apiKey>' header

What it means

Thrown by ApiKeysService.getRequestApiKey when request.authMethod does not equal AuthMethods.API_KEY ('api-key'). This endpoint requires API key authentication specifically, but the request was authenticated via a different method (OAuth client, access token, NextAuth session, or third-party access token). The guard has already authenticated the user, but the service rejects the request because API-key-specific operations require the API key itself for further processing (e.g. key refresh, key deletion).

Source

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

import { CreateApiKeyInput } from "@/modules/api-keys/inputs/create-api-key.input";
import { RefreshApiKeyInput } from "@/modules/api-keys/inputs/refresh-api-key.input";
import { ApiAuthGuardRequest } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
import { BadRequestException, Injectable, UnauthorizedException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { DateTime } from "luxon";

import { createApiKeyHandler } from "@calcom/platform-libraries";

@Injectable()
export class ApiKeysService {
  constructor(
    private readonly apiKeysRepository: ApiKeysRepository,
    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;

View on GitHub (pinned to 176037d0af)

Solutions

  1. Switch the Authorization header to use a valid API key (Bearer cal_<key>) instead of an OAuth access token when calling API-key management endpoints.
  2. If you don't have an API key yet, first create one via POST /v2/api-keys using an authenticated session or access token, then use that key for subsequent API-key-specific operations.
  3. Check the ApiAuthGuard configuration to confirm which auth methods are accepted on the route and ensure API_KEY is among them.

Example fix

// before: using OAuth access token for key-management endpoint
const res = await fetch('/v2/api-keys/refresh', {
  headers: { Authorization: `Bearer ${oauthAccessToken}` }
});

// after: use a valid API key for the Bearer header
const res = await fetch('/v2/api-keys/refresh', {
  headers: { Authorization: `Bearer cal_${validApiKey}` }
});
Defensive patterns

Strategy: validation

Validate before calling

// Validate auth method before calling API-key-only endpoints
const isApiKeyAuth = (authMethod: string): boolean =>
  authMethod === 'api-key';

// Ensure the Authorization header contains an API key, not an OAuth token
const ensureApiKeyAuth = (headers: Record<string, string>): void => {
  const auth = headers['Authorization'] ?? headers['authorization'];
  if (!auth?.startsWith('Bearer cal_')) {
    throw new Error('This endpoint requires API key auth (Bearer cal_<key>). Current auth is not an API key.');
  }
};

Type guard

type ApiKeyAuthHeaders = { Authorization: `Bearer cal_${string}` };
const hasApiKeyAuth = (h: Record<string, string>): h is ApiKeyAuthHeaders =>
  h['Authorization']?.startsWith('Bearer cal_') ?? false;

Prevention

When it happens

Trigger: Calling an API-key management endpoint (like DELETE /api-keys or POST /api-keys/refresh) while authenticated with an OAuth access token instead of an API key. The route is protected by ApiAuthGuard which sets authMethod based on which strategy succeeded, but the downstream service checks that the method is specifically API_KEY.

Common situations: A frontend app that uses OAuth login trying to manage API keys without first generating one. Swagger/OpenAPI UI using a bearer access token instead of an API key. Confusion between the OAuth access token flow and the API key flow in client code.

Related errors


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