calcom/cal.diy · error · UnauthorizedException

ApiKeysService - provided api key is not valid.

Error message

ApiKeysService - provided api key is not valid.

What it means

Thrown by ApiKeysService.refreshApiKey when the SHA-256 hash of the stripped API key does not match any record in the apiKey table via apiKeysRepository.getApiKeyFromHash. The refresh endpoint requires the caller to provide their existing valid API key, and if that key cannot be found (deleted, expired, wrong prefix, or incorrect), the refresh is rejected with UnauthorizedException (HTTP 401).

Source

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

        },
      },
      input: {
        note: createApiKeyInput.note,
        neverExpires: !!createApiKeyInput.apiKeyNeverExpires,
        expiresAt: apiKeyExpiresAt,
        teamId: createApiKeyInput.teamId,
      },
    });

    return apiKey;
  }

  async refreshApiKey(authUserId: number, apiKey: string, refreshApiKeyInput: RefreshApiKeyInput) {
    const strippedApiKey = stripApiKey(apiKey, this.config.get<string>("api.keyPrefix"));
    const apiKeyHash = sha256Hash(strippedApiKey);
    const apiKeyInDb = await this.apiKeysRepository.getApiKeyFromHash(apiKeyHash);
    if (!apiKeyInDb) {
      throw new UnauthorizedException("ApiKeysService - provided api key is not valid.");
    }

    const newApiKey = await this.createApiKey(authUserId, {
      ...refreshApiKeyInput,
      note: apiKeyInDb.note || undefined,
      teamId: apiKeyInDb.teamId || undefined,
    });

    await this.apiKeysRepository.deleteById(apiKeyInDb.id);

    return newApiKey;
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Verify the API key is the current active key (not one that was already refreshed or deleted).
  2. Confirm the api.keyPrefix config value matches between the client and server so stripApiKey removes the correct prefix.
  3. If the key is lost or deleted, create a new one via POST /v2/api-keys instead of attempting to refresh.
  4. Check for trailing whitespace or newlines in the key value, which would produce a different SHA-256 hash.

Example fix

// before: attempting to refresh an already-refreshed key
await client.post('/v2/api-keys/refresh', {
  body: { apiKey: oldKeyAlreadyDeleted }
});

// after: use the current key, or create a new one if lost
try {
  await client.post('/v2/api-keys/refresh', {
    body: { apiKey: currentActiveKey }
  });
} catch (e) {
  if (e.statusCode === 401) {
    // key is invalid; create a fresh one
    const { apiKey } = await client.post('/v2/api-keys', { body: { note: 'replacement' } });
    return apiKey;
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before refreshing, verify the key format matches the expected prefix
const canRefreshKey = (key: string, prefix: string): boolean => {
  return key.startsWith(prefix) && key.length > prefix.length + 5;
};
if (!canRefreshKey(currentKey, 'cal_')) {
  throw new Error('Current API key is malformed; create a new key instead of refreshing.');
}

Try / catch

// Handle refresh failure by falling back to key creation
const refreshOrCreate = async (client: ApiClient, currentKey: string): Promise<string> => {
  try {
    const { apiKey } = await client.post('/v2/api-keys/refresh', {
      body: { apiKey: currentKey }
    });
    return apiKey;
  } catch (err: any) {
    if (err?.response?.status === 401) {
      // Key is invalid or already refreshed; create a new one
      const { apiKey } = await client.post('/v2/api-keys', {
        body: { note: 'Replacement key' }
      });
      return apiKey;
    }
    throw err;
  }
};

Prevention

When it happens

Trigger: Calling POST /v2/api-keys/refresh with a key that was already refreshed (the old key is deleted at line 76 during refresh). Using a key from a different environment (dev key against prod API). The API_KEY_PREFIX mismatch causing stripApiKey to remove the wrong prefix, producing a different hash than what's stored.

Common situations: Attempting to refresh an already-refreshed key (the old key is immediately deleted). Copying a key with truncation or extra characters. Environment mismatch: key created in staging but used against production. The key expired and was cleaned up by a background job.

Related errors


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