diff --git a/changelogs/unreleased/add-runner-access-level-registration.yml b/changelogs/unreleased/add-runner-access-level-registration.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7ae95025abb211e755fe6f090327bdb5e9ab80a3
--- /dev/null
+++ b/changelogs/unreleased/add-runner-access-level-registration.yml
@@ -0,0 +1,5 @@
+---
+title: Add option to set access_level of runners upon registration
+merge_request: 27490
+author: Zelin L
+type: added
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 46f7b1d2a25071285d4ce15cae782e0e4d15a0c5..2d91428d1c161698bb0e5a0a4810048c97cc7ca6 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -486,6 +486,7 @@ POST /runners
 | `locked`     | boolean| no       | Whether the Runner should be locked for current project |
 | `run_untagged` | boolean | no | Whether the Runner should handle untagged jobs |
 | `tag_list` | Array[String] | no | List of Runner's tags |
+| `access_level`    | string   | no       | The access_level of the runner; `not_protected` or `ref_protected` |
 | `maximum_timeout` | integer | no | Maximum timeout set when this Runner will handle the job |
 
 ```
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index c60d25b88cb8f67a326e7271701bf55b878fa49c..ea36c24eca252daeb304d3f5bdcdc8abfc625f44 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -15,12 +15,14 @@ module API
         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, :maximum_timeout])
+        attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout])
           .merge(get_runner_details_from_request)
 
         attributes =
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 3585a82783831159b6725753e325180e917709fa..29515709a744c525dfd23e62b281e222968ff83b 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -168,6 +168,32 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
         end
       end
 
+      context 'when access_level is provided for Runner' do
+        context 'when access_level is set to ref_protected' do
+          it 'creates runner' do
+            post api('/runners'), params: {
+                                    token: registration_token,
+                                    access_level: 'ref_protected'
+                                  }
+
+            expect(response).to have_gitlab_http_status 201
+            expect(Ci::Runner.first.ref_protected?).to be true
+          end
+        end
+
+        context 'when access_level is set to not_protected' do
+          it 'creates runner' do
+            post api('/runners'), params: {
+                                    token: registration_token,
+                                    access_level: 'not_protected'
+                                  }
+
+            expect(response).to have_gitlab_http_status 201
+            expect(Ci::Runner.first.ref_protected?).to be false
+          end
+        end
+      end
+
       context 'when maximum job timeout is specified' do
         it 'creates runner' do
           post api('/runners'), params: {