diff --git a/changelogs/unreleased/23104-remove-public-param-for-projects.yml b/changelogs/unreleased/23104-remove-public-param-for-projects.yml
new file mode 100644
index 0000000000000000000000000000000000000000..78eb785279f86423a2236739e48bc73f2a7a00a3
--- /dev/null
+++ b/changelogs/unreleased/23104-remove-public-param-for-projects.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: remove `public` param for projects'
+merge_request: 8736
+author:
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 122075bbd115eabe4ec7776158b30073c87f7929..040153ac88006cfe1fa7f8632504487ce85a5a52 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -642,7 +642,6 @@ Parameters:
 | `snippets_enabled` | boolean | no | Enable snippets for this project |
 | `container_registry_enabled` | boolean | no | Enable container registry for this project |
 | `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
-| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 |
 | `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) |
 | `import_url` | string | no | URL to import repository from |
 | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
@@ -676,7 +675,6 @@ Parameters:
 | `snippets_enabled` | boolean | no | Enable snippets for this project |
 | `container_registry_enabled` | boolean | no | Enable container registry for this project |
 | `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
-| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 |
 | `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) |
 | `import_url` | string | no | URL to import repository from |
 | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
@@ -709,7 +707,6 @@ Parameters:
 | `snippets_enabled` | boolean | no | Enable snippets for this project |
 | `container_registry_enabled` | boolean | no | Enable container registry for this project |
 | `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
-| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 |
 | `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) |
 | `import_url` | string | no | URL to import repository from |
 | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 92a70faf1c266d791cfff4a33d815012e30851d1..bd4b23195ac08848b53178429a3eabf6cfc0cc53 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -16,7 +16,6 @@ module API
         optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
         optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
         optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
-        optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.'
         optional :visibility_level, type: Integer, values: [
           Gitlab::VisibilityLevel::PRIVATE,
           Gitlab::VisibilityLevel::INTERNAL,
@@ -26,16 +25,6 @@ module API
         optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
         optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
       end
-
-      def map_public_to_visibility_level(attrs)
-        publik = attrs.delete(:public)
-        if !publik.nil? && !attrs[:visibility_level].present?
-          # Since setting the public attribute to private could mean either
-          # private or internal, use the more conservative option, private.
-          attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
-        end
-        attrs
-      end
     end
 
     resource :projects do
@@ -161,7 +150,7 @@ module API
         use :create_params
       end
       post do
-        attrs = map_public_to_visibility_level(declared_params(include_missing: false))
+        attrs = declared_params(include_missing: false)
         project = ::Projects::CreateService.new(current_user, attrs).execute
 
         if project.saved?
@@ -190,7 +179,7 @@ module API
         user = User.find_by(id: params.delete(:user_id))
         not_found!('User') unless user
 
-        attrs = map_public_to_visibility_level(declared_params(include_missing: false))
+        attrs = declared_params(include_missing: false)
         project = ::Projects::CreateService.new(user, attrs).execute
 
         if project.saved?
@@ -268,14 +257,14 @@ module API
         at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled,
                         :wiki_enabled, :builds_enabled, :snippets_enabled,
                         :shared_runners_enabled, :container_registry_enabled,
-                        :lfs_enabled, :public, :visibility_level, :public_builds,
+                        :lfs_enabled, :visibility_level, :public_builds,
                         :request_access_enabled, :only_allow_merge_if_build_succeeds,
                         :only_allow_merge_if_all_discussions_are_resolved, :path,
                         :default_branch
       end
       put ':id' do
         authorize_admin_project
-        attrs = map_public_to_visibility_level(declared_params(include_missing: false))
+        attrs = declared_params(include_missing: false)
         authorize! :rename_project, user_project if attrs[:name].present?
         authorize! :change_visibility_level, user_project if attrs[:visibility_level].present?
 
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 225e2e005df084f5aa579b4871f7b247968ff384..ac0bbec44e09ec5b3633eb1b75d821650900e2ae 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -359,13 +359,6 @@ describe API::Projects, api: true  do
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
     end
 
-    it 'sets a project as public using :public' do
-      project = attributes_for(:project, { public: true })
-      post api('/projects', user), project
-      expect(json_response['public']).to be_truthy
-      expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
-    end
-
     it 'sets a project as internal' do
       project = attributes_for(:project, :internal)
       post api('/projects', user), project
@@ -373,13 +366,6 @@ describe API::Projects, api: true  do
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
     end
 
-    it 'sets a project as internal overriding :public' do
-      project = attributes_for(:project, :internal, { public: true })
-      post api('/projects', user), project
-      expect(json_response['public']).to be_falsey
-      expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
-    end
-
     it 'sets a project as private' do
       project = attributes_for(:project, :private)
       post api('/projects', user), project
@@ -387,13 +373,6 @@ describe API::Projects, api: true  do
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
     end
 
-    it 'sets a project as private using :public' do
-      project = attributes_for(:project, { public: false })
-      post api('/projects', user), project
-      expect(json_response['public']).to be_falsey
-      expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
-    end
-
     it 'sets a project as allowing merge even if build fails' do
       project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false })
       post api('/projects', user), project
