diff --git a/app/services/ml/find_or_create_model_version_service.rb b/app/services/ml/find_or_create_model_version_service.rb
index 1c6f5bb96dd605d22a58649aa02c7c55e9f939d7..617821667269d8aaa40abcd1711dce2c4eef81c5 100644
--- a/app/services/ml/find_or_create_model_version_service.rb
+++ b/app/services/ml/find_or_create_model_version_service.rb
@@ -8,21 +8,20 @@ def initialize(project, params = {})
       @version = params[:version]
       @package = params[:package]
       @description = params[:description]
+      @user = params[:user]
+      @params = params
     end
 
     def execute
-      model = Ml::FindOrCreateModelService.new(@project, @name).execute
+      model_version = Ml::ModelVersion.by_project_id_name_and_version(@project.id, @name, @version)
 
-      model_version = Ml::ModelVersion.find_or_create!(model, @version, @package, @description)
+      return model_version if model_version
 
-      unless model_version.candidate
-        model_version.candidate = ::Ml::CreateCandidateService.new(
-          model.default_experiment,
-          { model_version: model_version }
-        ).execute
-      end
+      model = Ml::Model.by_project_id_and_name(@project.id, @name)
 
-      model_version
+      return unless model
+
+      Ml::CreateModelVersionService.new(model, @params).execute
     end
   end
 end
diff --git a/app/services/packages/ml_model/create_package_file_service.rb b/app/services/packages/ml_model/create_package_file_service.rb
index ff569a8eecf71f463bfa69a03aae466557700d20..ee2f3077e4c70dc5e67148e0e1a1f2987e62ee39 100644
--- a/app/services/packages/ml_model/create_package_file_service.rb
+++ b/app/services/packages/ml_model/create_package_file_service.rb
@@ -4,47 +4,27 @@ module Packages
   module MlModel
     class CreatePackageFileService < BaseService
       def execute
-        ::Packages::Package.transaction do
-          package = find_or_create_package
-          find_or_create_model_version(package)
+        @package = params[:model_version]&.package
+
+        return unless @package
 
-          create_package_file(package)
+        ::Packages::Package.transaction do
+          update_package
+          create_package_file
         end
       end
 
       private
 
-      def find_or_create_package
-        package_params = {
-          name: params[:package_name],
-          version: params[:package_version],
-          build: params[:build],
-          status: params[:status]
-        }
-
-        package = ::Packages::MlModel::FindOrCreatePackageService
-                    .new(project, current_user, package_params)
-                    .execute
+      attr_reader :package
 
+      def update_package
         package.update_column(:status, params[:status]) if params[:status] && params[:status] != package.status
 
         package.create_build_infos!(params[:build])
-
-        package
-      end
-
-      def find_or_create_model_version(package)
-        model_version_params = {
-          model_name: package.name,
-          version: package.version,
-          package: package,
-          user: current_user
-        }
-
-        Ml::FindOrCreateModelVersionService.new(project, model_version_params).execute
       end
 
