diff --git a/doc/source/cli/command-objects/application-credentials.rst b/doc/source/cli/command-objects/application-credentials.rst
new file mode 100644
index 0000000000..08d85b119d
--- /dev/null
+++ b/doc/source/cli/command-objects/application-credentials.rst
@@ -0,0 +1,109 @@
+======================
+application credential
+======================
+
+Identity v3
+
+With application credentials, a user can grant their applications limited
+access to their cloud resources. Once created, users can authenticate with an
+application credential by using the ``v3applicationcredential`` auth type.
+
+application credential create
+-----------------------------
+
+Create new application credential
+
+.. program:: application credential create
+.. code:: bash
+
+    openstack application credential create
+        [--secret <secret>]
+        [--role <role>]
+        [--expiration <expiration>]
+        [--description <description>]
+        [--unrestricted]
+        <name>
+
+.. option:: --secret <secret>
+
+    Secret to use for authentication (if not provided, one will be generated)
+
+.. option:: --role <role>
+
+    Roles to authorize (name or ID) (repeat option to set multiple values)
+
+.. option:: --expiration <expiration>
+
+    Sets an expiration date for the application credential (format of
+    YYYY-mm-ddTHH:MM:SS)
+
+.. option:: --description <description>
+
+    Application credential description
+
+.. option:: --unrestricted
+
+    Enable application credential to create and delete other application
+    credentials and trusts (this is potentially dangerous behavior and is
+    disabled by default)
+
+.. option:: --restricted
+
+    Prohibit application credential from creating and deleting other
+    application credentials and trusts (this is the default behavior)
+
+.. describe:: <name>
+
+    Name of the application credential
+
+
+application credential delete
+-----------------------------
+
+Delete application credential(s)
+
+.. program:: application credential delete
+.. code:: bash
+
+    openstack application credential delete
+        <application-credential> [<application-credential> ...]
+
+.. describe:: <application-credential>
+
+    Application credential(s) to delete (name or ID)
+
+application credential list
+---------------------------
+
+List application credentials
+
+.. program:: application credential list
+.. code:: bash
+
+    openstack application credential list
+        [--user <user>]
+        [--user-domain <user-domain>]
+
+.. option:: --user
+
+    User whose application credentials to list (name or ID)
+
+.. option:: --user-domain
+
+    Domain the user belongs to (name or ID). This can be
+    used in case collisions between user names exist.
+
+application credential show
+---------------------------
+
+Display application credential details
+
+.. program:: application credential show
+.. code:: bash
+
+    openstack application credential show
+        <application-credential>
+
+.. describe:: <application-credential>
+
+    Application credential to display (name or ID)
diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py
index e119f66019..f36f5f73a9 100644
--- a/openstackclient/identity/common.py
+++ b/openstackclient/identity/common.py
@@ -101,6 +101,13 @@ def _get_token_resource(client, resource, parsed_name, parsed_domain=None):
         # user/project under different domain may has a same name
         if parsed_domain and parsed_domain not in obj['domain'].values():
             return parsed_name
+        if isinstance(obj, list):
+            for item in obj:
+                if item['name'] == parsed_name:
+                    return item['id']
+                if item['id'] == parsed_name:
+                    return parsed_name
+            return parsed_name
         return obj['id'] if obj['name'] == parsed_name else parsed_name
     # diaper defense in case parsing the token fails
     except Exception:  # noqa
diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py
new file mode 100644
index 0000000000..747fa20ed1
--- /dev/null
+++ b/openstackclient/identity/v3/application_credential.py
@@ -0,0 +1,220 @@
+#   Copyright 2018 SUSE Linux GmbH
+#
+#   Licensed under the Apache License, Version 2.0 (the "License"); you may
+#   not use this file except in compliance with the License. You may obtain
+#   a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#   License for the specific language governing permissions and limitations
+#   under the License.
+#
+
+"""Identity v3 Application Credential action implementations"""
+
+import datetime
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+import six
+
+from openstackclient.i18n import _
+from openstackclient.identity import common
+
+
+LOG = logging.getLogger(__name__)
+
+
+class CreateApplicationCredential(command.ShowOne):
+    _description = _("Create new application credential")
+
+    def get_parser(self, prog_name):
+        parser = super(CreateApplicationCredential, self).get_parser(prog_name)
+        parser.add_argument(
+            'name',
+            metavar='<name>',
+            help=_('Name of the application credential'),
+        )
+        parser.add_argument(
+            '--secret',
+            metavar='<secret>',
+            help=_('Secret to use for authentication (if not provided, one'
+                   ' will be generated)'),
+        )
+        parser.add_argument(
+            '--role',
+            metavar='<role>',
+            action='append',
+            default=[],
+            help=_('Roles to authorize (name or ID) (repeat option to set'
+                   ' multiple values)'),
+        )
+        parser.add_argument(
+            '--expiration',
+            metavar='<expiration>',
+            help=_('Sets an expiration date for the application credential,'
+                   ' format of YYYY-mm-ddTHH:MM:SS (if not provided, the'
+                   ' application credential will not expire)'),
+        )
+        parser.add_argument(
+            '--description',
+            metavar='<description>',
+            help=_('Application credential description'),
+        )
+        parser.add_argument(
+            '--unrestricted',
+            action="store_true",
+            help=_('Enable application credential to create and delete other'
+                   ' application credentials and trusts (this is potentially'
+                   ' dangerous behavior and is disabled by default)'),
+        )
+        parser.add_argument(
+            '--restricted',
+            action="store_true",
+            help=_('Prohibit application credential from creating and deleting'
+                   ' other application credentials and trusts (this is the'
+                   ' default behavior)'),
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        identity_client = self.app.client_manager.identity
+
+        role_ids = []
+        for role in parsed_args.role:
+            # A user can only create an application credential for themself,
+            # not for another user even as an admin, and only on the project to
+            # which they are currently scoped with a subset of the role
+            # assignments they have on that project. Don't bother trying to
+            # look up roles via keystone, just introspect the token.
+            role_id = common._get_token_resource(identity_client, "roles",
+                                                 role)
+            role_ids.append(role_id)
+
+        expires_at = None
+        if parsed_args.expiration:
+            expires_at = datetime.datetime.strptime(parsed_args.expiration,
+                                                    '%Y-%m-%dT%H:%M:%S')
+
+        if parsed_args.restricted:
+            unrestricted = False
+        else:
+            unrestricted = parsed_args.unrestricted
+
+        app_cred_manager = identity_client.application_credentials
+        application_credential = app_cred_manager.create(
+            parsed_args.name,
+            roles=role_ids,
+            expires_at=expires_at,
+            description=parsed_args.description,
+            secret=parsed_args.secret,
+            unrestricted=unrestricted,
+        )
+
+        application_credential._info.pop('links', None)
+
+        # Format roles into something sensible
+        roles = application_credential._info.pop('roles')
+        msg = ' '.join(r['name'] for r in roles)
+        application_credential._info['roles'] = msg
+
+        return zip(*sorted(six.iteritems(application_credential._info)))
+
+
+class DeleteApplicationCredential(command.Command):
+    _description = _("Delete application credentials(s)")
+
+    def get_parser(self, prog_name):
+        parser = super(DeleteApplicationCredential, self).get_parser(prog_name)
+        parser.add_argument(
+            'application_credential',
+            metavar='<application-credential>',
+            nargs="+",
+            help=_('Application credentials(s) to delete (name or ID)'),
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        identity_client = self.app.client_manager.identity
+
+        errors = 0
+        for ac in parsed_args.application_credential:
+            try:
+                app_cred = utils.find_resource(
+                    identity_client.application_credentials, ac)
+                identity_client.application_credentials.delete(app_cred.id)
+            except Exception as e:
+                errors += 1
+                LOG.error(_("Failed to delete application credential with "
+                          "name or ID '%(ac)s': %(e)s"),
+                          {'ac': ac, 'e': e})
+
+        if errors > 0:
+            total = len(parsed_args.application_credential)
+            msg = (_("%(errors)s of %(total)s application credentials failed "
+                   "to delete.") % {'errors': errors, 'total': total})
+            raise exceptions.CommandError(msg)
+
+
+class ListApplicationCredential(command.Lister):
+    _description = _("List application credentials")
+
+    def get_parser(self, prog_name):
+        parser = super(ListApplicationCredential, self).get_parser(prog_name)
+        parser.add_argument(
+            '--user',
+            metavar='<user>',
+            help=_('User whose application credentials to list (name or ID)'),
+        )
+        common.add_user_domain_option_to_parser(parser)
+        return parser
+
+    def take_action(self, parsed_args):
+        identity_client = self.app.client_manager.identity
+        if parsed_args.user:
+            user_id = common.find_user(identity_client,
+                                       parsed_args.user,
+                                       parsed_args.user_domain).id
+        else:
+            user_id = None
+
+        columns = ('ID', 'Name', 'Project ID', 'Description', 'Expires At')
+        data = identity_client.application_credentials.list(
+            user=user_id)
+        return (columns,
+                (utils.get_item_properties(
+                    s, columns,
+                    formatters={},
+                ) for s in data))
+
+
+class ShowApplicationCredential(command.ShowOne):
+    _description = _("Display application credential details")
+
+    def get_parser(self, prog_name):
+        parser = super(ShowApplicationCredential, self).get_parser(prog_name)
+        parser.add_argument(
+            'application_credential',
+            metavar='<application-credential>',
+            help=_('Application credential to display (name or ID)'),
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        identity_client = self.app.client_manager.identity
+        app_cred = utils.find_resource(identity_client.application_credentials,
+                                       parsed_args.application_credential)
+
+        app_cred._info.pop('links', None)
+
+        # Format roles into something sensible
+        roles = app_cred._info.pop('roles')
+        msg = ' '.join(r['name'] for r in roles)
+        app_cred._info['roles'] = msg
+
+        return zip(*sorted(six.iteritems(app_cred._info)))
diff --git a/openstackclient/tests/functional/identity/v3/test_application_credential.py b/openstackclient/tests/functional/identity/v3/test_application_credential.py
new file mode 100644
index 0000000000..daf6460785
--- /dev/null
+++ b/openstackclient/tests/functional/identity/v3/test_application_credential.py
@@ -0,0 +1,143 @@
+#    Copyright 2018 SUSE Linux GmbH
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import datetime
+
+from tempest.lib.common.utils import data_utils
+
+from openstackclient.tests.functional.identity.v3 import common
+
+
+class ApplicationCredentialTests(common.IdentityTests):
+
+    APPLICATION_CREDENTIAL_FIELDS = ['id', 'name', 'project_id',
+                                     'description', 'roles', 'expires_at',
+                                     'unrestricted']
+    APPLICATION_CREDENTIAL_LIST_HEADERS = ['ID', 'Name', 'Project ID',
+                                           'Description', 'Expires At']
+
+    def test_application_credential_create(self):
+        name = data_utils.rand_name('name')
+        raw_output = self.openstack('application credential create %(name)s'
+                                    % {'name': name})
+        self.addCleanup(
+            self.openstack,
+            'application credential delete %(name)s' % {'name': name})
+        items = self.parse_show(raw_output)
+        self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS)
+
+    def _create_role_assignments(self):
+        try:
+            user = self.openstack('configuration show -f value'
+                                  ' -c auth.username')
+        except Exception:
+            user = self.openstack('configuration show -f value'
+                                  ' -c auth.user_id')
+        try:
+            user_domain = self.openstack('configuration show -f value'
+                                         ' -c auth.user_domain_name')
+        except Exception:
+            user_domain = self.openstack('configuration show -f value'
+                                         ' -c auth.user_domain_id')
+        try:
+            project = self.openstack('configuration show -f value'
+                                     ' -c auth.project_name')
+        except Exception:
+            project = self.openstack('configuration show -f value'
+                                     ' -c auth.project_id')
+        try:
+            project_domain = self.openstack('configuration show -f value'
+                                            ' -c auth.project_domain_name')
+        except Exception:
+            project_domain = self.openstack('configuration show -f value'
+                                            ' -c auth.project_domain_id')
+        role1 = self._create_dummy_role()
+        role2 = self._create_dummy_role()
+        for role in role1, role2:
+            self.openstack('role add'
+                           ' --user %(user)s'
+                           ' --user-domain %(user_domain)s'
+                           ' --project %(project)s'
+                           ' --project-domain %(project_domain)s'
+                           ' %(role)s'
+                           % {'user': user,
+                              'user_domain': user_domain,
+                              'project': project,
+                              'project_domain': project_domain,
+                              'role': role})
+            self.addCleanup(self.openstack,
+                            'role remove'
+                            ' --user %(user)s'
+                            ' --user-domain %(user_domain)s'
+                            ' --project %(project)s'
+                            ' --project-domain %(project_domain)s'
+                            ' %(role)s'
+                            % {'user': user,
+                               'user_domain': user_domain,
+                               'project': project,
+                               'project_domain': project_domain,
+                               'role': role})
+        return role1, role2
+
+    def test_application_credential_create_with_options(self):
+        name = data_utils.rand_name('name')
+        secret = data_utils.rand_name('secret')
+        description = data_utils.rand_name('description')
+        tomorrow = (datetime.datetime.utcnow() +
+                    datetime.timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S%z')
+        role1, role2 = self._create_role_assignments()
+        raw_output = self.openstack('application credential create %(name)s'
+                                    ' --secret %(secret)s'
+                                    ' --description %(description)s'
+                                    ' --expiration %(tomorrow)s'
+                                    ' --role %(role1)s'
+                                    ' --role %(role2)s'
+                                    ' --unrestricted'
+                                    % {'name': name,
+                                       'secret': secret,
+                                       'description': description,
+                                       'tomorrow': tomorrow,
+                                       'role1': role1,
+                                       'role2': role2})
+        self.addCleanup(
+            self.openstack,
+            'application credential delete %(name)s' % {'name': name})
+        items = self.parse_show(raw_output)
+        self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS)
+
+    def test_application_credential_delete(self):
+        name = data_utils.rand_name('name')
+        self.openstack('application credential create %(name)s'
+                       % {'name': name})
+        raw_output = self.openstack('application credential delete '
+                                    '%(name)s' % {'name': name})
+        self.assertEqual(0, len(raw_output))
+
+    def test_application_credential_list(self):
+        raw_output = self.openstack('application credential list')
+        items = self.parse_listing(raw_output)
+        self.assert_table_structure(
+            items, self.APPLICATION_CREDENTIAL_LIST_HEADERS)
+
+    def test_application_credential_show(self):
+        name = data_utils.rand_name('name')
+        raw_output = self.openstack('application credential create %(name)s'
+                                    % {'name': name})
+        self.addCleanup(
+            self.openstack,
+            'application credential delete %(name)s' % {'name': name})
+        raw_output = self.openstack('application credential show '
+                                    '%(name)s' % {'name': name})
+        items = self.parse_show(raw_output)
+        self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS)
diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py
index 8ceca3ce91..779287924d 100644
--- a/openstackclient/tests/unit/identity/v3/fakes.py
+++ b/openstackclient/tests/unit/identity/v3/fakes.py
@@ -14,6 +14,7 @@
 #
 
 import copy
+import datetime
 import uuid
 
 from keystoneauth1 import access
@@ -457,6 +458,34 @@ OAUTH_VERIFIER = {
     'oauth_verifier': oauth_verifier_pin
 }
 
+app_cred_id = 'app-cred-id'
+app_cred_name = 'testing_app_cred'
+app_cred_role = {"id": role_id, "name": role_name, "domain": None},
+app_cred_description = 'app credential for testing'
+app_cred_expires = datetime.datetime(2022, 1, 1, 0, 0)
+app_cred_expires_str = app_cred_expires.strftime('%Y-%m-%dT%H:%M:%S%z')
+app_cred_secret = 'moresecuresecret'
+APP_CRED_BASIC = {
+    'id': app_cred_id,
+    'name': app_cred_name,
+    'project_id': project_id,
+    'roles': app_cred_role,
+    'description': None,
+    'expires_at': None,
+    'unrestricted': False,
+    'secret': app_cred_secret
+}
+APP_CRED_OPTIONS = {
+    'id': app_cred_id,
+    'name': app_cred_name,
+    'project_id': project_id,
+    'roles': app_cred_role,
+    'description': app_cred_description,
+    'expires_at': app_cred_expires_str,
+    'unrestricted': False,
+    'secret': app_cred_secret
+}
+
 
 def fake_auth_ref(fake_token, fake_service=None):
     """Create an auth_ref using keystoneauth's fixtures"""
@@ -544,6 +573,9 @@ class FakeIdentityv3Client(object):
         self.auth = FakeAuth()
         self.auth.client = mock.Mock()
         self.auth.client.resource_class = fakes.FakeResource(None, {})
+        self.application_credentials = mock.Mock()
+        self.application_credentials.resource_class = fakes.FakeResource(None,
+                                                                         {})
 
 
 class FakeFederationManager(object):
diff --git a/openstackclient/tests/unit/identity/v3/test_application_credential.py b/openstackclient/tests/unit/identity/v3/test_application_credential.py
new file mode 100644
index 0000000000..e7c8ede826
--- /dev/null
+++ b/openstackclient/tests/unit/identity/v3/test_application_credential.py
@@ -0,0 +1,309 @@
+#   Copyright 2018 SUSE Linux GmbH
+#
+#   Licensed under the Apache License, Version 2.0 (the "License"); you may
+#   not use this file except in compliance with the License. You may obtain
+#   a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#   License for the specific language governing permissions and limitations
+#   under the License.
+#
+
+import copy
+
+import mock
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.identity.v3 import application_credential
+from openstackclient.tests.unit import fakes
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
+
+
+class TestApplicationCredential(identity_fakes.TestIdentityv3):
+
+    def setUp(self):
+        super(TestApplicationCredential, self).setUp()
+
+        identity_manager = self.app.client_manager.identity
+        self.app_creds_mock = identity_manager.application_credentials
+        self.app_creds_mock.reset_mock()
+        self.roles_mock = identity_manager.roles
+        self.roles_mock.reset_mock()
+
+
+class TestApplicationCredentialCreate(TestApplicationCredential):
+
+    def setUp(self):
+        super(TestApplicationCredentialCreate, self).setUp()
+
+        self.roles_mock.get.return_value = fakes.FakeResource(
+            None,
+            copy.deepcopy(identity_fakes.ROLE),
+            loaded=True,
+        )
+
+        # Get the command object to test
+        self.cmd = application_credential.CreateApplicationCredential(
+            self.app, None)
+
+    def test_application_credential_create_basic(self):
+        self.app_creds_mock.create.return_value = fakes.FakeResource(
+            None,
+            copy.deepcopy(identity_fakes.APP_CRED_BASIC),
+            loaded=True,
+        )
+
+        name = identity_fakes.app_cred_name
+        arglist = [
+            name
+        ]
+        verifylist = [
+            ('name', identity_fakes.app_cred_name)
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        # In base command class ShowOne in cliff, abstract method take_action()
+        # returns a two-part tuple with a tuple of column names and a tuple of
+        # data to be shown.
+        columns, data = self.cmd.take_action(parsed_args)
+
+        # Set expected values
+        kwargs = {
+            'secret': None,
+            'roles': [],
+            'expires_at': None,
+            'description': None,
+            'unrestricted': False,
+        }
+        self.app_creds_mock.create.assert_called_with(
+            name,
+            **kwargs
+        )
+
+        collist = ('description', 'expires_at', 'id', 'name', 'project_id',
+                   'roles', 'secret', 'unrestricted')
+        self.assertEqual(collist, columns)
+        datalist = (
+            None,
+            None,
+            identity_fakes.app_cred_id,
+            identity_fakes.app_cred_name,
+            identity_fakes.project_id,
+            identity_fakes.role_name,
+            identity_fakes.app_cred_secret,
+            False,
+        )
+        self.assertEqual(datalist, data)
+
+    def test_application_credential_create_with_options(self):
+        name = identity_fakes.app_cred_name
+        self.app_creds_mock.create.return_value = fakes.FakeResource(
+            None,
+            copy.deepcopy(identity_fakes.APP_CRED_OPTIONS),
+            loaded=True,
+        )
+
+        arglist = [
+            name,
+            '--secret', 'moresecuresecret',
+            '--role', identity_fakes.role_id,
+            '--expiration', identity_fakes.app_cred_expires_str,
+            '--description', 'credential for testing'
+        ]
+        verifylist = [
+            ('name', identity_fakes.app_cred_name),
+            ('secret', 'moresecuresecret'),
+            ('role', [identity_fakes.role_id]),
+            ('expiration', identity_fakes.app_cred_expires_str),
+            ('description', 'credential for testing')
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        # In base command class ShowOne in cliff, abstract method take_action()
+        # returns a two-part tuple with a tuple of column names and a tuple of
+        # data to be shown.
+        columns, data = self.cmd.take_action(parsed_args)
+
+        # Set expected values
+        kwargs = {
+            'secret': 'moresecuresecret',
+            'roles': [identity_fakes.role_id],
+            'expires_at': identity_fakes.app_cred_expires,
+            'description': 'credential for testing',
+            'unrestricted': False
+        }
+        self.app_creds_mock.create.assert_called_with(
+            name,
+            **kwargs
+        )
+
+        collist = ('description', 'expires_at', 'id', 'name', 'project_id',
+                   'roles', 'secret', 'unrestricted')
+        self.assertEqual(collist, columns)
+        datalist = (
+            identity_fakes.app_cred_description,
+            identity_fakes.app_cred_expires_str,
+            identity_fakes.app_cred_id,
+            identity_fakes.app_cred_name,
+            identity_fakes.project_id,
+            identity_fakes.role_name,
+            identity_fakes.app_cred_secret,
+            False,
+        )
+        self.assertEqual(datalist, data)
+
+
+class TestApplicationCredentialDelete(TestApplicationCredential):
+
+    def setUp(self):
+        super(TestApplicationCredentialDelete, self).setUp()
+
+        # This is the return value for utils.find_resource()
+        self.app_creds_mock.get.return_value = fakes.FakeResource(
+            None,
+            copy.deepcopy(identity_fakes.APP_CRED_BASIC),
+            loaded=True,
+        )
+        self.app_creds_mock.delete.return_value = None
+
+        # Get the command object to test
+        self.cmd = application_credential.DeleteApplicationCredential(
+            self.app, None)
+
+    def test_application_credential_delete(self):
+        arglist = [
+            identity_fakes.app_cred_id,
+        ]
+        verifylist = [
+            ('application_credential', [identity_fakes.app_cred_id])
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        result = self.cmd.take_action(parsed_args)
+
+        self.app_creds_mock.delete.assert_called_with(
+            identity_fakes.app_cred_id,
+        )
+        self.assertIsNone(result)
+
+    @mock.patch.object(utils, 'find_resource')
+    def test_delete_multi_app_creds_with_exception(self, find_mock):
+        find_mock.side_effect = [self.app_creds_mock.get.return_value,
+                                 exceptions.CommandError]
+        arglist = [
+            identity_fakes.app_cred_id,
+            'nonexistent_app_cred',
+        ]
+        verifylist = [
+            ('application_credential', arglist),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        try:
+            self.cmd.take_action(parsed_args)
+            self.fail('CommandError should be raised.')
+        except exceptions.CommandError as e:
+            self.assertEqual('1 of 2 application credentials failed to'
+                             ' delete.', str(e))
+
+        find_mock.assert_any_call(self.app_creds_mock,
+                                  identity_fakes.app_cred_id)
+        find_mock.assert_any_call(self.app_creds_mock,
+                                  'nonexistent_app_cred')
+
+        self.assertEqual(2, find_mock.call_count)
+        self.app_creds_mock.delete.assert_called_once_with(
+            identity_fakes.app_cred_id)
+
+
+class TestApplicationCredentialList(TestApplicationCredential):
+
+    def setUp(self):
+        super(TestApplicationCredentialList, self).setUp()
+
+        self.app_creds_mock.list.return_value = [
+            fakes.FakeResource(
+                None,
+                copy.deepcopy(identity_fakes.APP_CRED_BASIC),
+                loaded=True,
+            ),
+        ]
+
+        # Get the command object to test
+        self.cmd = application_credential.ListApplicationCredential(self.app,
+                                                                    None)
+
+    def test_application_credential_list(self):
+        arglist = []
+        verifylist = []
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        # In base command class Lister in cliff, abstract method take_action()
+        # returns a tuple containing the column names and an iterable
+        # containing the data to be listed.
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.app_creds_mock.list.assert_called_with(user=None)
+
+        collist = ('ID', 'Name', 'Project ID', 'Description', 'Expires At')
+        self.assertEqual(collist, columns)
+        datalist = ((
+            identity_fakes.app_cred_id,
+            identity_fakes.app_cred_name,
+            identity_fakes.project_id,
+            None,
+            None
+        ), )
+        self.assertEqual(datalist, tuple(data))
+
+
+class TestApplicationCredentialShow(TestApplicationCredential):
+
+    def setUp(self):
+        super(TestApplicationCredentialShow, self).setUp()
+
+        self.app_creds_mock.get.return_value = fakes.FakeResource(
+            None,
+            copy.deepcopy(identity_fakes.APP_CRED_BASIC),
+            loaded=True,
+        )
+
+        # Get the command object to test
+        self.cmd = application_credential.ShowApplicationCredential(self.app,
+                                                                    None)
+
+    def test_application_credential_show(self):
+        arglist = [
+            identity_fakes.app_cred_id,
+        ]
+        verifylist = [
+            ('application_credential', identity_fakes.app_cred_id),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        # In base command class ShowOne in cliff, abstract method take_action()
+        # returns a two-part tuple with a tuple of column names and a tuple of
+        # data to be shown.
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.app_creds_mock.get.assert_called_with(identity_fakes.app_cred_id)
+
+        collist = ('description', 'expires_at', 'id', 'name', 'project_id',
+                   'roles', 'secret', 'unrestricted')
+        self.assertEqual(collist, columns)
+        datalist = (
+            None,
+            None,
+            identity_fakes.app_cred_id,
+            identity_fakes.app_cred_name,
+            identity_fakes.project_id,
+            identity_fakes.role_name,
+            identity_fakes.app_cred_secret,
+            False,
+        )
+        self.assertEqual(datalist, data)
diff --git a/releasenotes/notes/bp-application-credential-a7031a043efc4a25.yaml b/releasenotes/notes/bp-application-credential-a7031a043efc4a25.yaml
new file mode 100644
index 0000000000..2b4ab18980
--- /dev/null
+++ b/releasenotes/notes/bp-application-credential-a7031a043efc4a25.yaml
@@ -0,0 +1,9 @@
+---
+features:
+  - |
+    Adds support for creating, reading, and deleting application credentials
+    via the ``appication credential`` command. With application credentials, a
+    user can grant their applications limited access to their cloud resources.
+    Once created, users can authenticate with an application credential by
+    using the ``v3applicationcredential`` auth type.
+    [`blueprint application-credentials <https://blueprints.launchpad.net/keystone/+spec/application-credentials>`_]
diff --git a/setup.cfg b/setup.cfg
index df062e362d..b031cc5f6c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -202,6 +202,11 @@ openstack.identity.v2 =
 openstack.identity.v3 =
     access_token_create = openstackclient.identity.v3.token:CreateAccessToken
 
+    application_credential_create = openstackclient.identity.v3.application_credential:CreateApplicationCredential
+    application_credential_delete = openstackclient.identity.v3.application_credential:DeleteApplicationCredential
+    application_credential_list = openstackclient.identity.v3.application_credential:ListApplicationCredential
+    application_credential_show = openstackclient.identity.v3.application_credential:ShowApplicationCredential
+
     catalog_list = openstackclient.identity.v3.catalog:ListCatalog
     catalog_show = openstackclient.identity.v3.catalog:ShowCatalog