diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py
index 2769e9648..3000aaa53 100644
--- a/cinderclient/api_versions.py
+++ b/cinderclient/api_versions.py
@@ -160,6 +160,9 @@ class APIVersion(object):
             return "%s.%s" % (self.ver_major, "latest")
         return "%s.%s" % (self.ver_major, self.ver_minor)
 
+    def get_major_version(self):
+        return "%s" % self.ver_major
+
 
 class VersionedMethod(object):
 
diff --git a/cinderclient/shell.py b/cinderclient/shell.py
index ecc586298..75e42e908 100644
--- a/cinderclient/shell.py
+++ b/cinderclient/shell.py
@@ -516,6 +516,21 @@ class OpenStackCinderShell(object):
         else:
             return argv
 
+    @staticmethod
+    def _validate_input_api_version(options):
+        if not options.os_volume_api_version:
+            api_version = api_versions.APIVersion(api_versions.MAX_VERSION)
+        else:
+            api_version = api_versions.get_api_version(
+                options.os_volume_api_version)
+        return api_version
+
+    @staticmethod
+    def downgrade_warning(requested, discovered):
+        logger.warning("API version %s requested, " % requested.get_string())
+        logger.warning("downgrading to %s based on server support." %
+                       discovered.get_string())
+
     def main(self, argv):
         # Parse args once to find version and debug settings
         parser = self.get_base_parser()
@@ -527,14 +542,7 @@ class OpenStackCinderShell(object):
         do_help = ('help' in argv) or (
             '--help' in argv) or ('-h' in argv) or not argv
 
-        if not options.os_volume_api_version:
-            use_version = DEFAULT_MAJOR_OS_VOLUME_API_VERSION
-            if do_help:
-                use_version = api_versions.MAX_VERSION
-            api_version = api_versions.get_api_version(use_version)
-        else:
-            api_version = api_versions.get_api_version(
-                options.os_volume_api_version)
+        api_version = self._validate_input_api_version(options)
 
         # build available subcommands based on version
         major_version_string = "%s" % api_version.ver_major
@@ -670,9 +678,7 @@ class OpenStackCinderShell(object):
 
         insecure = self.options.insecure
 
-        self.cs = client.Client(
-            api_version, os_username,
-            os_password, os_project_name, os_auth_url,
+        client_args = dict(
             region_name=os_region_name,
             tenant_id=os_project_id,
             endpoint_type=endpoint_type,
@@ -689,6 +695,11 @@ class OpenStackCinderShell(object):
             session=auth_session,
             logger=self.ks_logger if auth_session else self.client_logger)
 
+        self.cs = client.Client(
+            api_version, os_username,
+            os_password, os_project_name, os_auth_url,
+            **client_args)
+
         try:
             if not utils.isunauthenticated(args.func):
                 self.cs.authenticate()
@@ -718,6 +729,28 @@ class OpenStackCinderShell(object):
                                "to the default API version: %s",
                                endpoint_api_version)
 
+        API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION)
+        if endpoint_api_version[0] == '3':
+            disc_client = client.Client(API_MAX_VERSION,
+                                        os_username,
+                                        os_password,
+                                        os_project_name,
+                                        os_auth_url,
+                                        **client_args)
+            self.cs, discovered_version = self._discover_client(
+                disc_client,
+                api_version,
+                args.os_endpoint_type,
+                args.service_type,
+                os_username,
+                os_password,
+                os_project_name,
+                os_auth_url,
+                client_args)
+
+            if discovered_version < api_version:
+                self.downgrade_warning(api_version, discovered_version)
+
         profile = osprofiler_profiler and options.profile
         if profile:
             osprofiler_profiler.init(options.profile)
@@ -731,6 +764,56 @@ class OpenStackCinderShell(object):
                 print("To display trace use next command:\n"
                       "osprofiler trace show --html %s " % trace_id)
 