-      def create_package_file(package)
+      def create_package_file
         file_params = {
           file: params[:file],
           size: params[:file].size,
diff --git a/lib/api/ml_model_packages.rb b/lib/api/ml_model_packages.rb
index 8a7a8fc9525e4d11e9e903d4f734e204094f04d9..85c8146dda854a468bb2eaf2c6ea622946f66b88 100644
--- a/lib/api/ml_model_packages.rb
+++ b/lib/api/ml_model_packages.rb
@@ -23,8 +23,8 @@ class MlModelPackages < ::API::Base
     end
 
     authenticate_with do |accept|
-      accept.token_types(:personal_access_token, :deploy_token, :job_token)
-            .sent_through(:http_token)
+      accept.token_types(:personal_access_token, :job_token)
+            .sent_through(:http_bearer_token)
     end
 
     helpers do
@@ -38,6 +38,15 @@ def project
       def max_file_size_exceeded?
         project.actual_limits.exceeded?(:ml_model_max_file_size, params[:file].size)
       end
+
+      def find_model_version!
+        ::Ml::ModelVersion.by_project_id_name_and_version(project.id, params[:model_name], params[:model_version]) ||
+          not_found!
+      end
+
+      def model_version
+        @model_version ||= find_model_version!
+      end
     end
 
     params do
@@ -88,10 +97,12 @@ def max_file_size_exceeded?
           end
           put do
             authorize_upload!(project)
+            not_found! unless can?(current_user, :write_model_registry, project)
 
             bad_request!('File is too large') if max_file_size_exceeded?
 
             create_package_file_params = declared(params).merge(
+              model_version: model_version,
               build: current_authenticated_job,
               package_name: params[:model_name],
               package_version: params[:model_version]
@@ -123,9 +134,7 @@ def max_file_size_exceeded?
           get do
             authorize_read_package!(project)
 
-            package = ::Packages::MlModel::PackageFinder.new(project)
-                                                        .execute!(params[:model_name], params[:model_version])
-            package_file = ::Packages::PackageFileFinder.new(package, params[:file_name]).execute!
+            package_file = ::Packages::PackageFileFinder.new(model_version.package, params[:file_name]).execute!
 
             present_package_file!(package_file)
           end
diff --git a/spec/requests/api/ml_model_packages_spec.rb b/spec/requests/api/ml_model_packages_spec.rb
index 3166298b430ccd584bfc88ff65387392c9e234aa..894127cac780c653af55387f6549813418c77693 100644
--- a/spec/requests/api/ml_model_packages_spec.rb
+++ b/spec/requests/api/ml_model_packages_spec.rb
@@ -16,6 +16,8 @@
   let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
   let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
   let_it_be(:another_project, reload: true) { create(:project) }
+  let_it_be(:model) { create(:ml_models, user: project.owner, project: project) }
+  let_it_be(:model_version) { create(:ml_model_versions, :with_package, model: model, version: '0.1.0') }
 
   let_it_be(:tokens) do
     {
@@ -70,10 +72,6 @@ def authorize_permissions_table
       :private | :guest      | false | :job_token             | true  | :not_found
       :private | :developer  | false | :job_token             | false | :unauthorized
       :private | :guest      | false | :job_token             | false | :unauthorized
-      :public  | :developer  | true  | :deploy_token          | true  | :success
-      :public  | :developer  | true  | :deploy_token          | false | :unauthorized
-      :private | :developer  | true  | :deploy_token          | true  | :success
-      :private | :developer  | true  | :deploy_token          | false | :unauthorized
     end
 
     # :visibility, :user_role, :member, :token_type, :valid_token, :expected_status
@@ -112,10 +110,6 @@ def download_permissions_tables
       :private | :guest      | false | :job_token             | true  |  :not_found
       :private | :developer  | false | :job_token             | false |  :unauthorized
       :private | :guest      | false | :job_token             | false |  :unauthorized
-      :public  | :developer  | true  | :deploy_token          | true  |  :success
-      :public  | :developer  | true  | :deploy_token          | false |  :unauthorized
-      :private | :developer  | true  | :deploy_token          | true  |  :success
-      :private | :developer  | true  | :deploy_token          | false |  :unauthorized
     end
     # rubocop:enable Metrics/AbcSize
   end
@@ -128,14 +122,15 @@ def download_permissions_tables
     include_context 'ml model authorize permissions table'
 
     let(:token) { tokens[:personal_access_token] }
-    let(:user_headers) { { 'HTTP_AUTHORIZATION' => token } }
+    let(:user_headers) { { 'Authorization' => "Bearer #{token}" } }
     let(:headers) { user_headers.merge(workhorse_headers) }
     let(:request) { authorize_upload_file(headers) }
-    let(:model_name) { 'my_package' }
+    let(:model_name) { model_version.name }
+    let(:version) { model_version.version }
     let(:file_name) { 'myfile.tar.gz' }
 
     subject(:api_response) do
-      url = "/projects/#{project.id}/packages/ml_models/#{model_name}/0.0.1/#{file_name}/authorize"
+      url = "/projects/#{project.id}/packages/ml_models/#{model_name}/#{version}/#{file_name}/authorize"
 
       put api(url), headers: headers
 
@@ -149,7 +144,7 @@ def download_permissions_tables
 
       with_them do
         let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' }
-        let(:user_headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } }
+        let(:user_headers) { user_role == :anonymous ? {} : { 'Authorization' => "Bearer #{token}" } }
 
         before do
           project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility.to_s))
