diff --git a/acquisitions.py b/acquisitions.py
index 951848c8e747b13997c8ddb7e0e6fae3a77703b1..0946291eeb58ba9a8e7aeb1eb062600c31a15315 100644
--- a/acquisitions.py
+++ b/acquisitions.py
@@ -36,7 +36,7 @@ class Acquisitions(base.RequestHandler):
         projection = {'label': 1, 'description': 1, 'types': 1, 'notes': 1}
         if self.public_request:
             query['public'] = True
-        elif not self.superuser:
+        elif not self.superuser_request:
             query['permissions'] = {'$elemMatch': {'uid': self.uid, 'site': self.source_site}}
             projection['permissions'] = {'$elemMatch': {'uid': self.uid, 'site': self.source_site}}
         acquisitions = list(self.dbc.find(query, projection))
diff --git a/base.py b/base.py
index d6a883847d0ca630b3ebb40e0b87448e944698d7..9b1137ea4a04de11af9cf1a5972ab3f39f3e1ea0 100644
--- a/base.py
+++ b/base.py
@@ -38,6 +38,13 @@ ROLES = [
 INTEGER_ROLES = {r['rid']: r['sort'] for r in ROLES}
 
 
+def mongo_dict(d):
+    def _mongo_list(d, pk=''):
+        pk = pk and pk + '.'
+        return sum([_mongo_list(v, pk+k) if isinstance(v, dict) else [(pk+k, v)] for k, v in d.iteritems()], [])
+    return dict(_mongo_list(d))
+
+
 class RequestHandler(webapp2.RequestHandler):
 
     """fetches pubkey from own self.db.remotes. needs to be aware of OWN site uid"""
@@ -112,11 +119,13 @@ class RequestHandler(webapp2.RequestHandler):
             if not self.app.db.remotes.find_one({'_id': remote_instance}):
                 self.abort(402, remote_instance + ' is not authorized')
         self.public_request = not bool(self.uid)
-        if not self.public_request:
-            user = self.app.db.users.find_one({'_id': self.uid}, ['superuser'])
+        if self.public_request or self.source_site:
+            self.superuser_request = False
+        else:
+            user = self.app.db.users.find_one({'_id': self.uid}, ['root', 'wheel'])
             if not user:
                 self.abort(403, 'user ' + self.uid + ' does not exist')
-        self.superuser = not self.public_request and not self.source_site and user.get('superuser')
+            self.superuser_request = user.get('root') and user.get('wheel')
 
     def dispatch(self):
         """dispatching and request forwarding"""
@@ -161,9 +170,12 @@ class RequestHandler(webapp2.RequestHandler):
             headers['Access-Control-Allow-Origin'] = self.response.headers['Access-Control-Allow-Origin']
         webapp2.abort(code, *args, **kwargs)
 
+    def handle_exception(self, exception, debug):
+        self.abort(500, exception.message)
+
     def options(self, *args, **kwargs):
         self.response.headers['Access-Control-Allow-Methods'] = 'GET, HEAD, POST, PUT, DELETE, OPTIONS'
-        self.response.headers['Access-Control-Allow-Headers'] = 'Authorization'
+        self.response.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type'
         self.response.headers['Access-Control-Max-Age'] = '151200'
 
     def schema(self, updates={}):
@@ -182,7 +194,7 @@ class Container(RequestHandler):
             if not container.get('public', False):
                 self.abort(403, 'this ' + self.__class__.__name__ + 'is not public')
             del container['permissions']
-        elif not self.superuser:
+        elif not self.superuser_request:
             user_perm = None
             for perm in container['permissions']:
                 if perm['uid'] == self.uid and perm.get('site') == self.source_site:
@@ -200,7 +212,7 @@ class Container(RequestHandler):
 class AcquisitionAccessChecker(object):
 
     def check_acq_list(self, acq_ids):
-        if not self.superuser:
+        if not self.superuser_request:
             for a_id in acq_ids:
                 agg_res = self.app.db.acquisitions.aggregate([
                         {'$match': {'_id': a_id}},
diff --git a/bootstrap.json.sample b/bootstrap.json.sample
index 2afa1d3d3b16fadb5369225ce4a4d289462d8726..ac13d52541be0e3f088d39b710695a7fc927e603 100644
--- a/bootstrap.json.sample
+++ b/bootstrap.json.sample
@@ -17,7 +17,7 @@
       "email": "user1@example.com",
       "firstname": "First",
       "lastname": "User",
-      "superuser": true
+      "wheel": true
     }
   ]
 }
