diff --git a/app/models/ee/user.rb b/app/models/ee/user.rb
index cd578d2905d754aa5d28142b30801f159df93e30..cf96291a0d28b7190c898fedd2eb9a82019f6cfd 100644
--- a/app/models/ee/user.rb
+++ b/app/models/ee/user.rb
@@ -15,6 +15,9 @@ module User
       # column directly.
       validate :auditor_requires_license_add_on, if: :auditor
       validate :cannot_be_admin_and_auditor
+
+      delegate :shared_runners_minutes_limit, :shared_runners_minutes_limit=,
+               to: :namespace
     end
 
     module ClassMethods
diff --git a/app/models/user.rb b/app/models/user.rb
index 331242488d978a5fa039a01bd114022e9176a281..6df0fce772e7bf95d8177cb42054fc2e44e12175 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -65,7 +65,7 @@ def update_tracked_fields!(request)
   #
 
   # Namespace for personal projects
-  has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id
+  has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, autosave: true
 
   # Profile
   has_many :keys, -> do
diff --git a/changelogs/unreleased-ee/add-api-shared_runners_minutes_limit.yml b/changelogs/unreleased-ee/add-api-shared_runners_minutes_limit.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0569c1045ef3f610bc1462bd10818820f0b017c5
--- /dev/null
+++ b/changelogs/unreleased-ee/add-api-shared_runners_minutes_limit.yml
@@ -0,0 +1,4 @@
+---
+title: Add shared_runners_minutes_limit to groups and users API
+merge_request: 1942
+author:
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 7b772a99bb50e39dfb481bc41601ebd4d83cc69b..b5bc627344f1d80edcd9f42c52c0e302eb781fb7 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -140,6 +140,7 @@ Example response:
   "full_name": "Twitter",
   "full_path": "twitter",
   "parent_id": null,
