diff --git a/examples/delete_model.py b/examples/delete_models.py
similarity index 92%
rename from examples/delete_model.py
rename to examples/delete_models.py
index c50186c..1d4616d 100644
--- a/examples/delete_model.py
+++ b/examples/delete_models.py
@@ -15,7 +15,6 @@
 """Delete the given Rel model from the given database."""
 
 from argparse import ArgumentParser
-import json
 from urllib.request import HTTPError
 from railib import api, config, show
 
@@ -23,8 +22,8 @@
 def run(database: str, engine: str, model: str, profile: str):
     cfg = config.read(profile=profile)
     ctx = api.Context(**cfg)
-    rsp = api.delete_model(ctx, database, engine, model)
-    print(json.dumps(rsp, indent=2))
+    rsp = api.delete_models(ctx, database, engine, [model])
+    print(rsp)
 
 
 if __name__ == "__main__":
diff --git a/examples/install_model.py b/examples/install_models.py
similarity index 93%
rename from examples/install_model.py
rename to examples/install_models.py
index 8d6937e..c447288 100644
--- a/examples/install_model.py
+++ b/examples/install_models.py
@@ -15,7 +15,6 @@
 """Install the given Rel model in the given database"""
 
 from argparse import ArgumentParser
-import json
 from os import path
 from urllib.request import HTTPError
 from railib import api, config, show
@@ -32,8 +31,8 @@ def run(database: str, engine: str, fname: str, profile: str):
         models[_sansext(fname)] = fp.read()  # model name => model
     cfg = config.read(profile=profile)
     ctx = api.Context(**cfg)
-    rsp = api.install_model(ctx, database, engine, models)
-    print(json.dumps(rsp, indent=2))
+    rsp = api.install_models(ctx, database, engine, models)
+    print(rsp)
 
 
 if __name__ == "__main__":
diff --git a/examples/run-all b/examples/run-all
index 07810dd..7fc9a39 100755
--- a/examples/run-all
+++ b/examples/run-all
@@ -36,7 +36,7 @@ python3 ./show_results.py $DATABASE $ENGINE
 python3 ./show_problems.py $DATABASE $ENGINE
 
 # load model
-python3 ./install_model.py $DATABASE $ENGINE hello.rel
+python3 ./install_models.py $DATABASE $ENGINE hello.rel
 python3 ./get_model.py $DATABASE $ENGINE hello
 python3 ./list_models.py $DATABASE $ENGINE
 python3 ./delete_model.py $DATABASE $ENGINE hello
@@ -60,7 +60,7 @@ python3 ./list_edbs.py $DATABASE $ENGINE
 python3 ./delete_database.py $DATABASE
 python3 ./create_database.py $DATABASE
 python3 ./load_json.py $DATABASE $ENGINE sample.json -r sample_json
-python3 ./install_model.py $DATABASE $ENGINE hello.rel
+python3 ./install_models.py $DATABASE $ENGINE hello.rel
 python3 ./clone_database.py $DATABASE_CLONE $DATABASE
 python3 ./get_database.py $DATABASE_CLONE
 python3 ./list_databases.py
diff --git a/railib/api.py b/railib/api.py
index 378b53d..1cf4e8b 100644
--- a/railib/api.py
+++ b/railib/api.py
@@ -19,9 +19,13 @@
 import time
 import re
 import io
+import sys
+import random
+
 from enum import Enum, unique
 from typing import List, Union
 from requests_toolbelt import multipart
+
 from . import rest
 
 from .pb.message_pb2 import MetadataInfo
@@ -114,7 +118,7 @@ class Permission(str, Enum):
     "create_oauth_client",
     "delete_database",
     "delete_engine",
-    "delete_model",
+    "delete_models",
     "disable_user",
     "enable_user",
     "delete_oauth_client",