diff --git a/projects.py b/projects.py
index 637edca7222805de605dab61282fcef5b0607b4b..a1cdb91583bdebd64fbdfe51994c72d85ddc0a97 100644
--- a/projects.py
+++ b/projects.py
@@ -31,7 +31,7 @@ class Projects(base.RequestHandler):
         query = None
         if self.public_request:
             query = {'public': True}
-        elif not self.superuser:
+        elif not self.superuser_request:
             projection['permissions'] = {'$elemMatch': {'uid': self.uid, 'site': self.source_site}}
             if self.request.get('admin').lower() in ('1', 'true'):
                 query = {'permissions': {'$elemMatch': {'uid': self.uid, 'site': self.source_site, 'access': 'admin'}}}
diff --git a/sessions.py b/sessions.py
index 51d15eb764622ce33b6b7133fc9670920f183f35..e78efef8540fad7354fe0d93d4b4c1574159844e 100644
--- a/sessions.py
+++ b/sessions.py
@@ -36,7 +36,7 @@ class Sessions(base.RequestHandler):
         projection = {'label': 1, 'subject.code': 1, 'notes': 1}
         if self.public_request:
             query['public'] = True
-        elif not self.superuser:
+        elif not self.superuser_request:
             query['permissions'] = {'$elemMatch': {'uid': self.uid, 'site': self.source_site}}
             projection['permissions'] = {'$elemMatch': {'uid': self.uid, 'site': self.source_site}}
         sessions =  list(self.dbc.find(query, projection))
diff --git a/users.py b/users.py
index 47ea5bd6cb6140109030504d3a303681371de156..58f1efdd351b95061d37cac24839e186902c80bf 100644
--- a/users.py
+++ b/users.py
@@ -28,14 +28,12 @@ class Users(base.RequestHandler):
         """Create a new User"""
         if self.public_request: # FIXME: who is allowed to create a new user?
             self.abort(403, 'must be logged in to create new user')
-        schema = copy.deepcopy(User.json_schema)
-        schema['required'] += ['email', 'firstname', 'lastname']
-        json_body = self.request.json_body
         try:
-            jsonschema.validate(json_body, schema)
+            json_body = self.request.json_body
+            jsonschema.validate(json_body, User.json_schema)
             json_body['email_hash'] = hashlib.md5(json_body['email']).hexdigest()
             self.dbc.insert(json_body)
-        except jsonschema.ValidationError as e:
+        except (ValueError, jsonschema.ValidationError) as e:
             self.abort(400, str(e))
         except pymongo.errors.DuplicateKeyError as e:
             self.abort(400, 'User ID %s already exists' % json_body['_id'])
@@ -44,7 +42,7 @@ class Users(base.RequestHandler):
         """Return the list of Users."""
         if self.public_request:
             self.abort(403, 'must be logged in to retrieve User list')
-        users = list(self.dbc.find({}, ['firstname', 'lastname', 'email_hash', 'superuser']))
+        users = list(self.dbc.find({}, ['firstname', 'lastname', 'email_hash', 'wheel']))
         if self.debug:
             for user in users:
                 user['details'] = self.uri_for('user', _id=str(user['_id']), _full=True) + '?' + self.request.query_string
@@ -80,8 +78,12 @@ class User(base.RequestHandler):
             'email_hash': {
                 'type': 'string',
             },
-            'superuser': {
-                'title': 'Superuser',
+            'root': {
+                'title': 'Root',
+                'type': 'boolean',
+            },
+            'wheel': {
+                'title': 'Wheel',
                 'type': 'boolean',
             },
             'preferences': {
@@ -95,7 +97,7 @@ class User(base.RequestHandler):
                 },
             },
         },
-        'required': ['_id'],
+        'required': ['_id', 'email', 'firstname', 'lastname'],
         'additionalProperties': False,
     }
 