+    def _discover_client(self,
+                         current_client,
+                         os_api_version,
+                         os_endpoint_type,
+                         os_service_type,
+                         os_username,
+                         os_password,
+                         os_project_name,
+                         os_auth_url,
+                         client_args):
+
+        if (os_api_version.get_major_version() in
+                api_versions.DEPRECATED_VERSIONS):
+            discovered_version = api_versions.DEPRECATED_VERSION
+            os_service_type = 'volume'
+        else:
+            discovered_version = api_versions.discover_version(
+                current_client,
+                os_api_version)
+
+        if not os_endpoint_type:
+            os_endpoint_type = DEFAULT_CINDER_ENDPOINT_TYPE
+
+        if not os_service_type:
+            os_service_type = self._discover_service_type(discovered_version)
+
+        API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION)
+
+        if (discovered_version != API_MAX_VERSION or
+                os_service_type != 'volume' or
+                os_endpoint_type != DEFAULT_CINDER_ENDPOINT_TYPE):
+            client_args['service_type'] = os_service_type
+            client_args['endpoint_type'] = os_endpoint_type
+
+            return (client.Client(discovered_version,
+                                  os_username,
+                                  os_password,
+                                  os_project_name,
+                                  os_auth_url,
+                                  **client_args),
+                    discovered_version)
+        else:
+            return current_client, discovered_version
+
+    def _discover_service_type(self, discovered_version):
+        SERVICE_TYPES = {'1': 'volume', '2': 'volumev2', '3': 'volumev3'}
+        major_version = discovered_version.get_major_version()
+        service_type = SERVICE_TYPES[major_version]
+        return service_type
+
     def _run_extension_hooks(self, hook_type, *args, **kwargs):
         """Runs hooks for all registered extensions."""
         for extension in self.extensions:
diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py
index cc3372ce5..f4aa59816 100644
--- a/cinderclient/tests/unit/v3/test_shell.py
+++ b/cinderclient/tests/unit/v3/test_shell.py
@@ -46,6 +46,7 @@ import six
 from six.moves.urllib import parse
 
 import cinderclient
+from cinderclient import api_versions
 from cinderclient import base
 from cinderclient import client
 from cinderclient import exceptions
@@ -92,7 +93,12 @@ class ShellTest(utils.TestCase):
         self.cs = mock.Mock()
 
     def run_command(self, cmd):
-        self.shell.main(cmd.split())
+        # Ensure the version negotiation indicates that
+        # all versions are supported
+        with mock.patch('cinderclient.api_versions._get_server_version_range',
+                return_value=(api_versions.APIVersion('3.0'),
+                              api_versions.APIVersion('3.99'))):
+            self.shell.main(cmd.split())
 
     def assert_called(self, method, url, body=None,
                       partial_body=None, **kwargs):
@@ -295,6 +301,14 @@ class ShellTest(utils.TestCase):
         mock_print.assert_called_once_with(mock.ANY, key_list,
             exclude_unavailable=True, sortby_index=0)
 
+    @mock.patch("cinderclient.shell.OpenStackCinderShell.downgrade_warning")
+    def test_list_version_downgrade(self, mock_warning):
+        self.run_command('--os-volume-api-version 3.998 list')
+        mock_warning.assert_called_once_with(
+            api_versions.APIVersion('3.998'),
+            api_versions.APIVersion(api_versions.MAX_VERSION)
+        )
+
     def test_list_availability_zone(self):
         self.run_command('availability-zone-list')
         self.assert_called('GET', '/os-availability-zone')
diff --git a/doc/source/user/shell.rst b/doc/source/user/shell.rst
index 0d03b0156..50d8feb19 100644
--- a/doc/source/user/shell.rst
+++ b/doc/source/user/shell.rst
@@ -40,6 +40,14 @@ For example, in Bash you'd use::
     export OS_AUTH_URL=http://auth.example.com:5000/v3
     export OS_VOLUME_API_VERSION=3
 
+If OS_VOLUME_API_VERSION is not set, the highest version
+supported by the server will be used.
+
+If OS_VOLUME_API_VERSION exceeds the highest version
+supported by the server, the highest version supported by
+both the client and server will be used.  A warning
+message is printed when this occurs.
+
 From there, all shell commands take the form::
 
     cinder <command> [arguments...]
diff --git a/releasenotes/notes/cli-api-ver-negotiation-9f8fd8b77ae299fd.yaml b/releasenotes/notes/cli-api-ver-negotiation-9f8fd8b77ae299fd.yaml
new file mode 100644
index 000000000..4501850d8
--- /dev/null
+++ b/releasenotes/notes/cli-api-ver-negotiation-9f8fd8b77ae299fd.yaml
@@ -0,0 +1,12 @@
+---
+features:
+  - |
+    Automatic version negotiation for the cinderclient CLI.
+    If an API version is not specified, the CLI will use the newest
+    supported by the client and the server.
+    If an API version newer than the server supports is requested,
+    the CLI will fall back to the newest version supported by the server
+    and issue a warning message.
+    This does not affect cinderclient library usage.
+
+