@@ -644,18 +648,6 @@ def run(self, ctx: Context, command: str, language: str, inputs: dict = None, **
         raise Exception("invalid response type")
 
 
-def _delete_model_action(name: str) -> dict:
-    return {"type": "ModifyWorkspaceAction", "delete_source": [name]}
-
-
-def _install_model_action(name: str, model: str) -> dict:
-    return {"type": "InstallAction", "sources": [_model(name, model)]}
-
-
-def _list_action():
-    return {"type": "ListSourceAction"}
-
-
 def _list_edb_action():
     return {"type": "ListEdbAction"}
 
@@ -703,17 +695,6 @@ def _model(name: str, model: str) -> dict:
     }
 
 
-# Returns full list of models.
-def _list_models(ctx: Context, database: str, engine: str) -> dict:
-    tx = Transaction(database, engine, mode=Mode.OPEN)
-    rsp = tx.run(ctx, _list_action())
-    actions = rsp["actions"]
-    assert len(actions) == 1
-    action = actions[0]
-    models = action["result"]["sources"]
-    return models
-
-
 def create_database(ctx: Context, database: str, source: str = None, **kwargs) -> dict:
     data = {"name": database, "source_name": source}
     url = _mkurl(ctx, PATH_DATABASE)
@@ -721,25 +702,84 @@ def create_database(ctx: Context, database: str, source: str = None, **kwargs) -
     return json.loads(rsp.read())
 
 
-def delete_model(ctx: Context, database: str, engine: str, model: str) -> dict:
-    tx = Transaction(database, engine, mode=Mode.OPEN, readonly=False)
-    actions = [_delete_model_action(model)]
-    return tx.run(ctx, *actions)
+# Returns full list of models.
+def list_models(ctx: Context, database: str, engine: str) -> List:
+    models = []
+    out_name = f'model{random.randint(0, sys.maxsize)}'
+    resp = exec(ctx, database, engine, f'def output:{out_name}[name] = rel:catalog:model(name, _)')
+    for result in resp.results:
+        if f'/:output/:{out_name}' in result['relationId']:
+            table = result['table'].to_pydict()
+            models.extend([table['v1'][i] for i in range(1, len(table['v1']))])
+
+    return models
+
+
+def delete_model(ctx: Context, database: str, engine: str, model: str) -> TransactionAsyncResponse:
+    return delete_models(ctx, database, engine, [model])
+
+
+def delete_models(ctx: Context, database: str, engine: str, models: List[str]) -> TransactionAsyncResponse:
+    queries = [
+        f'def delete:rel:catalog:model["{model_name}"] = rel:catalog:model["{model_name}"]'
+        for model_name in models
+    ]
+    return exec(ctx, database, engine, '\n'.join(queries), readonly=False)
+
+
+def delete_model_async(ctx: Context, database: str, engine: str, model: str) -> TransactionAsyncResponse:
+    return delete_models_async(ctx, database, engine, [model])
+
+
+def delete_models_async(ctx: Context, database: str, engine: str, models: List[str]) -> TransactionAsyncResponse:
+    queries = [
+        f'def delete:rel:catalog:model["{model_name}"] = rel:catalog:model["{model_name}"]'
+        for model_name in models
+    ]
+    return exec_async(ctx, database, engine, '\n'.join(queries), readonly=False)
 
 
 # Returns the named model
 def get_model(ctx: Context, database: str, engine: str, name: str) -> str:
-    models = _list_models(ctx, database, engine)
-    for model in models:
-        if model["name"] == name:
-            return model["value"]
+    out_name = f'model{random.randint(0, sys.maxsize)}'
+    cmd = f'def output:{out_name} = rel:catalog:model["{name}"]'
+    resp = exec(ctx, database, engine, cmd)
+    for result in resp.results:
+        if f'/:output/:{out_name}' in result['relationId']:
+            table = result['table'].to_pydict()
+            return table['v1'][0]
     raise Exception(f"model '{name}' not found")
 
 