@@ -183,15 +178,16 @@ def download_permissions_tables
     let_it_be(:file_name) { 'model.md5' }
 
     let(:token) { tokens[:personal_access_token] }
-    let(:user_headers) { { 'HTTP_AUTHORIZATION' => token } }
+    let(:user_headers) { { 'Authorization' => "Bearer #{token}" } }
     let(:headers) { user_headers.merge(workhorse_headers) }
     let(:params) { { file: temp_file(file_name) } }
     let(:file_key) { :file }
     let(:send_rewritten_field) { true }
-    let(:model_name) { 'my_package' }
+    let(:model_name) { model_version.name }
+    let(:version) { model_version.version }
 
     subject(:api_response) do
-      url = "/projects/#{project.id}/packages/ml_models/#{model_name}/0.0.1/#{file_name}"
+      url = "/projects/#{project.id}/packages/ml_models/#{model_name}/#{version}/#{file_name}"
 
       workhorse_finalize(
         api(url),
@@ -219,7 +215,7 @@ def download_permissions_tables
 
       with_them do
         let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' }
-        let(:user_headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } }
+        let(:user_headers) { user_role == :anonymous ? {} : { 'Authorization' => "Bearer #{token}" } }
 
         before do
           project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility.to_s))
@@ -233,25 +229,27 @@ def download_permissions_tables
       end
 
       it_behaves_like 'Endpoint not found if read_model_registry not available'
+      it_behaves_like 'Endpoint not found if write_model_registry not available'
+      it_behaves_like 'Not found when model version does not exist'
     end
   end
 
   describe 'GET /api/v4/projects/:project_id/packages/ml_models/:model_name/:model_version/:file_name' do
     include_context 'ml model authorize permissions table'
 
-    let_it_be(:package) { create(:ml_model_package, project: project, name: 'model', version: '0.0.1') }
+    let_it_be(:package) { model_version.package }
     let_it_be(:package_file) { create(:package_file, :generic, package: package, file_name: 'model.md5') }
 
-    let(:model_name) { package.name }
-    let(:model_version) { package.version }
+    let(:model_name) { model_version.name }
+    let(:version) { model_version.version }
     let(:file_name) { package_file.file_name }
 
     let(:token) { tokens[:personal_access_token] }
-    let(:user_headers) { { 'HTTP_AUTHORIZATION' => token } }
+    let(:user_headers) { { 'Authorization' => "Bearer #{token}" } }
     let(:headers) { user_headers.merge(workhorse_headers) }
 
     subject(:api_response) do
-      url = "/projects/#{project.id}/packages/ml_models/#{model_name}/#{model_version}/#{file_name}"
+      url = "/projects/#{project.id}/packages/ml_models/#{model_name}/#{version}/#{file_name}"
 
       get api(url), headers: headers
 
@@ -265,7 +263,7 @@ def download_permissions_tables
 
       with_them do
         let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' }
-        let(:user_headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } }
+        let(:user_headers) { user_role == :anonymous ? {} : { 'Authorization' => "Bearer #{token}" } }
 
         before do
           project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility.to_s))
diff --git a/spec/services/ml/find_or_create_model_version_service_spec.rb b/spec/services/ml/find_or_create_model_version_service_spec.rb
index e5ca7c3a450df44d569649df6f4a71b973645f1a..88647f23ad91a4b3a49199bbb34081f3c739a870 100644
--- a/spec/services/ml/find_or_create_model_version_service_spec.rb
+++ b/spec/services/ml/find_or_create_model_version_service_spec.rb
@@ -35,21 +35,29 @@
       end
     end
 
-    context 'when model version does not exist' do
+    context 'when model does not exist' do
       let(:project) { existing_version.project }
       let(:name) { 'a_new_model' }
       let(:version) { '2.0.0' }