+  "shared_runners_minutes_limit": 133,
   "projects": [
     {
       "id": 7,
@@ -290,6 +291,7 @@ Parameters:
 - `lfs_enabled` (optional)      - Enable/disable Large File Storage (LFS) for the projects in this group
 - `request_access_enabled` (optional) - Allow users to request member access.
 - `parent_id` (optional) - The parent group id for creating nested group.
+- `shared_runners_minutes_limit` (optional) - (admin-only) Pipeline minutes quota for this group
 
 ## Transfer project to group
 
@@ -323,6 +325,7 @@ PUT /groups/:id
 | `visibility` | string | no | The visibility level of the group. Can be `private`, `internal`, or `public`. |
 | `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group |
 | `request_access_enabled` | boolean | no | Allow users to request member access. |
+| `shared_runners_minutes_limit` | integer | no | (admin-only) Pipeline minutes quota for this group |
 
 ```bash
 curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/5?name=Experimental"
diff --git a/doc/api/users.md b/doc/api/users.md
index 8b57f8c0aaeaab22ddfa46a1ac5e288c2672904f..76ce729c399d4377546c3103b33ffb96b2e6617b 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -220,7 +220,8 @@ Parameters:
   "can_create_group": true,
   "can_create_project": true,
   "two_factor_enabled": true,
-  "external": false
+  "external": false,
+  "shared_runners_minutes_limit": 133
 }
 ```
 
@@ -253,6 +254,7 @@ Parameters:
 - `can_create_group` (optional) - User can create groups - true or false
 - `confirm` (optional)          - Require confirmation - true (default) or false
 - `external` (optional)         - Flags the user as external - true or false(default)
+- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user
 
 ## User modification
 
@@ -281,6 +283,7 @@ Parameters:
 - `admin` (optional)            - User is admin - true or false (default)
 - `can_create_group` (optional) - User can create groups - true or false
 - `external` (optional)         - Flags the user as external - true or false(default)
+- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user
 
 On password update, user will be forced to change it upon next login.
 Note, at the moment this method does only return a `404` error,
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index c4e767bcd29134f808670d3dcd1d5dbbff7c7ece..e4e6862d8fe7b25ac386f43297570720c476d274 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -41,6 +41,9 @@ class UserPublic < User
       expose :can_create_project?, as: :can_create_project
       expose :two_factor_enabled?, as: :two_factor_enabled
       expose :external
+
+      # EE-only
+      expose :shared_runners_minutes_limit
     end
 
     class UserWithPrivateDetails < UserPublic
@@ -188,6 +191,9 @@ class Group < Grape::Entity
     class GroupDetail < Group
       expose :projects, using: Entities::Project
       expose :shared_projects, using: Entities::Project
+
+      # EE-only
+      expose :shared_runners_minutes_limit
     end
 
     class RepoCommit < Grape::Entity
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index d1d80cabf2b0f738966c80296aa5689448ad4d2a..4b3abc7a752c8c6465256eadf96aca49db73b37e 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -17,6 +17,7 @@ class Groups < Grape::API
         optional :membership_lock, type: Boolean, desc: 'Prevent adding new members to project membership within this group'
         optional :ldap_cn, type: String, desc: 'LDAP Common Name'
         optional :ldap_access, type: Integer, desc: 'A valid access level'
+        optional :shared_runners_minutes_limit, type: Integer, desc: '(admin-only) Pipeline minutes quota for this group'
         all_or_none_of :ldap_cn, :ldap_access
       end
 
@@ -89,6 +90,9 @@ def present_groups(groups, options = {})
           group_access: params.delete(:ldap_access)
         }
 
+        # EE
+        authenticated_as_admin! if params[:shared_runners_minutes_limit]
+
         group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute
 
         if group.persisted?
@@ -100,7 +104,7 @@ def present_groups(groups, options = {})
             )
           end
 
-          present group, with: Entities::Group, current_user: current_user
+          present group, with: Entities::GroupDetail, current_user: current_user
         else
           render_api_error!("Failed to save group #{group.errors.messages}", 400)
         end
@@ -118,13 +122,18 @@ def present_groups(groups, options = {})
         optional :name, type: String, desc: 'The name of the group'
         optional :path, type: String, desc: 'The path of the group'
         use :optional_params
-        at_least_one_of :name, :path, :description, :visibility,
-                        :lfs_enabled, :request_access_enabled
       end
       put ':id' do
         group = find_group!(params[:id])
         authorize! :admin_group, group
 
+        # EE
+        if params[:shared_runners_minutes_limit].present? &&
+            group.shared_runners_minutes_limit.to_i !=
+                params[:shared_runners_minutes_limit].to_i
+          authenticated_as_admin!
+        end
+
         if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
           present group, with: Entities::GroupDetail, current_user: current_user
         else
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 9d436fc1d91d9fb74fab7b1b66aa6feb11a44f2d..387fb4c50095b1e77a9f5f4e120c44d628220136 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -30,6 +30,9 @@ def find_user(params)
           optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed'
           optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
           all_or_none_of :extern_uid, :provider
+
+          # EE
+          optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this user'
         end
       end
 
@@ -127,10 +130,6 @@ def find_user(params)
         optional :name, type: String, desc: 'The name of the user'
         optional :username, type: String, desc: 'The username of the user'
         use :optional_attributes
-        at_least_one_of :email, :password, :name, :username, :skype, :linkedin,
-                        :twitter, :website_url, :organization, :projects_limit,
-                        :extern_uid, :provider, :bio, :location, :admin,
-                        :can_create_group, :confirm, :external
       end
       put ":id" do
         authenticated_as_admin!
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 61c239f3051059dc25de50a0b06e9ec64a8eb14c..53343606b6a82589dbfbf75559de519d807ec89b 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -13,6 +13,14 @@
     it { is_expected.to include_module(TokenAuthenticatable) }
   end
 
+  describe 'delegations' do
+    it { is_expected.to delegate_method(:path).to(:namespace).with_prefix }
+
+    # EE
+    it { is_expected.to delegate_method(:shared_runners_minutes_limit).to(:namespace) }
+    it { is_expected.to delegate_method(:shared_runners_minutes_limit=).to(:namespace).with_arguments(133) }
+  end
+
   describe 'associations' do
     it { is_expected.to have_one(:namespace) }
     it { is_expected.to have_many(:snippets).dependent(:destroy) }
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 33fbf8a413a1a76bfffa8279032285f3c9fede49..5204844c55070dd8f2aa085ec505dcb41692cc61 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -272,6 +272,25 @@
 
         expect(response).to have_http_status(404)
       end
+
+      # EE
+      it 'returns 403 for updating shared_runners_minutes_limit' do
+        expect do
+          put api("/groups/#{group1.id}", user1), shared_runners_minutes_limit: 133
+        end.not_to change { group1.shared_runners_minutes_limit }
+
+        expect(response).to have_http_status(403)
+      end
+
+      it 'returns 200 if shared_runners_minutes_limit is not changing' do
+        group1.update(shared_runners_minutes_limit: 133)
+
+        expect do
+          put api("/groups/#{group1.id}", user1), shared_runners_minutes_limit: 133
+        end.not_to change { group1.shared_runners_minutes_limit }
+
+        expect(response).to have_http_status(200)
+      end
     end
 
     context 'when authenticated as the admin' do
@@ -281,6 +300,17 @@
         expect(response).to have_http_status(200)
         expect(json_response['name']).to eq(new_group_name)
       end
+
+      # EE
+      it 'updates the group for shared_runners_minutes_limit' do
+        expect do
+          put api("/groups/#{group1.id}", admin), shared_runners_minutes_limit: 133
+        end.to change { group1.reload.shared_runners_minutes_limit }
+          .from(nil).to(133)
+
+        expect(response).to have_http_status(200)
+        expect(json_response['shared_runners_minutes_limit']).to eq(133)
+      end
     end
 
     context 'when authenticated as an user that can see the group' do
@@ -479,23 +509,36 @@
         group_attributes = attributes_for(:group, ldap_cn: 'ldap-group', ldap_access: Gitlab::Access::DEVELOPER)
         expect { post api("/groups", admin), group_attributes }.to change{ LdapGroupLink.count }.by(1)
       end
-    end
-  end
 
-  describe "PUT /groups" do
-    context "when authenticated as user without group permissions" do
-      it "does not create group" do
-        put api("/groups/#{group2.id}", user1), attributes_for(:group)
-        expect(response.status).to eq(404)
-      end
-    end
+      # EE
+      context 'when shared_runners_minutes_limit is given' do
+        context 'when the current user is not an admin' do
+          it "does not create a group with shared_runners_minutes_limit" do
+            group = attributes_for(:group, { shared_runners_minutes_limit: 133 })
 
-    context "when authenticated as user with group permissions" do
-      it "updates group" do
-        group2.update(owner: user2)
-        put api("/groups/#{group2.id}", user2), { name: 'Renamed' }
-        expect(response.status).to eq(200)
-        expect(group2.reload.name).to eq('Renamed')
+            expect do
+              post api("/groups", user3), group
+            end.not_to change { Group.count }
+
+            expect(response).to have_http_status(403)
+          end
+        end
+
+        context 'when the current user is an admin' do
+          it "creates a group with shared_runners_minutes_limit" do
+            group = attributes_for(:group, { shared_runners_minutes_limit: 133 })
+
+            expect do
+              post api("/groups", admin), group
+            end.to change { Group.count }.by(1)
+
+            created_group = Group.find(json_response['id'])
+
+            expect(created_group.shared_runners_minutes_limit).to eq(133)
+            expect(response).to have_http_status(201)
+            expect(json_response['shared_runners_minutes_limit']).to eq(133)
+          end
+        end
       end
     end
   end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index de5995c33128657dfe54ff86592406b1da8fa17f..f4699da829bb701d94dfadcf84c6a8ebeb86fe9c 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -425,6 +425,17 @@
       expect(user.reload.external?).to be_truthy
     end
 
+    # EE
+    it "updates shared_runners_minutes_limit" do
+      expect do
+        put api("/users/#{user.id}", admin), { shared_runners_minutes_limit: 133 }
+      end.to change { user.reload.shared_runners_minutes_limit }
+        .from(nil).to(133)
+
+      expect(response).to have_http_status(200)
+      expect(json_response['shared_runners_minutes_limit']).to eq(133)
+    end
+
     it "does not update admin status" do
       put api("/users/#{admin_user.id}", admin), { can_create_group: false }
       expect(response).to have_http_status(200)
@@ -438,9 +449,22 @@
       expect(user.reload.email).not_to eq('invalid email')
     end
 
-    it "is not available for non admin users" do
-      put api("/users/#{user.id}", user), attributes_for(:user)
-      expect(response).to have_http_status(403)
+    context 'when the current user is not an admin' do
+      it "is not available" do
+        expect do
+          put api("/users/#{user.id}", user), attributes_for(:user)
+        end.not_to change { user.reload.attributes }
+
+        expect(response).to have_http_status(403)
+      end
+
+      it "cannot update their own shared_runners_minutes_limit" do
+        expect do
+          put api("/users/#{user.id}", user), { shared_runners_minutes_limit: 133 }
+        end.not_to change { user.reload.shared_runners_minutes_limit }
+
+        expect(response).to have_http_status(403)
+      end
     end
 
     it "returns 404 for non-existing user" do