@@ -105,7 +107,9 @@ class User(base.RequestHandler):
 
     def self(self):
         """Return details for the current User."""
-        return self.dbc.find_one({'_id': self.uid}, ['firstname', 'lastname', 'superuser', 'preferences'])
+        user = self.dbc.find_one({'_id': self.uid}, ['firstname', 'lastname', 'root', 'wheel', 'preferences', 'email_hash'])
+        user.setdefault('preferences', {})
+        return user
 
     def get(self, _id):
         """ Return User details."""
@@ -119,7 +123,7 @@ class User(base.RequestHandler):
         user = self.dbc.find_one({'_id': _id}, projection or None)
         if not user:
             self.abort(404, 'no such User')
-        if self.debug and (self.superuser or _id == self.uid):
+        if self.debug and (self.superuser_request or _id == self.uid):
             user['groups'] = self.uri_for('groups', _id=_id, _full=True) + '?' + self.request.query_string
         return user
 
@@ -127,26 +131,23 @@ class User(base.RequestHandler):
         """Update an existing User."""
         user = self.dbc.find_one({'_id': _id})
         if not user:
-            self.abort(404)
-        if _id == self.uid or self.superuser: # users can only update their own info
-            updates = {'$set': {'_id': _id}, '$unset': {'__null__': ''}}
-            for k, v in self.request.params.iteritems():
-                if k != 'superuser' and k in []:#user_fields:
-                    updates['$set'][k] = v # FIXME: do appropriate type conversion
-                elif k == 'superuser' and _id == self.uid and self.superuser is not None: # toggle superuser for requesting user
-                    updates['$set'][k] = v.lower() in ('1', 'true')
-                elif k == 'superuser' and _id != self.uid and self.superuser:             # enable/disable superuser for other user
-                    if v.lower() in ('1', 'true') and user.get('superuser') is None:
-                        updates['$set'][k] = False # superuser is tri-state: False indicates granted, but disabled, superuser privileges
-                    elif v.lower() not in ('1', 'true'):
-                        updates['$unset'][k] = ''
-            self.dbc.update({'_id': _id}, updates)
-        else:
-            self.abort(403)
+            self.abort(404, 'no such User')
+        if not self.superuser_request and _id != self.uid:
+            self.abort(403, 'must be superuser to update another User')
+        schema = copy.deepcopy(self.json_schema)
+        del schema['required']
+        try:
+            json_body = self.request.json_body
+            jsonschema.validate(json_body, schema)
+        except (ValueError, jsonschema.ValidationError) as e:
+            self.abort(400, str(e))
+        if 'wheel' in json_body and _id == self.uid:
+            self.abort(400, 'user cannot alter own superuser privilege')
+        self.dbc.update({'_id': _id}, {'$set': base.mongo_dict(json_body)})
 
     def delete(self, _id):
         """Delete a User."""
-        if not self.superuser:
+        if not self.superuser_request:
             self.abort(403, 'must be superuser to delete a User')
         self.dbc.remove({'_id': _id})
 
@@ -167,11 +168,11 @@ class Groups(base.RequestHandler):
         """Return the list of Groups."""
         query = None
         if _id is not None:
-            if _id != self.uid and not self.superuser:
+            if _id != self.uid and not self.superuser_request:
                 self.abort(403, 'User ' + self.uid + ' may not see the Groups of User ' + _id)
             query = {'roles.uid': _id}
         else:
-            if not self.superuser:
+            if not self.superuser_request:
                 if self.request.get('admin').lower() in ('1', 'true'):
                     query = {'roles': {'$elemMatch': {'uid': self.uid, 'access': 'admin'}}}
                 else:
@@ -228,7 +229,7 @@ class Group(base.RequestHandler):
         group = self.app.db.groups.find_one({'_id': _id})
         if not group:
             self.abort(404, 'no such Group: ' + _id)
-        if not self.superuser:
+        if not self.superuser_request:
             group = self.app.db.groups.find_one({'_id': _id, 'roles': {'$elemMatch': {'uid': self.uid, 'access': 'admin'}}})
             if not group:
                 self.abort(403, 'User ' + self.uid + ' is not an admin of Group ' + _id)