-def install_model(ctx: Context, database: str, engine: str, models: dict) -> dict:
-    tx = Transaction(database, engine, mode=Mode.OPEN, readonly=False)
-    actions = [_install_model_action(name, model) for name, model in models.items()]
-    return tx.run(ctx, *actions)
+def install_models(ctx: Context, database: str, engine: str, models: dict) -> TransactionAsyncResponse:
+    queries = []
+    queries_inputs = {}
+    randint = random.randint(0, sys.maxsize)
+    index = 0
+    for name, value in models.items():
+        input_name = f'input_{randint}_{index}'
+        queries.append(f'def delete:rel:catalog:model["{name}"] = rel:catalog:model["{name}"]')
+        queries.append(f'def insert:rel:catalog:model["{name}"] = {input_name}')
+
+        queries_inputs[input_name] = value
+        index += 1
+
+    return exec(ctx, database, engine, '\n'.join(queries), inputs=queries_inputs, readonly=False)
+
+
+def install_models_async(ctx: Context, database: str, engine: str, models: dict) -> TransactionAsyncResponse:
+    queries = []
+    queries_inputs = {}
+    randint = random.randint(0, sys.maxsize)
+    index = 0
+    for name, value in models.items():
+        input_name = f'input_{randint}_{index}'
+        queries.append(f'def delete:rel:catalog:model["{name}"] = rel:catalog:model["{name}"]')
+        queries.append(f'def insert:rel:catalog:model["{name}"] = {input_name}')
+
+        queries_inputs[input_name] = value
+        index += 1
+    return exec_async(ctx, database, engine, '\n'.join(queries), inputs=queries_inputs, readonly=False)
 
 
 def list_edbs(ctx: Context, database: str, engine: str) -> list:
@@ -752,12 +792,6 @@ def list_edbs(ctx: Context, database: str, engine: str) -> list:
     return rels
 
 
-# Returns a list of models installed in the given database.
-def list_models(ctx: Context, database: str, engine: str) -> list:
-    models = _list_models(ctx, database, engine)
-    return [model["name"] for model in models]
-
-
 # Generate a rel literal relation for the given dict.
 def _gen_literal_dict(items: dict) -> str:
     result = []
diff --git a/test/test_integration.py b/test/test_integration.py
index bb45708..9161592 100644
--- a/test/test_integration.py
+++ b/test/test_integration.py
@@ -68,11 +68,32 @@ def test_v2_exec(self):
         # results
         self.assertEqual(
             {
-                'v1': [
-                    1, 2, 3, 4, 5], 'v2': [
-                    1, 4, 9, 16, 25], 'v3': [
-                    1, 8, 27, 64, 125], 'v4': [
-                        1, 16, 81, 256, 625]}, rsp.results[0]["table"].to_pydict())
+                'v1': [1, 2, 3, 4, 5],
+                'v2': [1, 4, 9, 16, 25],
+                'v3': [1, 8, 27, 64, 125],
+                'v4': [1, 16, 81, 256, 625]
+            },
+            rsp.results[0]["table"].to_pydict())
+
+    def test_models(self):
+        models = api.list_models(ctx, dbname, engine)
+        self.assertTrue(len(models) > 0)
+
+        models = {'test_model': 'def foo=:bar'}
+        resp = api.install_models(ctx, dbname, engine, models)
+        self.assertEqual(resp.transaction['state'], 'COMPLETED')
+
+        value = api.get_model(ctx, dbname, engine, 'test_model')
+        self.assertEqual(models['test_model'], value)
+
+        models = api.list_models(ctx, dbname, engine)
+        self.assertTrue('test_model' in models)
+
+        resp = api.delete_models(ctx, dbname, engine, ['test_model'])
+        self.assertEqual(resp.transaction['state'], 'COMPLETED')
+
+        models = api.list_models(ctx, dbname, engine)
+        self.assertFalse('test_model' in models)
 
     def tearDown(self):
         api.delete_engine(ctx, engine)