diff --git a/agavepy/agave.py b/agavepy/agave.py
index 6245c49..3ade07f 100644
--- a/agavepy/agave.py
+++ b/agavepy/agave.py
@@ -20,7 +20,8 @@ import dateutil.parser
 import requests
 
 from agavepy.tenants import tenant_list
-from agavepy.clients import clients_create, clients_delete, clients_list
+from agavepy.clients import (clients_create, clients_delete, clients_list, 
+    clients_subscribe)
 from agavepy.tokens import token_create, refresh_token
 from agavepy.utils import load_config, save_config
 from agavepy.files import (files_copy, files_delete, files_download, 
@@ -683,6 +684,23 @@ class Agave(object):
             self.api_key, self.api_secret = "", ""
 
 
+    def clients_subscribe(self, api_name, api_version, api_provider, client_name=None):
+        """ Subscribe the oauth client to an api
+        """
+        # Set username.
+        if self.username == "" or self.username is None:
+            self.username = input("API username: ")
+
+        # If client_name is not set, then delete the current client, if it
+        # exists.
+        if client_name is None:
+            client_name = self.client_name
+
+        # Subscribe client.
+        clients_subscribe(self.username, client_name, self.api_server, 
+            api_name, api_version, api_provider)
+
+
     def clients_list(self):
         """ List all oauth clients
         """
diff --git a/agavepy/clients/__init__.py b/agavepy/clients/__init__.py
index cfa3ed8..7e2aedf 100644
--- a/agavepy/clients/__init__.py
+++ b/agavepy/clients/__init__.py
@@ -1,3 +1,4 @@
 from .create import clients_create
 from .delete import clients_delete
 from .list import clients_list
+from .subscribe import clients_subscribe
diff --git a/agavepy/clients/subscribe.py b/agavepy/clients/subscribe.py
new file mode 100644
index 0000000..0e21f6d
--- /dev/null
+++ b/agavepy/clients/subscribe.py
@@ -0,0 +1,41 @@
+"""
+    subscribe.py
+
+Subscribe to TACC apis
+"""
+import getpass
+import requests                                                                 
+from .exceptions import AgaveClientError                                        
+from ..utils import handle_bad_response_status_code
+
+
+
+def clients_subscribe(username, client_name, tenant_url, 
+        api_name, api_version, api_provider):
+    """ Subscribe an oauth client to an api
+    """
+    # Set request endpoint.
+    endpoint = "{}/clients/v2/{}/subscriptions".format(tenant_url, client_name)
+
+    # Get user's password.
+    passwd = getpass.getpass(prompt="API password: ")
+
+    # Make sure client_name is valid.
+    if client_name == "" or client_name is None:
+        raise AgaveClientError("Error creating client: invalid client_name")
+
+    # Make request.
+    try:
+        data = {
+            "apiName": api_name,
+            "apiVersion": api_version,
+            "apiProvider": api_provider,
+        }
+        resp = requests.post(endpoint, data=data, auth=(username, passwd))
+        del passwd
+    except Exception as err:
+        del passwd
+        raise AgaveClientError(err)
+
+    # Handle bad status code.
+    handle_bad_response_status_code(resp)
diff --git a/docs/docsite/authentication/clients.rst b/docs/docsite/authentication/clients.rst
index b3b4402..96d8d85 100644
--- a/docs/docsite/authentication/clients.rst
+++ b/docs/docsite/authentication/clients.rst
@@ -61,3 +61,17 @@ If you want to delete an oauth client, you can do as such:
 
 If you don't pass a client name to ``clients_delete``, then the ``Agave``
 object will try to delete the oauth client in its current session.
+
+
+Subscribing to an API
+#####################
+
+If you want to subscribe an oauth client to a TACC api, then you can do so as
+follows:
+
+.. code-block:: pycon
+
+    >>> ag.clients_subscribe("PublicKeys", "v2", "admin", client_name="client")
+
+If you don't set the ``client_name`` argument then the ``ag`` object will try
+to use the oauth client stored in your current session.

Implement a method to subscribe oauth clients to apis
Currently, there is no way to use `AgavePy` to subscribe oauth clients to services. 
`TACC-Cloud` requires this functionality.
diff --git a/tests/clients_test.py b/tests/clients_test.py
index 729f9ce..88aa14a 100644
--- a/tests/clients_test.py
+++ b/tests/clients_test.py
@@ -48,7 +48,7 @@ class MockServerClientEndpoints(BaseHTTPRequestHandler):
 
 
     def do_POST(self):
-        """ Mock agave client creation
+        """ Mock agave client creation and subscription
         """
         # Check that basic auth is used.
         authorization = self.headers.get("Authorization")
@@ -63,35 +63,37 @@ class MockServerClientEndpoints(BaseHTTPRequestHandler):
             headers = self.headers,
             environ={'REQUEST_METHOD': 'POST'})
 
-        # Check client name is set.
+        # Check parameters for client creation.
         client_name = form.getvalue("clientName", "")
-        if client_name == "":
-            self.send_response(400)
-            self.end_headers()
-            return
-
-        # Check client description is set.
         client_description = form.getvalue("description", "")
-        if client_description == "":
-            self.send_response(400)
-            self.end_headers()
-            return
+        client_tier = form.getvalue("tier", "")
+        creation = True
+        if client_name == "" or client_description == "" or client_tier == "":
+            creation = False
 
-        # CHeck client tier is set.
-        if form.getvalue("tier", "") == "":
-            self.send_response(400)
-            self.end_headers()
-            return
-
-        # Update response fields.
+        # Update response fields for client creation.
         sample_client_create_response["result"]["name"] = client_name
         sample_client_create_response["result"]["description"] = client_description
         sample_client_create_response["result"]["consumerKey"] = "some api key"
         sample_client_create_response["result"]["consumerSecret"] = "some secret"
 
-        self.send_response(200)
-        self.end_headers()
-        self.wfile.write(json.dumps(sample_client_create_response).encode())
+        # Check parameters for client subscription.
+        api_name = form.getvalue("apiName", "")
+        api_version = form.getvalue("apiVersion", "")
+        api_provider = form.getvalue("apiProvider", "")
+        subscription = True
+        if api_name == "" or api_version == "" or api_provider == "":
+            subscription = False
+
+        if creation or subscription:
+            self.send_response(200)
+            self.end_headers()
+            if creation:
+                self.wfile.write(json.dumps(sample_client_create_response).encode())
+        else:
+            self.send_response(400)
+            self.end_headers()
+        return
 
 
     def do_DELETE(self):
@@ -198,3 +200,25 @@ class TestMockServer(MockServer):
         out, err = capfd.readouterr()
         assert "DefaultApplication" in out
         assert "\"GET /clients/v2 HTTP/1.1\" 200" in err 
+
+
+    @patch("agavepy.agave.input")
+    @patch("agavepy.clients.subscribe.getpass.getpass")
+    def test_clients_subscribe(self, mock_input, mock_pass, capfd):
+        """ Test clients subscribe
+        """
+        # Patch username and password.
+        mock_input.return_value = "user"
+        mock_pass.return_value = "pass"
+
+        # Instantiate Agave object making reference to local mock server.
+        local_uri = "http://localhost:{port}".format(port=self.mock_server_port)
+        ag = Agave(api_server=local_uri)
+
+        # Subscribe a client.
+        ag.clients_subscribe("PublicKeys", "v2", "admin", client_name="client")
+
+        # Stdout should contain the putput from the command.
+        # Stderr will contain logs from the mock http server.
+        out, err = capfd.readouterr()
+        assert "200" in err

pip install -e .
parse_log_pytest
requirements.txt
pytest
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
-e git+https://github.com/TACC/agavepy.git@d5436fdb4c186e66a46853c7f2f30626f0f9410b#egg=agavepy
attrs==22.2.0
backports.ssl-match-hostname==3.7.0.1
certifi==2021.5.30
charset-normalizer==2.0.12
cloudpickle==2.2.1
configparser==5.2.0
future==1.0.0
idna==3.10
importlib-metadata==4.8.3
iniconfig==1.1.1
Jinja2==3.0.3
MarkupSafe==2.0.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
python-dateutil==2.9.0.post0
requests==2.27.1
requests-toolbelt==1.0.0
six==1.17.0
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
websocket-client==0.53.0
zipp==3.6.0

name: agavepy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - backports-ssl-match-hostname==3.7.0.1
      - charset-normalizer==2.0.12
      - cloudpickle==2.2.1
      - configparser==5.2.0
      - future==1.0.0
      - idna==3.10
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - jinja2==3.0.3
      - markupsafe==2.0.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - requests==2.27.1
      - requests-toolbelt==1.0.0
      - six==1.17.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - websocket-client==0.53.0
      - zipp==3.6.0
prefix: /opt/conda/envs/agavepy

tests/clients_test.py::TestMockServer::test_clients_subscribe
tests/clients_test.py::TestMockServer::test_client_create tests/clients_test.py::TestMockServer::test_client_delete tests/clients_test.py::TestMockServer::test_clients_list
docs/docsite/authentication/clients.rst agavepy/clients/subscribe.py agavepy/agave.py agavepy/clients/__init__.py
docs/docsite/authentication/clients.rst agavepy/clients/subscribe.py agavepy/agave.py agavepy/clients/__init__.py