diff --git a/ee/lib/gitlab_subscriptions/api/internal/upcoming_reconciliations.rb b/ee/lib/gitlab_subscriptions/api/internal/upcoming_reconciliations.rb
index 213b2ed9490cb290b0e4ef81462334a1763f4a6e..60a5b21bf95e648fb271b3759d7c3f525b0c2238 100644
--- a/ee/lib/gitlab_subscriptions/api/internal/upcoming_reconciliations.rb
+++ b/ee/lib/gitlab_subscriptions/api/internal/upcoming_reconciliations.rb
@@ -6,6 +6,9 @@ module Internal
       class UpcomingReconciliations < ::API::Base
         before do
           forbidden!('This API is gitlab.com only!') unless ::Gitlab::Saas.feature_available?(:gitlab_com_subscriptions)
+
+          @namespace = find_namespace(params[:namespace_id])
+          not_found!('Namespace') unless @namespace.present?
         end
 
         feature_category :subscription_management
@@ -24,21 +27,24 @@ class UpcomingReconciliations < ::API::Base
                   requires :display_alert_from, type: Date
                 end
                 put '/' do
-                  upcoming_reconciliations = [
-                    {
-                      namespace_id: params[:namespace_id],
-                      next_reconciliation_date: params[:next_reconciliation_date],
-                      display_alert_from: params[:display_alert_from]
-                    }
-                  ]
-                  service = ::UpcomingReconciliations::UpdateService.new(upcoming_reconciliations)
-                  response = service.execute
-
-                  if response.success?
-                    status 200
+                  attributes = {
+                    next_reconciliation_date: params[:next_reconciliation_date],
+                    display_alert_from: params[:display_alert_from]
+                  }
+
+                  reconciliation = GitlabSubscriptions::UpcomingReconciliation.next(@namespace.id)
+
+                  if reconciliation
+                    reconciliation.update!(attributes)
                   else
-                    render_api_error!({ error: response.errors.first }, 500)
+                    GitlabSubscriptions::UpcomingReconciliation.create!(
+                      attributes.merge({ namespace: @namespace, organization: @namespace.organization })
+                    )
                   end
+
+                  status 200
+                rescue ActiveRecord::RecordInvalid => e
+                  render_api_error!({ error: e.record.errors.full_messages.join(', ') }, 500)
                 end
 
                 desc 'Destroy upcoming reconciliation record'
diff --git a/ee/spec/requests/gitlab_subscriptions/api/internal/upcoming_reconciliations_spec.rb b/ee/spec/requests/gitlab_subscriptions/api/internal/upcoming_reconciliations_spec.rb
index fada96d84f4e7b8412653f54e51a1dfe899e64e1..0cf2aa169a534fe727866c7fbdfe329e54bc6140 100644
--- a/ee/spec/requests/gitlab_subscriptions/api/internal/upcoming_reconciliations_spec.rb
+++ b/ee/spec/requests/gitlab_subscriptions/api/internal/upcoming_reconciliations_spec.rb
@@ -30,22 +30,45 @@ def upcoming_reconciliations_path(namespace_id)
         stub_internal_api_authentication
       end
 
-      context 'when supplied valid params' do
-        it 'updates the upcoming reconciliation' do
-          params = {
-            next_reconciliation_date: Date.today + 5.days,
-            display_alert_from: Date.today - 2.days
-          }
+      context 'when supplied with valid params' do
+        context 'when upcoming reconciliation does not exist for namespace' do
+          it 'creates new upcoming reconciliation' do
+            params = {
+              next_reconciliation_date: Date.today + 5.days,
+              display_alert_from: Date.today - 2.days
+            }
 
-          expect { put upcoming_reconciliations_path(namespace.id), headers: internal_api_headers, params: params }
-            .to change { namespace.reload.upcoming_reconciliation }
-            .to be_present
+            expect { put upcoming_reconciliations_path(namespace.id), headers: internal_api_headers, params: params }
+              .to change { namespace.reload.upcoming_reconciliation }.from(nil).to be_present
 
-          expect(response).to have_gitlab_http_status(:ok)
+            expect(response).to have_gitlab_http_status(:ok)
+          end
+        end
+
+        context 'when upcoming reconciliation exists for namespace' do
+          it 'updates the existing upcoming reconciliation' do
+            create(:upcoming_reconciliation, :saas, namespace: namespace)
+
+            expected_next_reconciliation_date = Date.today + 5.days
+            expected_display_alert_from_date = Date.today + 2.days
+
+            params = {
+              next_reconciliation_date: expected_next_reconciliation_date,
+              display_alert_from: expected_display_alert_from_date
+            }
+
+            expect { put upcoming_reconciliations_path(namespace.id), headers: internal_api_headers, params: params }
+              .not_to change { namespace.reload.upcoming_reconciliation }
+
+            expect(namespace.upcoming_reconciliation.next_reconciliation_date).to eq(expected_next_reconciliation_date)
+            expect(namespace.upcoming_reconciliation.display_alert_from).to eq(expected_display_alert_from_date)
+
+            expect(response).to have_gitlab_http_status(:ok)
+          end
         end
       end
 
-      context 'when supplied invalid params' do
+      context 'when supplied with invalid params' do
         it 'returns an error' do
           params = {
             next_reconciliation_date: nil,
@@ -57,6 +80,20 @@ def upcoming_reconciliations_path(namespace_id)
           expect(response).to have_gitlab_http_status(:internal_server_error)
           expect(json_response['message']['error']).to include "Next reconciliation date can't be blank"
         end
+
+        context 'when namespace does not exist' do
+          it 'returns namespace not found error' do
+            params = {
+              next_reconciliation_date: Date.today + 5.days,
+              display_alert_from: Date.today - 2.days
+            }
+
+            put upcoming_reconciliations_path(-1), headers: internal_api_headers, params: params
+
+            expect(response).to have_gitlab_http_status(:not_found)
+            expect(json_response['message']).to eq('404 Namespace Not Found')
+          end
+        end
       end
     end
 
@@ -107,19 +144,18 @@ def upcoming_reconciliations_path(namespace_id)
         end
 
         context 'when update service failed' do
-          let(:error_message) { 'update_service_error' }
-
-          before do
-            allow_next_instance_of(::UpcomingReconciliations::UpdateService) do |service|
-              allow(service).to receive(:execute).and_return(ServiceResponse.error(message: error_message))
-            end
+          let(:params) do
+            {
+              next_reconciliation_date: nil,
+              display_alert_from: Date.today - 2.days
+            }
           end
 
           it 'returns error' do
             put_upcoming_reconciliations
 
             expect(response).to have_gitlab_http_status(:internal_server_error)
-            expect(json_response.dig('message', 'error')).to eq(error_message)
+            expect(json_response['message']['error']).to include "Next reconciliation date can't be blank"
           end
         end
 
@@ -171,6 +207,15 @@ def upcoming_reconciliations_path(namespace_id)
         end
       end
 
+      context 'when namespace does not exist' do
+        it 'returns namespace not found error' do
+          delete upcoming_reconciliations_path(-1), headers: internal_api_headers
+
+          expect(response).to have_gitlab_http_status(:not_found)
+          expect(json_response['message']).to eq('404 Namespace Not Found')
+        end
+      end
+
       context 'when the namespace_id does not have an upcoming reconciliation' do
         it 'returns a not found error' do
           expect { delete_upcoming_reconciliation }.not_to change { GitlabSubscriptions::UpcomingReconciliation.count }