diff --git a/requirements-dev.txt b/requirements-dev.txt
index 5924a553..348c4bde 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -11,8 +11,9 @@ dash_auth==1.4.1
 dash_core_components==1.15.0
 dash_html_components==1.1.2
 dash_renderer==1.9.0
+Werkzeug<=2.0.3
 solvebio==2.12.0
 pyyaml==5.3.1
 click==7.1.2
 ruamel.yaml==0.16.12
-pytest
\ No newline at end of file
+pytest
diff --git a/solvebio/global_search.py b/solvebio/global_search.py
index 22d77711..ed79b51e 100644
--- a/solvebio/global_search.py
+++ b/solvebio/global_search.py
@@ -23,6 +23,8 @@ def __init__(
             query=None,
             filters=None,
             entities=None,
+            entities_match='any',
+            vault_scope='all',
             ordering=None,
             limit=float('inf'),
             page_size=QueryBase.DEFAULT_PAGE_SIZE,
@@ -37,6 +39,8 @@ def __init__(
           - `query` (optional): An optional query string (advanced search).
           - `filters` (optional): Filter or List of filter objects.
           - `entities` (optional): List of entity tuples to filter on (entity type, entity).
+          - `entities_match` (optional): Can be 'all' or 'any' (match any provided entity).
+          - `vault_scope` (optional): Can be 'all' or 'access'.
           - `ordering` (optional): List of fields to order the results by.
           - `limit` (optional): Maximum number of query results to return.
           - `page_size` (optional): Number of results to fetch per query page.
@@ -49,12 +53,13 @@ def __init__(
         self._data_url = '/v2/search'
         self._query = query
         self._entities = entities
+        self._entities_match = entities_match
+        self._vault_scope = vault_scope
         self._ordering = ordering
         self._result_class = result_class
         self._debug = debug
         self._raw_results = raw_results
         self._error = None
-        self._is_join = False
 
         if filters:
             if isinstance(filters, Filter):
@@ -97,6 +102,8 @@ def _clone(self, filters=None, entities=None, limit=None):
                              ordering=self._ordering,
                              page_size=self._page_size,
                              result_class=self._result_class,
+                             vault_scope=self._vault_scope,
+                             entities_match=self._entities_match,
                              debug=self._debug,
                              client=self._client)
         new._filters += self._filters
@@ -124,9 +131,6 @@ def __len__(self):
                  SELECT * FROM <table> [WHERE condition] [LIMIT number]
               )
         """
-        if self._is_join:
-            return len(self._buffer)
-
         return super(GlobalSearch, self).__len__()
 
     def _build_query(self, **kwargs):
@@ -148,6 +152,12 @@ def _build_query(self, **kwargs):
         if self._ordering is not None:
             q['ordering'] = self._ordering
 
+        if self._vault_scope is not None:
+            q['vault_scope'] = self._vault_scope
+
+        if self._entities_match is not None:
+            q['entities_match'] = self._entities_match
+
         if self._debug:
             q['debug'] = 'True'
 
@@ -158,14 +168,20 @@ def _build_query(self, **kwargs):
         return q
 
     def execute(self, offset=0, **query):
+        def _process_result(result):
+            # Internally the client uses object_type, not type
+            result['object_type'] = result['type']
+            if result['object_type'] == 'vault':
+                return Vault.construct_from(result)
+            else:
+                return Object.construct_from(result)
+
         # Call superclass method execute
         super(GlobalSearch, self).execute(offset, **query)
 
         # Cast logical objects from response to Object/Vault instances
         if not self._raw_results:
-            self._response['results'] = [Vault.construct_from(result) if result['type'] == 'vault'
-                                            else Object.construct_from(result)
-                                            for result in self._response['results']]
+            self._response['results'] = [_process_result(i) for i in self._response['results']]
 
     def entity(self, **kwargs):
         """
@@ -198,3 +214,12 @@ def subjects_count(self):
         gs.execute()
 
         return gs._response.get('subjects_count')
+
+    def vaults(self):
+        """Returns the list of vaults"""
+
+        # Executes a query to get a full API response which contains vaults list
+        gs = self.limit(0)
+        gs.execute(include_vaults=True)
+
+        return gs._response.get('vaults')
diff --git a/solvebio/resource/object.py b/solvebio/resource/object.py
index e48f30d2..7af07410 100644
--- a/solvebio/resource/object.py
+++ b/solvebio/resource/object.py
@@ -440,12 +440,9 @@ def __getattr__(self, name):
         try:
             return self[name]
         except KeyError as err:
-            # If the Object has a dataset_id, it is of object_type "dataset"
-            # If there is no dataset_id, either this Object is a file or folder
-            # or the resource has not yet been retrieved from the API.
-            if name in valid_dataset_attrs and self.dataset_id:
+            if name in valid_dataset_attrs and self['object_type'] == "dataset":
                 return getattr(
-                    Dataset(self.dataset_id, client=self._client), name)
+                    Dataset(self['id'], client=self._client), name)
 
             raise AttributeError(*err.args)
 
@@ -458,7 +455,7 @@ def dataset(self):
                 "Only dataset objects have a Dataset resource. This is a {}"
                 .format(self.object_type))
 
-        return Dataset.retrieve(self.dataset_id, client=self._client)
+        return Dataset.retrieve(self['id'], client=self._client)
 
     @property
     def parent(self):
@@ -560,7 +557,7 @@ def query(self, **params):
         from solvebio.query import QueryFile
 
         if self.is_dataset:
-            return Dataset(self.dataset_id, client=self._client).query(**params)
+            return Dataset(self['id'], client=self._client).query(**params)
         elif self.is_file:
             return QueryFile(self['id'], client=self._client, **params)
         else:
diff --git a/solvebio/test/test_dataset_migrations.py b/solvebio/test/test_dataset_migrations.py
index 0c39188c..397bf56a 100644
--- a/solvebio/test/test_dataset_migrations.py
+++ b/solvebio/test/test_dataset_migrations.py
@@ -117,10 +117,10 @@ def test_migration_target_dataset_object(self, Create):
         """Target is an Object of object_type=dataset"""
         Create.side_effect = fake_migration_create
 
-        source = self.client.Object(1, object_type='dataset')
-        source.dataset_id = source.id
-        target = self.client.Object(2, object_type='dataset')
-        target.dataset_id = target.id
+        source = self.client.Object(1)
+        source['object_type'] = 'dataset'
+        target = self.client.Object(2)
+        target['object_type'] = 'dataset'
         migration = source.migrate(target=target, follow=False)
         self.assertEqual(migration.source_id, source.id)
         self.assertEqual(migration.target_id, target.id)