+
+      it 'does not create a new model version', :aggregate_failures do
+        expect { model_version }.to change { Ml::ModelVersion.count }.by(0)
+      end
+    end
+
+    context 'when model exists and model version does not' do
+      let(:project) { existing_version.project }
+      let(:name) { existing_version.name }
+      let(:version) { '2.0.0' }
       let(:description) { 'A model version' }
 
       let(:package) { create(:ml_model_package, project: project, name: name, version: version) }
 
       it 'creates a new model version', :aggregate_failures do
-        expect { model_version }.to change { Ml::ModelVersion.count }.by(1).and change { Ml::Candidate.count }.by(1)
+        expect { model_version }.to change { Ml::ModelVersion.count }.by(1)
 
-        expect(model_version.name).to eq(name)
         expect(model_version.version).to eq(version)
-        expect(model_version.package).to eq(package)
-        expect(model_version.candidate.model_version_id).to eq(model_version.id)
+        expect(model_version.model).to eq(existing_version.model)
         expect(model_version.description).to eq(description)
       end
     end
diff --git a/spec/services/packages/ml_model/create_package_file_service_spec.rb b/spec/services/packages/ml_model/create_package_file_service_spec.rb
index 30a6bedd07b23aa0d9dd504cad4d053584a194ba..505c80389769c78989ced7ba46e6febf91aba2bc 100644
--- a/spec/services/packages/ml_model/create_package_file_service_spec.rb
+++ b/spec/services/packages/ml_model/create_package_file_service_spec.rb
@@ -8,6 +8,8 @@
     let_it_be(:user) { create(:user) }
     let_it_be(:pipeline) { create(:ci_pipeline, user: user, project: project) }
     let_it_be(:file_name) { 'myfile.tar.gz.1' }
+    let_it_be(:model) { create(:ml_models, user: user, project: project) }
+    let_it_be(:model_version) { create(:ml_model_versions, :with_package, model: model, version: '0.1.0') }
 
     let(:build) { instance_double(Ci::Build, pipeline: pipeline) }
 
@@ -26,47 +28,24 @@
       FileUtils.rm_f(temp_file)
     end
 
-    context 'without existing package' do
+    context 'when model version is nil' do
       let(:params) do
         {
-          package_name: 'new_model',
-          package_version: '1.0.0',
+          model_version: nil,
           file: file,
           file_name: file_name
         }
       end
 
-      it 'creates package file', :aggregate_failures do
-        expect { execute_service }
-          .to change { Packages::MlModel::Package.count }.by(1)
-          .and change { Packages::PackageFile.count }.by(1)
-          .and change { Packages::PackageFileBuildInfo.count }.by(0)
-          .and change { Ml::ModelVersion.count }.by(1)
-
-        new_model = Packages::MlModel::Package.last
-        package_file = new_model.package_files.last
-        new_model_version = Ml::ModelVersion.last
-
-        expect(new_model.name).to eq('new_model')
-        expect(new_model.version).to eq('1.0.0')
-        expect(new_model.status).to eq('default')
-        expect(package_file.package).to eq(new_model)
-        expect(package_file.file_name).to eq(file_name)
-        expect(package_file.size).to eq(file.size)
-        expect(package_file.file_sha256).to eq(sha256)
-        expect(new_model_version.name).to eq('new_model')
-        expect(new_model_version.version).to eq('1.0.0')
-        expect(new_model_version.package).to eq(new_model)
+      it 'does not create package file', :aggregate_failures do
+        expect(execute_service).to be(nil)
       end
     end
 