@@ -431,13 +410,14 @@ describe API::Projects, api: true  do
     end
 
     context 'when a visibility level is restricted' do
+      let(:project_param) { attributes_for(:project, :public) }
+
       before do
-        @project = attributes_for(:project, { public: true })
         stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
       end
 
       it 'does not allow a non-admin to use a restricted visibility level' do
-        post api('/projects', user), @project
+        post api('/projects', user), project_param
 
         expect(response).to have_http_status(400)
         expect(json_response['message']['visibility_level'].first).to(
@@ -446,7 +426,8 @@ describe API::Projects, api: true  do
       end
 
       it 'allows an admin to override restricted visibility settings' do
-        post api('/projects', admin), @project
+        post api('/projects', admin), project_param
+
         expect(json_response['public']).to be_truthy
         expect(json_response['visibility_level']).to(
           eq(Gitlab::VisibilityLevel::PUBLIC)
@@ -499,15 +480,6 @@ describe API::Projects, api: true  do
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
     end
 
-    it 'sets a project as public using :public' do
-      project = attributes_for(:project, { public: true })
-      post api("/projects/user/#{user.id}", admin), project
-
-      expect(response).to have_http_status(201)
-      expect(json_response['public']).to be_truthy
-      expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
-    end
-
     it 'sets a project as internal' do
       project = attributes_for(:project, :internal)
       post api("/projects/user/#{user.id}", admin), project
@@ -517,14 +489,6 @@ describe API::Projects, api: true  do
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
     end
 
-    it 'sets a project as internal overriding :public' do
-      project = attributes_for(:project, :internal, { public: true })
-      post api("/projects/user/#{user.id}", admin), project
-      expect(response).to have_http_status(201)
-      expect(json_response['public']).to be_falsey
-      expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
-    end
-
     it 'sets a project as private' do
       project = attributes_for(:project, :private)
       post api("/projects/user/#{user.id}", admin), project
@@ -532,13 +496,6 @@ describe API::Projects, api: true  do
       expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
     end
 
-    it 'sets a project as private using :public' do
-      project = attributes_for(:project, { public: false })
-      post api("/projects/user/#{user.id}", admin), project
-      expect(json_response['public']).to be_falsey
-      expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
-    end
-
     it 'sets a project as allowing merge even if build fails' do
       project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false })
       post api("/projects/user/#{user.id}", admin), project
@@ -865,7 +822,7 @@ describe API::Projects, api: true  do
     it 'creates a new project snippet' do
       post api("/projects/#{project.id}/snippets", user),
         title: 'api test', file_name: 'sample.rb', code: 'test',
-        visibility_level: '0'
+        visibility_level: Gitlab::VisibilityLevel::PRIVATE
       expect(response).to have_http_status(201)
       expect(json_response['title']).to eq('api test')
     end
@@ -1114,7 +1071,7 @@ describe API::Projects, api: true  do
       end
 
       it 'updates visibility_level' do
-        project_param = { visibility_level: 20 }
+        project_param = { visibility_level: Gitlab::VisibilityLevel::PUBLIC }
         put api("/projects/#{project3.id}", user), project_param
         expect(response).to have_http_status(200)
         project_param.each_pair do |k, v|
@@ -1124,7 +1081,7 @@ describe API::Projects, api: true  do
 
       it 'updates visibility_level from public to private' do
         project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
-        project_param = { public: false }
+        project_param = { visibility_level: Gitlab::VisibilityLevel::PRIVATE }
         put api("/projects/#{project3.id}", user), project_param
         expect(response).to have_http_status(200)
         project_param.each_pair do |k, v|
@@ -1197,7 +1154,7 @@ describe API::Projects, api: true  do
       end
 
       it 'does not update visibility_level' do
-        project_param = { visibility_level: 20 }
+        project_param = { visibility_level: Gitlab::VisibilityLevel::PUBLIC }
         put api("/projects/#{project3.id}", user4), project_param
         expect(response).to have_http_status(403)
       end