diff --git a/ee/lib/api/project_mirror.rb b/ee/lib/api/project_mirror.rb
index 707ed4d4c8988e0a8318eee2959b62c58aa2d6ec..482f6fbf20ff510d7c7a0152fbb38c36264ad3ed 100644
--- a/ee/lib/api/project_mirror.rb
+++ b/ee/lib/api/project_mirror.rb
@@ -49,7 +49,7 @@ def project
       end
 
       def process_pull_request
-        external_pull_request = Ci::ExternalPullRequests::ProcessGithubEventService.new(project, mirror_user).execute(params)
+        external_pull_request = ::Ci::ExternalPullRequests::ProcessGithubEventService.new(project, mirror_user).execute(params)
 
         if external_pull_request
           render_validation_error!(external_pull_request)
diff --git a/lib/api/api.rb b/lib/api/api.rb
index fb67258f3312a9ef5013ad121356545ad3ca8787..1cdb500e6ac60aa177050a2e406eea05294b656c 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -131,6 +131,8 @@ class API < Grape::API
       mount ::API::Boards
       mount ::API::Branches
       mount ::API::BroadcastMessages
+      mount ::API::Ci::Runner
+      mount ::API::Ci::Runners
       mount ::API::Commits
       mount ::API::CommitStatuses
       mount ::API::ContainerRegistryEvent
@@ -195,8 +197,6 @@ class API < Grape::API
       mount ::API::Release::Links
       mount ::API::RemoteMirrors
       mount ::API::Repositories
-      mount ::API::Runner
-      mount ::API::Runners
       mount ::API::Search
       mount ::API::Services
       mount ::API::Settings
diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3116af24ff5d42b2f01c4d8f668f0f153700f530
--- /dev/null
+++ b/lib/api/ci/runner.rb
@@ -0,0 +1,299 @@
+# frozen_string_literal: true
+
+module API
+  module Ci
+    class Runner < Grape::API
+      helpers ::API::Helpers::Runner
+
+      resource :runners do
+        desc 'Registers a new Runner' do
+          success Entities::RunnerRegistrationDetails
+          http_codes [[201, 'Runner was created'], [403, 'Forbidden']]
+        end
+        params do
+          requires :token, type: String, desc: 'Registration token'
+          optional :description, type: String, desc: %q(Runner's description)
+          optional :info, type: Hash, desc: %q(Runner's metadata)
+          optional :active, type: Boolean, desc: 'Should Runner be active'
+          optional :locked, type: Boolean, desc: 'Should Runner be locked for current project'
+          optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys,
+                                  desc: 'The access_level of the runner'
+          optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs'
+          optional :tag_list, type: Array[String], desc: %q(List of Runner's tags)
+          optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
+        end
+        post '/' do
+          attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout])
+            .merge(get_runner_details_from_request)
+
+          attributes =
+            if runner_registration_token_valid?
+              # Create shared runner. Requires admin access
+              attributes.merge(runner_type: :instance_type)
+            elsif project = Project.find_by_runners_token(params[:token])
+              # Create a specific runner for the project
+              attributes.merge(runner_type: :project_type, projects: [project])
+            elsif group = Group.find_by_runners_token(params[:token])
+              # Create a specific runner for the group
+              attributes.merge(runner_type: :group_type, groups: [group])
+            else
+              forbidden!
+            end
+
+          runner = ::Ci::Runner.create(attributes)
+
+          if runner.persisted?
+            present runner, with: Entities::RunnerRegistrationDetails
+          else
+            render_validation_error!(runner)
+          end
+        end
+
+        desc 'Deletes a registered Runner' do
+          http_codes [[204, 'Runner was deleted'], [403, 'Forbidden']]
+        end
+        params do
+          requires :token, type: String, desc: %q(Runner's authentication token)
+        end
+        delete '/' do
+          authenticate_runner!
+
+          runner = ::Ci::Runner.find_by_token(params[:token])
+
+          destroy_conditionally!(runner)
+        end
+
+        desc 'Validates authentication credentials' do
+          http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']]
+        end
+        params do
+          requires :token, type: String, desc: %q(Runner's authentication token)
+        end
+        post '/verify' do
+          authenticate_runner!
+          status 200
+        end
+      end
+
+      resource :jobs do
+        before do
+          Gitlab::ApplicationContext.push(
+            user: -> { current_job&.user },
+            project: -> { current_job&.project }
+          )
+        end
+
+        desc 'Request a job' do
+          success Entities::JobRequest::Response
+          http_codes [[201, 'Job was scheduled'],
+                      [204, 'No job for Runner'],
+                      [403, 'Forbidden']]
+        end
+        params do
+          requires :token, type: String, desc: %q(Runner's authentication token)
+          optional :last_update, type: String, desc: %q(Runner's queue last_update token)
+          optional :info, type: Hash, desc: %q(Runner's metadata) do
+            optional :name, type: String, desc: %q(Runner's name)
+            optional :version, type: String, desc: %q(Runner's version)
+            optional :revision, type: String, desc: %q(Runner's revision)
+            optional :platform, type: String, desc: %q(Runner's platform)
+            optional :architecture, type: String, desc: %q(Runner's architecture)
+            optional :executor, type: String, desc: %q(Runner's executor)
+            optional :features, type: Hash, desc: %q(Runner's features)
+          end
+          optional :session, type: Hash, desc: %q(Runner's session data) do
+            optional :url, type: String, desc: %q(Session's url)
+            optional :certificate, type: String, desc: %q(Session's certificate)
+            optional :authorization, type: String, desc: %q(Session's authorization)
+          end
+          optional :job_age, type: Integer, desc: %q(Job should be older than passed age in seconds to be ran on runner)
+        end
+        post '/request' do
+          authenticate_runner!
+
+          unless current_runner.active?
+            header 'X-GitLab-Last-Update', current_runner.ensure_runner_queue_value
+            break no_content!
+          end
+
+          runner_params = declared_params(include_missing: false)
+
+          if current_runner.runner_queue_value_latest?(runner_params[:last_update])
+            header 'X-GitLab-Last-Update', runner_params[:last_update]
+            Gitlab::Metrics.add_event(:build_not_found_cached)
+            break no_content!
+          end
+
+          new_update = current_runner.ensure_runner_queue_value
+          result = ::Ci::RegisterJobService.new(current_runner).execute(runner_params)
+
+          if result.valid?
+            if result.build
+              Gitlab::Metrics.add_event(:build_found)
+              present ::Ci::BuildRunnerPresenter.new(result.build), with: Entities::JobRequest::Response
+            else
+              Gitlab::Metrics.add_event(:build_not_found)
+              header 'X-GitLab-Last-Update', new_update
+              no_content!
+            end
+          else
+            # We received build that is invalid due to concurrency conflict
+            Gitlab::Metrics.add_event(:build_invalid)
+            conflict!
+          end
+        end
+
+        desc 'Updates a job' do
+          http_codes [[200, 'Job was updated'], [403, 'Forbidden']]
+        end
+        params do
+          requires :token, type: String, desc: %q(Runners's authentication token)
+          requires :id, type: Integer, desc: %q(Job's ID)
+          optional :trace, type: String, desc: %q(Job's full trace)
+          optional :state, type: String, desc: %q(Job's status: success, failed)
+          optional :failure_reason, type: String, desc: %q(Job's failure_reason)
+        end
+        put '/:id' do
+          job = authenticate_job!
+
+          job.trace.set(params[:trace]) if params[:trace]
+
+          Gitlab::Metrics.add_event(:update_build)
+
+          case params[:state].to_s
+          when 'running'
+            job.touch if job.needs_touch?
+          when 'success'
+            job.success!
+          when 'failed'
+            job.drop!(params[:failure_reason] || :unknown_failure)
+          end
+        end
+
+        desc 'Appends a patch to the job trace' do
+          http_codes [[202, 'Trace was patched'],
+                      [400, 'Missing Content-Range header'],
+                      [403, 'Forbidden'],
+                      [416, 'Range not satisfiable']]
+        end
+        params do
+          requires :id, type: Integer, desc: %q(Job's ID)
+          optional :token, type: String, desc: %q(Job's authentication token)
+        end
+        patch '/:id/trace' do
+          job = authenticate_job!
+
+          error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range')
+          content_range = request.headers['Content-Range']
+          content_range = content_range.split('-')
+
+          # TODO:
+          # it seems that `Content-Range` as formatted by runner is wrong,
+          # the `byte_end` should point to final byte, but it points byte+1
+          # that means that we have to calculate end of body,
+          # as we cannot use `content_length[1]`
+          # Issue: https://gitlab.com/gitlab-org/gitlab-runner/issues/3275
+
+          body_data = request.body.read
+          body_start = content_range[0].to_i
+          body_end = body_start + body_data.bytesize
+
+          stream_size = job.trace.append(body_data, body_start)
+          unless stream_size == body_end
+            break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" })
+          end
+
+          status 202
+          header 'Job-Status', job.status
+          header 'Range', "0-#{stream_size}"
+          header 'X-GitLab-Trace-Update-Interval', job.trace.update_interval.to_s
+        end
+
+        desc 'Authorize artifacts uploading for job' do
+          http_codes [[200, 'Upload allowed'],
+                      [403, 'Forbidden'],
+                      [405, 'Artifacts support not enabled'],
+                      [413, 'File too large']]
+        end
+        params do
+          requires :id, type: Integer, desc: %q(Job's ID)
+          optional :token, type: String, desc: %q(Job's authentication token)
+          optional :filesize, type: Integer, desc: %q(Artifacts filesize)
+          optional :artifact_type, type: String, desc: %q(The type of artifact),
+                                  default: 'archive', values: ::Ci::JobArtifact.file_types.keys
+        end
+        post '/:id/artifacts/authorize' do
+          not_allowed! unless Gitlab.config.artifacts.enabled
+          require_gitlab_workhorse!
+          Gitlab::Workhorse.verify_api_request!(headers)
+
+          job = authenticate_job!
+
+          service = ::Ci::AuthorizeJobArtifactService.new(job, params, max_size: max_artifacts_size(job))
+
+          forbidden! if service.forbidden?
+          file_too_large! if service.too_large?
+
+          status 200
+          content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
+          service.headers
+        end
+
+        desc 'Upload artifacts for job' do
+          success Entities::JobRequest::Response
+          http_codes [[201, 'Artifact uploaded'],
+                      [400, 'Bad request'],
+                      [403, 'Forbidden'],
+                      [405, 'Artifacts support not enabled'],
+                      [413, 'File too large']]
+        end
+        params do
+          requires :id, type: Integer, desc: %q(Job's ID)
+          requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware))
+          optional :token, type: String, desc: %q(Job's authentication token)
+          optional :expire_in, type: String, desc: %q(Specify when artifacts should expire)
+          optional :artifact_type, type: String, desc: %q(The type of artifact),
+                                  default: 'archive', values: ::Ci::JobArtifact.file_types.keys
+          optional :artifact_format, type: String, desc: %q(The format of artifact),
+                                    default: 'zip', values: ::Ci::JobArtifact.file_formats.keys
+          optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware))
+        end
+        post '/:id/artifacts' do
+          not_allowed! unless Gitlab.config.artifacts.enabled
+          require_gitlab_workhorse!
+
+          job = authenticate_job!
+
+          artifacts = params[:file]
+          metadata = params[:metadata]
+
+          file_too_large! unless artifacts.size < max_artifacts_size(job)
+
+          result = ::Ci::CreateJobArtifactsService.new(job.project).execute(job, artifacts, params, metadata_file: metadata)
+
+          if result[:status] == :success
+            status :created
+          else
+            render_api_error!(result[:message], result[:http_status])
+          end
+        end
+
+        desc 'Download the artifacts file for job' do
+          http_codes [[200, 'Upload allowed'],
+                      [403, 'Forbidden'],
+                      [404, 'Artifact not found']]
+        end
+        params do
+          requires :id, type: Integer, desc: %q(Job's ID)
+          optional :token, type: String, desc: %q(Job's authentication token)
+          optional :direct_download, default: false, type: Boolean, desc: %q(Perform direct download from remote storage instead of proxying artifacts)
+        end
+        get '/:id/artifacts' do
+          job = authenticate_job!(require_running: false)
+
+          present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download])
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a4ef54534bac0f45708b37e8c1a8e203f222f7b9
--- /dev/null
+++ b/lib/api/ci/runners.rb
@@ -0,0 +1,289 @@
+# frozen_string_literal: true
+
+module API
+  module Ci
+    class Runners < Grape::API
+      include PaginationParams
+
+      before { authenticate! }
+
+      resource :runners do
+        desc 'Get runners available for user' do
+          success Entities::Runner
+        end
+        params do
+          optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
+                          desc: 'The scope of specific runners to show'
+          optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
+                          desc: 'The type of the runners to show'
+          optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
+                            desc: 'The status of the runners to show'
+          optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
+          use :pagination
+        end
+        get do
+          runners = current_user.ci_owned_runners
+          runners = filter_runners(runners, params[:scope], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
+          runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
+          runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
+          runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
+
+          present paginate(runners), with: Entities::Runner
+        end
+
+        desc 'Get all runners - shared and specific' do
+          success Entities::Runner
+        end
+        params do
+          optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES,
+                          desc: 'The scope of specific runners to show'
+          optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
+                          desc: 'The type of the runners to show'
+          optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
+                            desc: 'The status of the runners to show'
+          optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
+          use :pagination
+        end
+        get 'all' do
+          authenticated_as_admin!
+
+          runners = ::Ci::Runner.all
+          runners = filter_runners(runners, params[:scope])
+          runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
+          runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
+          runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
+
+          present paginate(runners), with: Entities::Runner
+        end
+
+        desc "Get runner's details" do
+          success Entities::RunnerDetails
+        end
+        params do
+          requires :id, type: Integer, desc: 'The ID of the runner'
+        end
+        get ':id' do
+          runner = get_runner(params[:id])
+          authenticate_show_runner!(runner)
+
+          present runner, with: Entities::RunnerDetails, current_user: current_user
+        end
+
+        desc "Update runner's details" do
+          success Entities::RunnerDetails
+        end
+        params do
+          requires :id, type: Integer, desc: 'The ID of the runner'
+          optional :description, type: String, desc: 'The description of the runner'
+          optional :active, type: Boolean, desc: 'The state of a runner'
+          optional :tag_list, type: Array[String], desc: 'The list of tags for a runner'
+          optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs'
+          optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
+          optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys,
+                                  desc: 'The access_level of the runner'
+          optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
+          at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout
+        end
+        put ':id' do
+          runner = get_runner(params.delete(:id))
+          authenticate_update_runner!(runner)
+          update_service = ::Ci::UpdateRunnerService.new(runner)
+
+          if update_service.update(declared_params(include_missing: false))
+            present runner, with: Entities::RunnerDetails, current_user: current_user
+          else
+            render_validation_error!(runner)
+          end
+        end
+
+        desc 'Remove a runner' do
+          success Entities::Runner
+        end
+        params do
+          requires :id, type: Integer, desc: 'The ID of the runner'
+        end
+        delete ':id' do
+          runner = get_runner(params[:id])
+
+          authenticate_delete_runner!(runner)
+
+          destroy_conditionally!(runner)
+        end
+
+        desc 'List jobs running on a runner' do
+          success Entities::JobBasicWithProject
+        end
+        params do
+          requires :id, type: Integer, desc: 'The ID of the runner'
+          optional :status, type: String, desc: 'Status of the job', values: ::Ci::Build::AVAILABLE_STATUSES
+          optional :order_by, type: String, desc: 'Order by `id` or not', values: ::Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
+          optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)'
+          use :pagination
+        end
+        get ':id/jobs' do
+          runner = get_runner(params[:id])
+          authenticate_list_runners_jobs!(runner)
+
+          jobs = ::Ci::RunnerJobsFinder.new(runner, params).execute
+
+          present paginate(jobs), with: Entities::JobBasicWithProject
+        end
+      end
+
+      params do
+        requires :id, type: String, desc: 'The ID of a project'
+      end
+      resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+        before { authorize_admin_project }
+
+        desc 'Get runners available for project' do
+          success Entities::Runner
+        end
+        params do
+          optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES,
+                          desc: 'The scope of specific runners to show'
+          optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
+                          desc: 'The type of the runners to show'
+          optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
+                            desc: 'The status of the runners to show'
+          optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
+          use :pagination
+        end
+        get ':id/runners' do
+          runners = ::Ci::Runner.owned_or_instance_wide(user_project.id)
+          # scope is deprecated (for project runners), however api documentation still supports it.
+          # Not including them in `apply_filter` method as it's not supported for group runners
+          runners = filter_runners(runners, params[:scope])
+          runners = apply_filter(runners, params)
+
+          present paginate(runners), with: Entities::Runner
+        end
+
+        desc 'Enable a runner for a project' do
+          success Entities::Runner
+        end
+        params do
+          requires :runner_id, type: Integer, desc: 'The ID of the runner'
+        end
+        post ':id/runners' do
+          runner = get_runner(params[:runner_id])
+          authenticate_enable_runner!(runner)
+
+          if runner.assign_to(user_project)
+            present runner, with: Entities::Runner
+          else
+            render_validation_error!(runner)
+          end
+        end
+
+        desc "Disable project's runner" do
+          success Entities::Runner
+        end
+        params do
+          requires :runner_id, type: Integer, desc: 'The ID of the runner'
+        end
+        # rubocop: disable CodeReuse/ActiveRecord
+        delete ':id/runners/:runner_id' do
+          runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
+          not_found!('Runner') unless runner_project
+
+          runner = runner_project.runner
+          forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
+
+          destroy_conditionally!(runner_project)
+        end
+        # rubocop: enable CodeReuse/ActiveRecord
+      end
+
+      params do
+        requires :id, type: String, desc: 'The ID of a group'
+      end
+      resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+        before { authorize_admin_group }
+
+        desc 'Get runners available for group' do
+          success Entities::Runner
+        end
+        params do
+          optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
+                  desc: 'The type of the runners to show'
+          optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
+                  desc: 'The status of the runners to show'
+          optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
+          use :pagination
+        end
+        get ':id/runners' do
+          runners = ::Ci::Runner.belonging_to_group(user_group.id, include_ancestors: true)
+          runners = apply_filter(runners, params)
+
+          present paginate(runners), with: Entities::Runner
+        end
+      end
+
+      helpers do
+        def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES)
+          return runners unless scope.present?
+
+          unless allowed_scopes.include?(scope)
+            render_api_error!('Scope contains invalid value', 400)
+          end
+
+          # Support deprecated scopes
+          if runners.respond_to?("deprecated_#{scope}")
+            scope = "deprecated_#{scope}"
+          end
+
+          runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend
+        end
+
+        def apply_filter(runners, params)
+          runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
+          runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
+          runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
+
+          runners
+        end
+
+        def get_runner(id)
+          runner = ::Ci::Runner.find(id)
+          not_found!('Runner') unless runner
+          runner
+        end
+
+        def authenticate_show_runner!(runner)
+          return if runner.instance_type? || current_user.admin?
+
+          forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
+        end
+
+        def authenticate_update_runner!(runner)
+          return if current_user.admin?
+
+          forbidden!("No access granted") unless can?(current_user, :update_runner, runner)
+        end
+
+        def authenticate_delete_runner!(runner)
+          return if current_user.admin?
+
+          forbidden!("Runner associated with more than one project") if runner.projects.count > 1
+          forbidden!("No access granted") unless can?(current_user, :delete_runner, runner)
+        end
+
+        def authenticate_enable_runner!(runner)
+          forbidden!("Runner is a group runner") if runner.group_type?
+
+          return if current_user.admin?
+
+          forbidden!("Runner is locked") if runner.locked?
+          forbidden!("No access granted") unless can?(current_user, :assign_runner, runner)
+        end
+
+        def authenticate_list_runners_jobs!(runner)
+          return if current_user.admin?
+
+          forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index b4c5d7869a22417e377069236f49ef74481f05b1..fcd317b2daacbb3c690504bd9836a3d86d536ad9 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -60,7 +60,7 @@ class CommitStatuses < Grape::API
 
         not_found! 'Commit' unless commit
 
-        # Since the CommitStatus is attached to Ci::Pipeline (in the future Pipeline)
+        # Since the CommitStatus is attached to ::Ci::Pipeline (in the future Pipeline)
         # We need to always have the pipeline object
         # To have a valid pipeline object that can be attached to specific MR
         # Other CI service needs to send `ref`
diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb
index 916f89649a5ff2b8a0d06d4dacd7a4e1da6c33e9..95aa587d0c790073cc49b8b1f2dbc48263f14611 100644
--- a/lib/api/group_variables.rb
+++ b/lib/api/group_variables.rb
@@ -48,7 +48,7 @@ class GroupVariables < Grape::API
         requires :value, type: String, desc: 'The value of the variable'
         optional :protected, type: String, desc: 'Whether the variable is protected'
         optional :masked, type: String, desc: 'Whether the variable is masked'
-        optional :variable_type, type: String, values: Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
+        optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
       end
       post ':id/variables' do
         variable_params = declared_params(include_missing: false)
@@ -70,7 +70,7 @@ class GroupVariables < Grape::API
         optional :value, type: String, desc: 'The value of the variable'
         optional :protected, type: String, desc: 'Whether the variable is protected'
         optional :masked, type: String, desc: 'Whether the variable is masked'
-        optional :variable_type, type: String, values: Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
+        optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
       end
       # rubocop: disable CodeReuse/ActiveRecord
       put ':id/variables/:key' do
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 293d7ed9a6a72088d9ff70501477be99d134d95a..d3824597d0844f28c88b798d35020076ef6f0f75 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -60,7 +60,7 @@ def authenticate_job!(require_running: true)
 
       def current_job
         strong_memoize(:current_job) do
-          Ci::Build.find_by_id(params[:id])
+          ::Ci::Build.find_by_id(params[:id])
         end
       end
 
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 61a7fc107efff05c0f5c2f399d742a04c9aa88e1..9432b992d745f793df65707bca660917b30a3a19 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -160,7 +160,7 @@ class Jobs < Grape::API
         authorize!(:update_build, build)
         break forbidden!('Job is not retryable') unless build.retryable?
 
-        build = Ci::Build.retry(build, current_user)
+        build = ::Ci::Build.retry(build, current_user)
 
         present build, with: Entities::Job
       end
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
index edc99590cdb2789c3cf839730537ec68f4d5e8fc..7486518cd4a018701ab2fd88ba2be9ad99c6eadc 100644
--- a/lib/api/pipeline_schedules.rb
+++ b/lib/api/pipeline_schedules.rb
@@ -22,7 +22,7 @@ class PipelineSchedules < Grape::API
       get ':id/pipeline_schedules' do
         authorize! :read_pipeline_schedule, user_project
 
-        schedules = Ci::PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope])
+        schedules = ::Ci::PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope])
           .preload([:owner, :last_pipeline])
         present paginate(schedules), with: Entities::PipelineSchedule
       end
@@ -51,7 +51,7 @@ class PipelineSchedules < Grape::API
       post ':id/pipeline_schedules' do
         authorize! :create_pipeline_schedule, user_project
 
-        pipeline_schedule = Ci::CreatePipelineScheduleService
+        pipeline_schedule = ::Ci::CreatePipelineScheduleService
           .new(user_project, current_user, declared_params(include_missing: false))
           .execute
 
@@ -137,7 +137,7 @@ class PipelineSchedules < Grape::API
         requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
         requires :key, type: String, desc: 'The key of the variable'
         requires :value, type: String, desc: 'The value of the variable'
-        optional :variable_type, type: String, values: Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
+        optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
       end
       post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do
         authorize! :update_pipeline_schedule, pipeline_schedule
@@ -158,7 +158,7 @@ class PipelineSchedules < Grape::API
         requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
         requires :key, type: String, desc: 'The key of the variable'
         optional :value, type: String, desc: 'The value of the variable'
-        optional :variable_type, type: String, values: Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
+        optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
       end
       put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
         authorize! :update_pipeline_schedule, pipeline_schedule
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 20ac121dffa95bbab2f0a7b82a9843363ead5201..b27ff2d24f8e54a0326ca94a4a84b4bf334ccfdd 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -18,7 +18,7 @@ class Pipelines < Grape::API
         use :pagination
         optional :scope,    type: String, values: %w[running pending finished branches tags],
                             desc: 'The scope of pipelines'
-        optional :status,   type: String, values: Ci::HasStatus::AVAILABLE_STATUSES,
+        optional :status,   type: String, values: ::Ci::HasStatus::AVAILABLE_STATUSES,
                             desc: 'The status of pipelines'
         optional :ref,      type: String, desc: 'The ref of pipelines'
         optional :sha,      type: String, desc: 'The sha of pipelines'
@@ -27,7 +27,7 @@ class Pipelines < Grape::API
         optional :username, type: String, desc: 'The username of the user who triggered pipelines'
         optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
         optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
-        optional :order_by, type: String, values: Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id',
+        optional :order_by, type: String, values: ::Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id',
                             desc: 'Order pipelines'
         optional :sort,     type: String, values: %w[asc desc], default: 'desc',
                             desc: 'Sort pipelines'
@@ -36,7 +36,7 @@ class Pipelines < Grape::API
         authorize! :read_pipeline, user_project
         authorize! :read_build, user_project
 
-        pipelines = Ci::PipelinesFinder.new(user_project, current_user, params).execute
+        pipelines = ::Ci::PipelinesFinder.new(user_project, current_user, params).execute
         present paginate(pipelines), with: Entities::PipelineBasic
       end
 
@@ -57,9 +57,9 @@ class Pipelines < Grape::API
           .merge(variables_attributes: params[:variables])
           .except(:variables)
 
-        new_pipeline = Ci::CreatePipelineService.new(user_project,
-                                                     current_user,
-                                                     pipeline_params)
+        new_pipeline = ::Ci::CreatePipelineService.new(user_project,
+                                                       current_user,
+                                                       pipeline_params)
                            .execute(:api, ignore_skip_ci: true, save_on_errors: false)
 
         if new_pipeline.persisted?
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
deleted file mode 100644
index 5f08ebe4a065c671186edae4e83c7d3dc3a260ce..0000000000000000000000000000000000000000
--- a/lib/api/runner.rb
+++ /dev/null
@@ -1,297 +0,0 @@
-# frozen_string_literal: true
-
-module API
-  class Runner < Grape::API
-    helpers ::API::Helpers::Runner
-
-    resource :runners do
-      desc 'Registers a new Runner' do
-        success Entities::RunnerRegistrationDetails
-        http_codes [[201, 'Runner was created'], [403, 'Forbidden']]
-      end
-      params do
-        requires :token, type: String, desc: 'Registration token'
-        optional :description, type: String, desc: %q(Runner's description)
-        optional :info, type: Hash, desc: %q(Runner's metadata)
-        optional :active, type: Boolean, desc: 'Should Runner be active'
-        optional :locked, type: Boolean, desc: 'Should Runner be locked for current project'
-        optional :access_level, type: String, values: Ci::Runner.access_levels.keys,
-                                desc: 'The access_level of the runner'
-        optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs'
-        optional :tag_list, type: Array[String], desc: %q(List of Runner's tags)
-        optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
-      end
-      post '/' do
-        attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout])
-          .merge(get_runner_details_from_request)
-
-        attributes =
-          if runner_registration_token_valid?
-            # Create shared runner. Requires admin access
-            attributes.merge(runner_type: :instance_type)
-          elsif project = Project.find_by_runners_token(params[:token])
-            # Create a specific runner for the project
-            attributes.merge(runner_type: :project_type, projects: [project])
-          elsif group = Group.find_by_runners_token(params[:token])
-            # Create a specific runner for the group
-            attributes.merge(runner_type: :group_type, groups: [group])
-          else
-            forbidden!
-          end
-
-        runner = Ci::Runner.create(attributes)
-
-        if runner.persisted?
-          present runner, with: Entities::RunnerRegistrationDetails
-        else
-          render_validation_error!(runner)
-        end
-      end
-
-      desc 'Deletes a registered Runner' do
-        http_codes [[204, 'Runner was deleted'], [403, 'Forbidden']]
-      end
-      params do
-        requires :token, type: String, desc: %q(Runner's authentication token)
-      end
-      delete '/' do
-        authenticate_runner!
-
-        runner = Ci::Runner.find_by_token(params[:token])
-
-        destroy_conditionally!(runner)
-      end
-
-      desc 'Validates authentication credentials' do
-        http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']]
-      end
-      params do
-        requires :token, type: String, desc: %q(Runner's authentication token)
-      end
-      post '/verify' do
-        authenticate_runner!
-        status 200
-      end
-    end
-
-    resource :jobs do
-      before do
-        Gitlab::ApplicationContext.push(
-          user: -> { current_job&.user },
-          project: -> { current_job&.project }
-        )
-      end
-
-      desc 'Request a job' do
-        success Entities::JobRequest::Response
-        http_codes [[201, 'Job was scheduled'],
-                    [204, 'No job for Runner'],
-                    [403, 'Forbidden']]
-      end
-      params do
-        requires :token, type: String, desc: %q(Runner's authentication token)
-        optional :last_update, type: String, desc: %q(Runner's queue last_update token)
-        optional :info, type: Hash, desc: %q(Runner's metadata) do
-          optional :name, type: String, desc: %q(Runner's name)
-          optional :version, type: String, desc: %q(Runner's version)
-          optional :revision, type: String, desc: %q(Runner's revision)
-          optional :platform, type: String, desc: %q(Runner's platform)
-          optional :architecture, type: String, desc: %q(Runner's architecture)
-          optional :executor, type: String, desc: %q(Runner's executor)
-          optional :features, type: Hash, desc: %q(Runner's features)
-        end
-        optional :session, type: Hash, desc: %q(Runner's session data) do
-          optional :url, type: String, desc: %q(Session's url)
-          optional :certificate, type: String, desc: %q(Session's certificate)
-          optional :authorization, type: String, desc: %q(Session's authorization)
-        end
-        optional :job_age, type: Integer, desc: %q(Job should be older than passed age in seconds to be ran on runner)
-      end
-      post '/request' do
-        authenticate_runner!
-
-        unless current_runner.active?
-          header 'X-GitLab-Last-Update', current_runner.ensure_runner_queue_value
-          break no_content!
-        end
-
-        runner_params = declared_params(include_missing: false)
-
-        if current_runner.runner_queue_value_latest?(runner_params[:last_update])
-          header 'X-GitLab-Last-Update', runner_params[:last_update]
-          Gitlab::Metrics.add_event(:build_not_found_cached)
-          break no_content!
-        end
-
-        new_update = current_runner.ensure_runner_queue_value
-        result = ::Ci::RegisterJobService.new(current_runner).execute(runner_params)
-
-        if result.valid?
-          if result.build
-            Gitlab::Metrics.add_event(:build_found)
-            present Ci::BuildRunnerPresenter.new(result.build), with: Entities::JobRequest::Response
-          else
-            Gitlab::Metrics.add_event(:build_not_found)
-            header 'X-GitLab-Last-Update', new_update
-            no_content!
-          end
-        else
-          # We received build that is invalid due to concurrency conflict
-          Gitlab::Metrics.add_event(:build_invalid)
-          conflict!
-        end
-      end
-
-      desc 'Updates a job' do
-        http_codes [[200, 'Job was updated'], [403, 'Forbidden']]
-      end
-      params do
-        requires :token, type: String, desc: %q(Runners's authentication token)
-        requires :id, type: Integer, desc: %q(Job's ID)
-        optional :trace, type: String, desc: %q(Job's full trace)
-        optional :state, type: String, desc: %q(Job's status: success, failed)
-        optional :failure_reason, type: String, desc: %q(Job's failure_reason)
-      end
-      put '/:id' do
-        job = authenticate_job!
-
-        job.trace.set(params[:trace]) if params[:trace]
-
-        Gitlab::Metrics.add_event(:update_build)
-
-        case params[:state].to_s
-        when 'running'
-          job.touch if job.needs_touch?
-        when 'success'
-          job.success!
-        when 'failed'
-          job.drop!(params[:failure_reason] || :unknown_failure)
-        end
-      end
-
-      desc 'Appends a patch to the job trace' do
-        http_codes [[202, 'Trace was patched'],
-                    [400, 'Missing Content-Range header'],
-                    [403, 'Forbidden'],
-                    [416, 'Range not satisfiable']]
-      end
-      params do
-        requires :id, type: Integer, desc: %q(Job's ID)
-        optional :token, type: String, desc: %q(Job's authentication token)
-      end
-      patch '/:id/trace' do
-        job = authenticate_job!
-
-        error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range')
-        content_range = request.headers['Content-Range']
-        content_range = content_range.split('-')
-
-        # TODO:
-        # it seems that `Content-Range` as formatted by runner is wrong,
-        # the `byte_end` should point to final byte, but it points byte+1
-        # that means that we have to calculate end of body,
-        # as we cannot use `content_length[1]`
-        # Issue: https://gitlab.com/gitlab-org/gitlab-runner/issues/3275
-
-        body_data = request.body.read
-        body_start = content_range[0].to_i
-        body_end = body_start + body_data.bytesize
-
-        stream_size = job.trace.append(body_data, body_start)
-        unless stream_size == body_end
-          break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" })
-        end
-
-        status 202
-        header 'Job-Status', job.status
-        header 'Range', "0-#{stream_size}"
-        header 'X-GitLab-Trace-Update-Interval', job.trace.update_interval.to_s
-      end
-
-      desc 'Authorize artifacts uploading for job' do
-        http_codes [[200, 'Upload allowed'],
-                    [403, 'Forbidden'],
-                    [405, 'Artifacts support not enabled'],
-                    [413, 'File too large']]
-      end
-      params do
-        requires :id, type: Integer, desc: %q(Job's ID)
-        optional :token, type: String, desc: %q(Job's authentication token)
-        optional :filesize, type: Integer, desc: %q(Artifacts filesize)
-        optional :artifact_type, type: String, desc: %q(The type of artifact),
-                                 default: 'archive', values: Ci::JobArtifact.file_types.keys
-      end
-      post '/:id/artifacts/authorize' do
-        not_allowed! unless Gitlab.config.artifacts.enabled
-        require_gitlab_workhorse!
-        Gitlab::Workhorse.verify_api_request!(headers)
-
-        job = authenticate_job!
-
-        service = Ci::AuthorizeJobArtifactService.new(job, params, max_size: max_artifacts_size(job))
-
-        forbidden! if service.forbidden?
-        file_too_large! if service.too_large?
-
-        status 200
-        content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
-        service.headers
-      end
-
-      desc 'Upload artifacts for job' do
-        success Entities::JobRequest::Response
-        http_codes [[201, 'Artifact uploaded'],
-                    [400, 'Bad request'],
-                    [403, 'Forbidden'],
-                    [405, 'Artifacts support not enabled'],
-                    [413, 'File too large']]
-      end
-      params do
-        requires :id, type: Integer, desc: %q(Job's ID)
-        requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware))
-        optional :token, type: String, desc: %q(Job's authentication token)
-        optional :expire_in, type: String, desc: %q(Specify when artifacts should expire)
-        optional :artifact_type, type: String, desc: %q(The type of artifact),
-                                 default: 'archive', values: Ci::JobArtifact.file_types.keys
-        optional :artifact_format, type: String, desc: %q(The format of artifact),
-                                   default: 'zip', values: Ci::JobArtifact.file_formats.keys
-        optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware))
-      end
-      post '/:id/artifacts' do
-        not_allowed! unless Gitlab.config.artifacts.enabled
-        require_gitlab_workhorse!
-
-        job = authenticate_job!
-
-        artifacts = params[:file]
-        metadata = params[:metadata]
-
-        file_too_large! unless artifacts.size < max_artifacts_size(job)
-
-        result = Ci::CreateJobArtifactsService.new(job.project).execute(job, artifacts, params, metadata_file: metadata)
-
-        if result[:status] == :success
-          status :created
-        else
-          render_api_error!(result[:message], result[:http_status])
-        end
-      end
-
-      desc 'Download the artifacts file for job' do
-        http_codes [[200, 'Upload allowed'],
-                    [403, 'Forbidden'],
-                    [404, 'Artifact not found']]
-      end
-      params do
-        requires :id, type: Integer, desc: %q(Job's ID)
-        optional :token, type: String, desc: %q(Job's authentication token)
-        optional :direct_download, default: false, type: Boolean, desc: %q(Perform direct download from remote storage instead of proxying artifacts)
-      end
-      get '/:id/artifacts' do
-        job = authenticate_job!(require_running: false)
-
-        present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download])
-      end
-    end
-  end
-end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
deleted file mode 100644
index 43ee1dd1f71e2279b37d930f32675c087946a059..0000000000000000000000000000000000000000
--- a/lib/api/runners.rb
+++ /dev/null
@@ -1,287 +0,0 @@
-# frozen_string_literal: true
-
-module API
-  class Runners < Grape::API
-    include PaginationParams
-
-    before { authenticate! }
-
-    resource :runners do
-      desc 'Get runners available for user' do
-        success Entities::Runner
-      end
-      params do
-        optional :scope, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
-                         desc: 'The scope of specific runners to show'
-        optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
-                        desc: 'The type of the runners to show'
-        optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
-                          desc: 'The status of the runners to show'
-        optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
-        use :pagination
-      end
-      get do
-        runners = current_user.ci_owned_runners
-        runners = filter_runners(runners, params[:scope], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
-        runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
-        runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
-        runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
-
-        present paginate(runners), with: Entities::Runner
-      end
-
-      desc 'Get all runners - shared and specific' do
-        success Entities::Runner
-      end
-      params do
-        optional :scope, type: String, values: Ci::Runner::AVAILABLE_SCOPES,
-                         desc: 'The scope of specific runners to show'
-        optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
-                        desc: 'The type of the runners to show'
-        optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
-                          desc: 'The status of the runners to show'
-        optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
-        use :pagination
-      end
-      get 'all' do
-        authenticated_as_admin!
-
-        runners = Ci::Runner.all
-        runners = filter_runners(runners, params[:scope])
-        runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
-        runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
-        runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
-
-        present paginate(runners), with: Entities::Runner
-      end
-
-      desc "Get runner's details" do
-        success Entities::RunnerDetails
-      end
-      params do
-        requires :id, type: Integer, desc: 'The ID of the runner'
-      end
-      get ':id' do
-        runner = get_runner(params[:id])
-        authenticate_show_runner!(runner)
-
-        present runner, with: Entities::RunnerDetails, current_user: current_user
-      end
-
-      desc "Update runner's details" do
-        success Entities::RunnerDetails
-      end
-      params do
-        requires :id, type: Integer, desc: 'The ID of the runner'
-        optional :description, type: String, desc: 'The description of the runner'
-        optional :active, type: Boolean, desc: 'The state of a runner'
-        optional :tag_list, type: Array[String], desc: 'The list of tags for a runner'
-        optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs'
-        optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
-        optional :access_level, type: String, values: Ci::Runner.access_levels.keys,
-                                desc: 'The access_level of the runner'
-        optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
-        at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout
-      end
-      put ':id' do
-        runner = get_runner(params.delete(:id))
-        authenticate_update_runner!(runner)
-        update_service = Ci::UpdateRunnerService.new(runner)
-
-        if update_service.update(declared_params(include_missing: false))
-          present runner, with: Entities::RunnerDetails, current_user: current_user
-        else
-          render_validation_error!(runner)
-        end
-      end
-
-      desc 'Remove a runner' do
-        success Entities::Runner
-      end
-      params do
-        requires :id, type: Integer, desc: 'The ID of the runner'
-      end
-      delete ':id' do
-        runner = get_runner(params[:id])
-
-        authenticate_delete_runner!(runner)
-
-        destroy_conditionally!(runner)
-      end
-
-      desc 'List jobs running on a runner' do
-        success Entities::JobBasicWithProject
-      end
-      params do
-        requires :id, type: Integer, desc: 'The ID of the runner'
-        optional :status, type: String, desc: 'Status of the job', values: Ci::Build::AVAILABLE_STATUSES
-        optional :order_by, type: String, desc: 'Order by `id` or not', values: Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
-        optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)'
-        use :pagination
-      end
-      get ':id/jobs' do
-        runner = get_runner(params[:id])
-        authenticate_list_runners_jobs!(runner)
-
-        jobs = Ci::RunnerJobsFinder.new(runner, params).execute
-
-        present paginate(jobs), with: Entities::JobBasicWithProject
-      end
-    end
-
-    params do
-      requires :id, type: String, desc: 'The ID of a project'
-    end
-    resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
-      before { authorize_admin_project }
-
-      desc 'Get runners available for project' do
-        success Entities::Runner
-      end
-      params do
-        optional :scope, type: String, values: Ci::Runner::AVAILABLE_SCOPES,
-                         desc: 'The scope of specific runners to show'
-        optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
-                        desc: 'The type of the runners to show'
-        optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
-                          desc: 'The status of the runners to show'
-        optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
-        use :pagination
-      end
-      get ':id/runners' do
-        runners = Ci::Runner.owned_or_instance_wide(user_project.id)
-        # scope is deprecated (for project runners), however api documentation still supports it.
-        # Not including them in `apply_filter` method as it's not supported for group runners
-        runners = filter_runners(runners, params[:scope])
-        runners = apply_filter(runners, params)
-
-        present paginate(runners), with: Entities::Runner
-      end
-
-      desc 'Enable a runner for a project' do
-        success Entities::Runner
-      end
-      params do
-        requires :runner_id, type: Integer, desc: 'The ID of the runner'
-      end
-      post ':id/runners' do
-        runner = get_runner(params[:runner_id])
-        authenticate_enable_runner!(runner)
-
-        if runner.assign_to(user_project)
-          present runner, with: Entities::Runner
-        else
-          render_validation_error!(runner)
-        end
-      end
-
-      desc "Disable project's runner" do
-        success Entities::Runner
-      end
-      params do
-        requires :runner_id, type: Integer, desc: 'The ID of the runner'
-      end
-      # rubocop: disable CodeReuse/ActiveRecord
-      delete ':id/runners/:runner_id' do
-        runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
-        not_found!('Runner') unless runner_project
-
-        runner = runner_project.runner
-        forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
-
-        destroy_conditionally!(runner_project)
-      end
-      # rubocop: enable CodeReuse/ActiveRecord
-    end
-
-    params do
-      requires :id, type: String, desc: 'The ID of a group'
-    end
-    resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
-      before { authorize_admin_group }
-
-      desc 'Get runners available for group' do
-        success Entities::Runner
-      end
-      params do
-        optional :type, type: String, values: Ci::Runner::AVAILABLE_TYPES,
-                 desc: 'The type of the runners to show'
-        optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
-                 desc: 'The status of the runners to show'
-        optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
-        use :pagination
-      end
-      get ':id/runners' do
-        runners = Ci::Runner.belonging_to_group(user_group.id, include_ancestors: true)
-        runners = apply_filter(runners, params)
-
-        present paginate(runners), with: Entities::Runner
-      end
-    end
-
-    helpers do
-      def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES)
-        return runners unless scope.present?
-
-        unless allowed_scopes.include?(scope)
-          render_api_error!('Scope contains invalid value', 400)
-        end
-
-        # Support deprecated scopes
-        if runners.respond_to?("deprecated_#{scope}")
-          scope = "deprecated_#{scope}"
-        end
-
-        runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend
-      end
-
-      def apply_filter(runners, params)
-        runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
-        runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
-        runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
-
-        runners
-      end
-
-      def get_runner(id)
-        runner = Ci::Runner.find(id)
-        not_found!('Runner') unless runner
-        runner
-      end
-
-      def authenticate_show_runner!(runner)
-        return if runner.instance_type? || current_user.admin?
-
-        forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
-      end
-
-      def authenticate_update_runner!(runner)
-        return if current_user.admin?
-
-        forbidden!("No access granted") unless can?(current_user, :update_runner, runner)
-      end
-
-      def authenticate_delete_runner!(runner)
-        return if current_user.admin?
-
-        forbidden!("Runner associated with more than one project") if runner.projects.count > 1
-        forbidden!("No access granted") unless can?(current_user, :delete_runner, runner)
-      end
-
-      def authenticate_enable_runner!(runner)
-        forbidden!("Runner is a group runner") if runner.group_type?
-
-        return if current_user.admin?
-
-        forbidden!("Runner is locked") if runner.locked?
-        forbidden!("No access granted") unless can?(current_user, :assign_runner, runner)
-      end
-
-      def authenticate_list_runners_jobs!(runner)
-        return if current_user.admin?
-
-        forbidden!("No access granted") unless can?(current_user, :read_runner, runner)
-      end
-    end
-  end
-end
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index e1829403941d57232e6441e79fcfa6f31dfcd95d..65e9118e3ef126e61d74baed6c64e68209248cb1 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -32,7 +32,7 @@ class Triggers < Grape::API
         project = find_project(params[:id])
         not_found! unless project
 
-        result = Ci::PipelineTriggerService.new(project, nil, params).execute
+        result = ::Ci::PipelineTriggerService.new(project, nil, params).execute
         not_found! unless result
 
         if result[:http_status]
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 192b06b8a1b06ba09c813a57ae8520af07a00461..f3def756fe8da117caef87883f9e06b14db0d83d 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -56,7 +56,7 @@ def filter_variable_parameters(params)
         requires :value, type: String, desc: 'The value of the variable'
         optional :protected, type: Boolean, desc: 'Whether the variable is protected'
         optional :masked, type: Boolean, desc: 'Whether the variable is masked'
-        optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
+        optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
         optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
       end
       post ':id/variables' do
@@ -80,7 +80,7 @@ def filter_variable_parameters(params)
         optional :value, type: String, desc: 'The value of the variable'
         optional :protected, type: Boolean, desc: 'Whether the variable is protected'
         optional :masked, type: Boolean, desc: 'Whether the variable is masked'
-        optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
+        optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
         optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
       end
       # rubocop: disable CodeReuse/ActiveRecord
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/ci/runner_spec.rb
similarity index 98%
rename from spec/requests/api/runner_spec.rb
rename to spec/requests/api/ci/runner_spec.rb
index c977908149c26609df080b881a4d64a3514f9d81..6323d1f68e344318f80d4909bc4a17bb6c4699bc 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/ci/runner_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
+RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
   include StubGitlabCalls
   include RedisHelpers
   include WorkhorseHelpers
@@ -13,7 +13,7 @@
     stub_feature_flags(ci_enable_live_trace: true)
     stub_gitlab_calls
     stub_application_setting(runners_registration_token: registration_token)
-    allow_any_instance_of(Ci::Runner).to receive(:cache_attributes)
+    allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
   end
 
   describe '/api/v4/runners' do
@@ -38,7 +38,7 @@
         it 'creates runner with default values' do
           post api('/runners'), params: { token: registration_token }
 
-          runner = Ci::Runner.first
+          runner = ::Ci::Runner.first
 
           expect(response).to have_gitlab_http_status(:created)
           expect(json_response['id']).to eq(runner.id)
@@ -57,7 +57,7 @@
 
             expect(response).to have_gitlab_http_status(:created)
             expect(project.runners.size).to eq(1)
-            runner = Ci::Runner.first
+            runner = ::Ci::Runner.first
             expect(runner.token).not_to eq(registration_token)
             expect(runner.token).not_to eq(project.runners_token)
             expect(runner).to be_project_type
@@ -72,7 +72,7 @@
 
             expect(response).to have_gitlab_http_status(:created)
             expect(group.runners.reload.size).to eq(1)
-            runner = Ci::Runner.first
+            runner = ::Ci::Runner.first
             expect(runner.token).not_to eq(registration_token)
             expect(runner.token).not_to eq(group.runners_token)
             expect(runner).to be_group_type
@@ -88,7 +88,7 @@
                                 }
 
           expect(response).to have_gitlab_http_status(:created)
-          expect(Ci::Runner.first.description).to eq('server.hostname')
+          expect(::Ci::Runner.first.description).to eq('server.hostname')
         end
       end
 
@@ -100,7 +100,7 @@
                                 }
 
           expect(response).to have_gitlab_http_status(:created)
-          expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
+          expect(::Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
         end
       end
 
@@ -114,8 +114,8 @@
                                   }
 
             expect(response).to have_gitlab_http_status(:created)
-            expect(Ci::Runner.first.run_untagged).to be false
-            expect(Ci::Runner.first.tag_list.sort).to eq(['tag'])
+            expect(::Ci::Runner.first.run_untagged).to be false
+            expect(::Ci::Runner.first.tag_list.sort).to eq(['tag'])
           end
         end
 
@@ -141,7 +141,7 @@
                                 }
 
           expect(response).to have_gitlab_http_status(:created)
-          expect(Ci::Runner.first.locked).to be true
+          expect(::Ci::Runner.first.locked).to be true
         end
       end
 
@@ -154,7 +154,7 @@
                                   }
 
             expect(response).to have_gitlab_http_status(:created)
-            expect(Ci::Runner.first.active).to be true
+            expect(::Ci::Runner.first.active).to be true
           end
         end
 
@@ -166,7 +166,7 @@
                                   }
 
             expect(response).to have_gitlab_http_status(:created)
-            expect(Ci::Runner.first.active).to be false
+            expect(::Ci::Runner.first.active).to be false
           end
         end
       end
@@ -180,7 +180,7 @@
                                   }
 
             expect(response).to have_gitlab_http_status(:created)
-            expect(Ci::Runner.first.ref_protected?).to be true
+            expect(::Ci::Runner.first.ref_protected?).to be true
           end
         end
 
@@ -192,7 +192,7 @@
                                   }
 
             expect(response).to have_gitlab_http_status(:created)
-            expect(Ci::Runner.first.ref_protected?).to be false
+            expect(::Ci::Runner.first.ref_protected?).to be false
           end
         end
       end
@@ -205,7 +205,7 @@
                                 }
 
           expect(response).to have_gitlab_http_status(:created)
-          expect(Ci::Runner.first.maximum_timeout).to eq(9000)
+          expect(::Ci::Runner.first.maximum_timeout).to eq(9000)
         end
 
         context 'when maximum job timeout is empty' do
@@ -216,7 +216,7 @@
                                   }
 
             expect(response).to have_gitlab_http_status(:created)
-            expect(Ci::Runner.first.maximum_timeout).to be_nil
+            expect(::Ci::Runner.first.maximum_timeout).to be_nil
           end
         end
       end
@@ -232,7 +232,7 @@
                                   }
 
             expect(response).to have_gitlab_http_status(:created)
-            expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
+            expect(::Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
           end
         end
       end
@@ -243,7 +243,7 @@
              headers: { 'X-Forwarded-For' => '123.111.123.111' }
 
         expect(response).to have_gitlab_http_status(:created)
-        expect(Ci::Runner.first.ip_address).to eq('123.111.123.111')
+        expect(::Ci::Runner.first.ip_address).to eq('123.111.123.111')
       end
     end
 
@@ -271,7 +271,7 @@
           delete api('/runners'), params: { token: runner.token }
 
           expect(response).to have_gitlab_http_status(:no_content)
-          expect(Ci::Runner.count).to eq(0)
+          expect(::Ci::Runner.count).to eq(0)
         end
 
         it_behaves_like '412 response' do
@@ -537,7 +537,7 @@
           end
 
           it 'creates persistent ref' do
-            expect_any_instance_of(Ci::PersistentRef).to receive(:create_ref)
+            expect_any_instance_of(::Ci::PersistentRef).to receive(:create_ref)
               .with(job.sha, "refs/#{Repository::REF_PIPELINES}/#{job.commit_id}")
 
             request_job info: { platform: :darwin }
@@ -749,7 +749,7 @@
 
           context 'when concurrently updating a job' do
             before do
-              expect_any_instance_of(Ci::Build).to receive(:run!)
+              expect_any_instance_of(::Ci::Build).to receive(:run!)
                   .and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
             end
 
@@ -890,7 +890,7 @@
             let!(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, builds: [job], trigger: trigger) }
 
             before do
-              project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
+              project.variables << ::Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
             end
 
             shared_examples 'expected variables behavior' do
@@ -1099,7 +1099,7 @@ def request_job(token = runner.token, **params)
         let_it_be(:project) { create(:project, :repository) }
 
         let(:runner) { create(:ci_runner, :project, projects: [project]) }
-        let(:service) { Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute }
+        let(:service) { ::Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute }
         let(:pipeline) { service[:pipeline] }
         let(:build) { pipeline.builds.first }
         let(:job) { {} }
@@ -2258,7 +2258,7 @@ def authorize_artifacts_with_token_in_headers(params = {}, request_headers = hea
             FileUtils.remove_entry(new_tmpdir)
           end
 
-          it' "fails to post artifacts for outside of tmp path"' do
+          it 'fails to post artifacts for outside of tmp path' do
             upload_artifacts(file_upload, headers_with_token)
 
             expect(response).to have_gitlab_http_status(:bad_request)
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
similarity index 98%
rename from spec/requests/api/runners_spec.rb
rename to spec/requests/api/ci/runners_spec.rb
index 0726ab5e3d8584637d3448d02a515e0b574f53d4..670456e5dba32abf137a749cebfb3d81b9512de8 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe API::Runners do
+RSpec.describe API::Ci::Runners do
   let_it_be(:admin) { create(:user, :admin) }
   let_it_be(:user) { create(:user) }
   let_it_be(:user2) { create(:user) }
@@ -266,7 +266,7 @@
               delete api("/runners/#{unused_project_runner.id}", admin)
 
               expect(response).to have_gitlab_http_status(:no_content)
-            end.to change { Ci::Runner.project_type.count }.by(-1)
+            end.to change { ::Ci::Runner.project_type.count }.by(-1)
           end
         end
 
@@ -493,7 +493,7 @@ def update_runner(id, user, args)
             delete api("/runners/#{shared_runner.id}", admin)
 
             expect(response).to have_gitlab_http_status(:no_content)
-          end.to change { Ci::Runner.instance_type.count }.by(-1)
+          end.to change { ::Ci::Runner.instance_type.count }.by(-1)
         end
 
         it_behaves_like '412 response' do
@@ -507,7 +507,7 @@ def update_runner(id, user, args)
             delete api("/runners/#{project_runner.id}", admin)
 
             expect(response).to have_gitlab_http_status(:no_content)
-          end.to change { Ci::Runner.project_type.count }.by(-1)
+          end.to change { ::Ci::Runner.project_type.count }.by(-1)
         end
       end
 
@@ -542,7 +542,7 @@ def update_runner(id, user, args)
             delete api("/runners/#{project_runner.id}", user)
 
             expect(response).to have_gitlab_http_status(:no_content)
-          end.to change { Ci::Runner.project_type.count }.by(-1)
+          end.to change { ::Ci::Runner.project_type.count }.by(-1)
         end
 
         it 'does not delete group runner with guest access' do
@@ -574,7 +574,7 @@ def update_runner(id, user, args)
             delete api("/runners/#{group_runner_a.id}", user)
 
             expect(response).to have_gitlab_http_status(:no_content)
-          end.to change { Ci::Runner.group_type.count }.by(-1)
+          end.to change { ::Ci::Runner.group_type.count }.by(-1)
         end
 
         it 'deletes inherited group runner with owner access' do
@@ -582,7 +582,7 @@ def update_runner(id, user, args)
             delete api("/runners/#{group_runner_b.id}", user)
 
             expect(response).to have_gitlab_http_status(:no_content)
-          end.to change { Ci::Runner.group_type.count }.by(-1)
+          end.to change { ::Ci::Runner.group_type.count }.by(-1)
         end
 
         it_behaves_like '412 response' do
@@ -968,7 +968,7 @@ def update_runner(id, user, args)
       end
 
       it 'does not enable locked runner' do
-        project_runner2.update(locked: true)
+        project_runner2.update!(locked: true)
 
         expect do
           post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id }