-    context 'with existing package' do
-      let_it_be(:model) { create(:ml_model_package, creator: user, project: project, version: '0.1.0') }
-
+    context 'with existing model version' do
       let(:params) do
         {
-          package_name: model.name,
-          package_version: model.version,
+          model_version: model_version,
           file: file,
           file_name: file_name,
           status: :hidden,
@@ -76,18 +55,16 @@
 
       it 'adds the package file and updates status and ci_build', :aggregate_failures do
         expect { execute_service }
-          .to change { project.packages.ml_model.count }.by(0)
-          .and change { model.package_files.count }.by(1)
+          .to change { model_version.package.package_files.count }.by(1)
           .and change { Packages::PackageFileBuildInfo.count }.by(1)
 
-        model.reload
-
-        package_file = model.package_files.last
+        package = model_version.reload.package
+        package_file = package.package_files.last
 
-        expect(model.build_infos.first.pipeline).to eq(build.pipeline)
-        expect(model.status).to eq('hidden')
+        expect(package.build_infos.first.pipeline).to eq(build.pipeline)
+        expect(package.status).to eq('hidden')
 
-        expect(package_file.package).to eq(model)
+        expect(package_file.package).to eq(package)
         expect(package_file.file_name).to eq(file_name)
         expect(package_file.size).to eq(file.size)
         expect(package_file.file_sha256).to eq(sha256)
diff --git a/spec/support/shared_examples/requests/api/ml_model_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/ml_model_packages_shared_examples.rb
index 47cbd268a650edd2e50c5add36a5fc5062a713ac..30a1398bf94ed4390d6202125cf75e269a801f7b 100644
--- a/spec/support/shared_examples/requests/api/ml_model_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/ml_model_packages_shared_examples.rb
@@ -15,11 +15,31 @@
   end
 end
 
-RSpec.shared_examples 'creates model experiments package files' do
+RSpec.shared_examples 'Endpoint not found if write_model_registry not available' do
+  context 'when write_model_registry is disabled for current project' do
+    before do
+      allow(Ability).to receive(:allowed?).and_call_original
+      allow(Ability).to receive(:allowed?)
+                          .with(user, :write_model_registry, project)
+                          .and_return(false)
+    end
+
+    it { is_expected.to have_gitlab_http_status(:not_found) }
+  end
+end
+
+RSpec.shared_examples 'Not found when model version does not exist' do
+  context 'when model version does not exist' do
+    let(:version) { "#{non_existing_record_id}.0.0" }
+
+    it { is_expected.to have_gitlab_http_status(:not_found) }
+  end
+end
+
+RSpec.shared_examples 'creates package files for model versions' do
   it 'creates package files', :aggregate_failures do
     expect { api_response }
-      .to change { project.packages.count }.by(1)
-                                           .and change { Packages::PackageFile.count }.by(1)
+      .to change { Packages::PackageFile.count }.by(1)
     expect(api_response).to have_gitlab_http_status(:created)
 
     package_file = project.packages.last.package_files.reload.last
@@ -59,7 +79,7 @@
 
     context 'with correct params' do
       it_behaves_like 'package workhorse uploads'
-      it_behaves_like 'creates model experiments package files'
+      it_behaves_like 'creates package files for model versions'
       # To be reactivated with https://gitlab.com/gitlab-org/gitlab/-/issues/414270
       # it_behaves_like 'a package tracking event', '::API::MlModelPackages', 'push_package'
     end
@@ -81,7 +101,7 @@
         stub_package_file_object_storage(direct_upload: true)
       end
 
-      it_behaves_like 'creates model experiments package files'
+      it_behaves_like 'creates package files for model versions'
 
       ['123123', '../../123123'].each do |remote_id|
         context "with invalid remote_id: #{remote_id}" do
@@ -102,7 +122,7 @@
         stub_package_file_object_storage(direct_upload: false)
       end
 
-      it_behaves_like 'creates model experiments package files'
+      it_behaves_like 'creates package files for model versions'
     end
   end
 end
@@ -112,13 +132,5 @@
     it { is_expected.to have_gitlab_http_status(:success) }
   end
 
-  context 'when record does not exist' do
-    it 'response is not found' do
-      expect_next_instance_of(::Packages::MlModel::PackageFinder) do |instance|
-        expect(instance).to receive(:execute!).and_raise(ActiveRecord::RecordNotFound)
-      end
-
-      expect(api_response).to have_gitlab_http_status(:not_found)
-    end
-  end
+  it_behaves_like 'Not found when model version does not exist'
 end