diff --git a/databricks_cli/cli.py b/databricks_cli/cli.py
index 0b0a8f1b..3d12f4a1 100644
--- a/databricks_cli/cli.py
+++ b/databricks_cli/cli.py
@@ -25,6 +25,7 @@
 
 from databricks_cli.configure.config import profile_option, debug_option
 from databricks_cli.libraries.cli import libraries_group
+from databricks_cli.permissions.cli import permissions_group
 from databricks_cli.version import print_version_callback, version
 from databricks_cli.utils import CONTEXT_SETTINGS
 from databricks_cli.configure.cli import configure_cli
@@ -65,6 +66,7 @@ def cli():
 cli.add_command(tokens_group, name='tokens')
 cli.add_command(instance_pools_group, name="instance-pools")
 cli.add_command(pipelines_group, name='pipelines')
+cli.add_command(permissions_group, name='permissions')
 
 if __name__ == "__main__":
     cli()
diff --git a/databricks_cli/click_types.py b/databricks_cli/click_types.py
index ff508a74..074f803d 100644
--- a/databricks_cli/click_types.py
+++ b/databricks_cli/click_types.py
@@ -114,7 +114,7 @@ def __init__(self, *args, **kwargs):
     def handle_parse_result(self, ctx, opts, args):
         cleaned_opts = set([o.replace('_', '-') for o in opts.keys()])
         if len(cleaned_opts.intersection(set(self.one_of))) == 0:
-            raise MissingParameter('One of {} must be provided.'.format(self.one_of))
+            raise MissingParameter('One of {} must be provided.'.format(self.one_of), param=self)
         if len(cleaned_opts.intersection(set(self.one_of))) > 1:
             raise UsageError('Only one of {} should be provided.'.format(self.one_of))
         return super(OneOfOption, self).handle_parse_result(ctx, opts, args)
@@ -124,6 +124,7 @@ class OptionalOneOfOption(Option):
     def __init__(self, *args, **kwargs):
         self.one_of = kwargs.pop('one_of')
         super(OptionalOneOfOption, self).__init__(*args, **kwargs)
+        self.param_hint = self.one_of
 
     def handle_parse_result(self, ctx, opts, args):
         cleaned_opts = set([o.replace('_', '-') for o in opts.keys()])
diff --git a/databricks_cli/permissions/__init__.py b/databricks_cli/permissions/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/databricks_cli/permissions/api.py b/databricks_cli/permissions/api.py
new file mode 100644
index 00000000..fe437781
--- /dev/null
+++ b/databricks_cli/permissions/api.py
@@ -0,0 +1,343 @@
+# Databricks CLI
+# Copyright 2017 Databricks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"), except
+# that the use of services to which certain application programming
+# interfaces (each, an "API") connect requires that the user first obtain
+# a license for the use of the APIs from Databricks, Inc. ("Databricks"),
+# by creating an account at www.databricks.com and agreeing to either (a)
+# the Community Edition Terms of Service, (b) the Databricks Terms of
+# Service, or (c) another written agreement between Licensee and Databricks
+# for the use of the APIs.
+#
+# 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.
+from enum import Enum
+
+from databricks_cli.sdk.permissions_service import PermissionsService
+from .exceptions import PermissionsError
+
+
+class PermissionTargets(Enum):
+    clusters = 'clusters'
+    cluster = clusters
+    directories = 'directories'
+    directory = directories
+    instance_pools = 'instance-pools'
+    instance_pool = instance_pools
+    jobs = 'jobs'
+    job = jobs
+    notebooks = 'notebooks'
+    notebook = notebooks
+    registered_models = 'registered-models'
+    registered_model = registered_models
+    model = registered_models
+    models = registered_models
+
+    @classmethod
+    def values(cls):
+        return [e.value for e in PermissionTargets]
+
+    @classmethod
+    def help_values(cls):
+        return ', '.join([e.value for e in PermissionTargets])
+
+    @classmethod
+    def get(cls, item):
+        if '-' in item:
+            item = item.replace('-', '_')
+        return PermissionTargets[item]
+
+
+class PermissionLevel(Enum):
+    no_permissions = 'NONE'
+    manage = 'CAN_MANAGE'
+    manage_staging_versions = 'CAN_MANAGE_STAGING_VERSIONS'
+    manage_production_versions = 'CAN_MANAGE_PRODUCTION_VERSIONS'
+    restart = 'CAN_RESTART'
+    attach = 'CAN_ATTACH_TO'
+    manage_run = 'CAN_MANAGE_RUN'
+    owner = 'IS_OWNER'
+    view = 'CAN_VIEW'
+    read = 'CAN_READ'
+    run = 'CAN_RUN'
+    edit = 'CAN_EDIT'
+    use = 'CAN_USE'
+
+    @classmethod
+    def names(cls):
+        return [e.name for e in PermissionLevel]
+
+    @classmethod
+    def values(cls):
+        return [e.value for e in PermissionLevel]
+
+    @classmethod
+    def help_values(cls):
+        return ', '.join([e.value for e in PermissionLevel])
+
+
+class BasicPermissions(object):
+    def __init__(self, object_type, valid_permissions):
+        self.object_type = object_type
+        self.valid_permissions = valid_permissions
+
+    def is_valid_target(self, permission):
+        # type: (str) -> bool
+        return permission in self.valid_permissions
+
+    def valid_targets(self):
+        return [s.name for s in self.valid_permissions]
+
+
+class TokenPermissions(BasicPermissions):
+    def __init__(self):
+        super().__init__(PermissionTargets.token, {
+            PermissionLevel.no_permissions,
+            PermissionLevel.use,
+            PermissionLevel.manage,
+        })
+
+
+class PasswordPermissions(BasicPermissions):
+    def __init__(self):
+        super().__init__(PermissionTargets.password, {
+            PermissionLevel.no_permissions,
+            PermissionLevel.use,
+        })
+
+
+class ClusterPermissions(BasicPermissions):
+    def __init__(self):
+        super().__init__(PermissionTargets.clusters, {
+            PermissionLevel.no_permissions,
+            PermissionLevel.attach,
+            PermissionLevel.restart,
+            PermissionLevel.manage,
+        })
+
+
+class InstancePoolPermissions(BasicPermissions):
+    def __init__(self):
+        super().__init__(PermissionTargets.instance_pools, {
+            PermissionLevel.no_permissions,
+            PermissionLevel.attach,
+            PermissionLevel.manage,
+        })
+
+
+class JobPermissions(BasicPermissions):
+    def __init__(self):
+        super().__init__(PermissionTargets.jobs, {
+            PermissionLevel.no_permissions,
+            PermissionLevel.view,
+            PermissionLevel.manage_run,
+            PermissionLevel.owner,
+            PermissionLevel.manage,
+        })
+
+
+class NotebookPermissions(BasicPermissions):
+    def __init__(self):
+        super().__init__(PermissionTargets.notebook, {
+            PermissionLevel.no_permissions,
+            PermissionLevel.read,
+            PermissionLevel.run,
+            PermissionLevel.edit,
+            PermissionLevel.manage,
+        })
+
+
+class DirectoryPermissions(BasicPermissions):
+    def __init__(self):
+        super().__init__(PermissionTargets.directory, {
+            PermissionLevel.no_permissions,
+            PermissionLevel.read,
+            PermissionLevel.run,
+            PermissionLevel.edit,
+            PermissionLevel.manage,
+        })
+
+
+class MlFlowPermissions(BasicPermissions):
+    def __init__(self):
+        super().__init__(PermissionTargets.models, {
+            PermissionLevel.no_permissions,
+            PermissionLevel.read,
+            PermissionLevel.edit,
+            PermissionLevel.manage_staging_versions,
+            PermissionLevel.manage_production_versions,
+            PermissionLevel.manage,
+        })
+
+
+class PermissionType(Enum):
+    user = 'user_name'
+    group = 'group_name'
+    service = 'service_principal_name'
+
+    @classmethod
+    def values(cls):
+        return [e.value for e in PermissionType]
+
+
+class PermissionsLookup(object):
+    """
+    static lookup table for permissions
+    """
+
+    items = {
+        'CAN_MANAGE': PermissionLevel.manage,
+        'CAN_RESTART': PermissionLevel.restart,
+        'CAN_ATTACH_TO': PermissionLevel.attach,
+        'CAN_MANAGE_RUN': PermissionLevel.manage_run,
+        'IS_OWNER': PermissionLevel.owner,
+        'CAN_VIEW': PermissionLevel.view,
+        'CAN_READ': PermissionLevel.read,
+        'CAN_RUN': PermissionLevel.run,
+        'CAN_EDIT': PermissionLevel.edit,
+        'user_name': PermissionType.user,
+        'group_name': PermissionType.group,
+        'service_principal_name': PermissionType.service,
+
+        'clusters': ClusterPermissions(),
+        'cluster': ClusterPermissions(),
+        'directories': DirectoryPermissions(),
+        'directory': DirectoryPermissions(),
+        'instance-pools': InstancePoolPermissions(),
+        'instance_pools': InstancePoolPermissions(),
+        'jobs': JobPermissions(),
+        'job': JobPermissions(),
+        'notebooks': NotebookPermissions(),
+        'notebook': NotebookPermissions(),
+        'registered-models': MlFlowPermissions(),
+        'registered_models': MlFlowPermissions(),
+        'model': MlFlowPermissions(),
+        'models': MlFlowPermissions(),
+    }
+
+
+class Permission(object):
+    def __init__(self, object_type, permission_type, permission_level, permission_value):
+        # type: (str, PermissionType,  str, str) -> None
+        self.validator = PermissionsLookup.items[object_type]
+
+        if not self.validator.is_valid_target(permission_level):
+            raise PermissionsError(
+                '{} is not a valid target for {}\n'.format(permission_level,
+                                                           self.validator.object_type) +
+
+                'Valid values are {}'.format(self.validator.valid_targets()))
+
+        self.permission_type = permission_type
+        self.permission_level = PermissionLevel[permission_level]
+        self.value = permission_value
+
+    def to_dict(self):
+        # type: () -> dict
+        if not self.permission_type or not self.permission_level:
+            return {}
+
+        return {
+            self.permission_type.value: self.value,
+            'permission_level': self.permission_level.value
+        }
+
+
+class PermissionsObject(object):
+    def __init__(self, permissions=None):
+        if not permissions:
+            permissions = []
+        self.permissions = permissions
+
+    def add(self, permission):
+        # type: (Permission) -> None
+        self.permissions.append(permission)
+
+    def user(self, name, level):
+        # type: (str, PermissionLevel) -> None
+        self.add(Permission(PermissionType.user, value=name, permission_level=level))
+
+    def group(self, name, level):
+        # type: (str, PermissionLevel) -> None
+        self.add(Permission(PermissionType.group, value=name, permission_level=level))
+
+    def service(self, name, level):
+        # type: (str, PermissionLevel) -> None
+        self.add(Permission(PermissionType.service, value=name, permission_level=level))
+
+    def to_dict(self):
+        # type: () -> dict
+        if not self.permissions:
+            return {}
+
+        return {
+            'access_control_list': [entry.to_dict() for entry in self.permissions]
+        }
+
+    def check_if_valid_for(self, object_type):
+        """
+        Check if the permissions are valid for this object type.
+        """
+        pass
+
+
+# FIXME: add set/update permissions, right now this is read only.
+class PermissionsApi(object):
+    def __init__(self, api_client):
+        self.api_client = api_client
+        self.client = PermissionsService(api_client)
+
+    def get_permissions(self, object_type, object_id):
+        # type: (str, str) -> dict
+        if not object_type:
+            raise PermissionsError('object_type is invalid')
+
+        if not object_id:
+            object_id = ''
+            # raise PermissionsError('object_id is invalid')
+
+        return self.client.get_permissions(object_type=PermissionTargets.get(object_type).value,
+                                           object_id=object_id)
+
+    def get_possible_permissions(self, object_type, object_id):
+        # type: (str, str) -> dict
+        if not object_type:
+            raise PermissionsError('object_type is invalid')
+
+        if not object_id:
+            raise PermissionsError('object_id is invalid')
+
+        return self.client.get_possible_permissions(
+            object_type=PermissionTargets.get(object_type).value,
+            object_id=object_id)
+
+    def add_permissions(self, object_type, object_id, permissions):
+        # type: (str, str, PermissionsObject) -> dict
+        if not object_type:
+            raise PermissionsError('object_type is invalid')
+
+        if not object_id:
+            raise PermissionsError('object_id is invalid')
+
+        return self.client.add_permissions(object_type=PermissionTargets.get(object_type).value,
+                                           object_id=object_id, data=permissions.to_dict())
+
+    def update_permissions(self, object_type, object_id, permissions):
+        # type: (str, str, PermissionsObject) -> dict
+        if not object_type:
+            raise PermissionsError('object_type is invalid')
+
+        if not object_id:
+            raise PermissionsError('object_id is invalid')
+
+        return self.client.update_permissions(object_type=PermissionTargets.get(object_type).value,
+                                              object_id=object_id, data=permissions.to_dict())
diff --git a/databricks_cli/permissions/cli.py b/databricks_cli/permissions/cli.py
new file mode 100644
index 00000000..5601cf2b
--- /dev/null
+++ b/databricks_cli/permissions/cli.py
@@ -0,0 +1,178 @@
+# Databricks CLI
+# Copyright 2017 Databricks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"), except
+# that the use of services to which certain application programming
+# interfaces (each, an "API") connect requires that the user first obtain
+# a license for the use of the APIs from Databricks, Inc. ("Databricks"),
+# by creating an account at www.databricks.com and agreeing to either (a)
+# the Community Edition Terms of Service, (b) the Databricks Terms of
+# Service, or (c) another written agreement between Licensee and Databricks
+# for the use of the APIs.
+#
+# 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 json
+
+import click
+from click import UsageError
+
+from databricks_cli.click_types import OneOfOption
+from databricks_cli.configure.config import provide_api_client, profile_option, debug_option
+from databricks_cli.permissions.api import PermissionsApi, PermissionTargets, PermissionLevel, \
+    PermissionType, Permission, PermissionsObject
+from databricks_cli.utils import eat_exceptions, CONTEXT_SETTINGS
+from databricks_cli.utils import pretty_format
+from databricks_cli.version import print_version_callback, version
+from databricks_cli.workspace.api import WorkspaceApi
+
+FILTERS_HELP = 'Filters for filtering the list of users: ' + \
+               'https://docs.databricks.com/api/latest/scim.html#filters'
+USER_OPTIONS = ['user-id', 'user-name']
+JSON_FILE_OPTIONS = ['json-file', 'json']
+CREATE_USER_OPTIONS = JSON_FILE_OPTIONS + ['user-name']
+
+GROUP_USER_SERVICE_OPTIONS = ['group-name', 'user-name', 'service-name']
+PERMISSION_LEVEL_OPTIONS = PermissionLevel.names()
+POSSIBLE_OBJECT_TYPES = 'Possible object types are: \n\t{}\n'.format(
+    PermissionTargets.help_values())
+
+POSSIBLE_PERMISSION_LEVELS = 'Possible permission levels are: \n\t{}\n'.format(
+    PermissionLevel.help_values())
+
+
+@click.command(context_settings=CONTEXT_SETTINGS,
+               short_help='Get permissions for an item.  ' + POSSIBLE_OBJECT_TYPES)
+@click.option('--object-type', required=True, help=POSSIBLE_OBJECT_TYPES)
+@click.option('--object-id', required=True, help='object id to require permission about')
+@debug_option
+@profile_option
+@eat_exceptions
+@provide_api_client
+def get_cli(api_client, object_type, object_id):
+    perms_api = PermissionsApi(api_client)
+    click.echo(pretty_format(perms_api.get_permissions(object_type, object_id)))
+
+
+@click.command(context_settings=CONTEXT_SETTINGS,
+               short_help='List permission types')
+@click.option('--object-type', required=True, help=POSSIBLE_OBJECT_TYPES)
+@click.option('--object-id', required=True, help='object id to require permission about')
+@debug_option
+@profile_option
+@eat_exceptions
+@provide_api_client
+def list_permissions_types_cli(api_client, object_type, object_id):
+    perms_api = PermissionsApi(api_client)
+    click.echo(pretty_format(perms_api.get_possible_permissions(object_type, object_id)))
+
+
+@click.command(context_settings=CONTEXT_SETTINGS,
+               short_help='Add or modify permission types')
+@click.option('--object-type', required=True, help=POSSIBLE_OBJECT_TYPES)
+@click.option('--object-id', required=True, help='object id to require permission about')
+@click.option('--group-name', metavar='<string>', cls=OneOfOption,
+              one_of=GROUP_USER_SERVICE_OPTIONS)
+@click.option('--user-name', metavar='<string>', cls=OneOfOption, one_of=GROUP_USER_SERVICE_OPTIONS)
+@click.option('--service-name', metavar='<string>', cls=OneOfOption,
+              one_of=GROUP_USER_SERVICE_OPTIONS)
+@click.option('--permission-level', metavar='<string>', type=click.Choice(PermissionLevel.names()),
+              required=True, help=POSSIBLE_PERMISSION_LEVELS)
+@debug_option
+@profile_option
+@eat_exceptions
+@provide_api_client
+def add_cli(api_client, object_type, object_id, user_name, group_name, service_name,
+            permission_level):
+    perms_api = PermissionsApi(api_client)
+
+    # Determine the type of permissions we're adding.
+    if user_name:
+        perm_type = PermissionType.user
+        value = user_name
+    elif group_name:
+        perm_type = PermissionType.group
+        value = group_name
+    elif service_name:
+        perm_type = PermissionType.service
+        value = service_name
+    else:
+        # hanging if.  This shouldn't be hit, because OneOfOption should prevent it.
+        # this else/raise is for readability when doing a review.
+        raise UsageError('Invalid argument')
+
+    permission = Permission(object_type, perm_type, permission_level, value)
+    all_permissions = PermissionsObject([permission])
+
+    all_permissions.check_if_valid_for(object_type)
+
+    click.echo(pretty_format(perms_api.add_permissions(object_type, object_id, all_permissions)))
+
+
+@click.command(context_settings=CONTEXT_SETTINGS)
+@debug_option
+@profile_option
+@eat_exceptions
+def list_permissions_targets_cli():
+    click.echo(POSSIBLE_OBJECT_TYPES)
+
+
+@click.command(context_settings=CONTEXT_SETTINGS)
+@debug_option
+@profile_option
+@eat_exceptions
+def list_permissions_level_cli():
+    click.echo(POSSIBLE_PERMISSION_LEVELS)
+
+
+@click.command(context_settings=CONTEXT_SETTINGS,
+               short_help='Get permissions for a directory')
+@click.option('--path', required=True, help='Path in the workspace for to get permissions for.')
+@debug_option
+@profile_option
+@eat_exceptions
+@provide_api_client
+def directory_cli(api_client, path):
+    perms_api = PermissionsApi(api_client)
+    workspace_api = WorkspaceApi(api_client)
+
+    object_type = 'directories'
+    object_ids = workspace_api.get_id_for_directory(path)
+
+    if not object_ids:
+        click.echo('Failed to find id for {}'.format(path))
+        return
+
+    for object_id in object_ids:
+        click.echo(pretty_format(perms_api.get_permissions(object_type, object_id)))
+
+
+@click.group(context_settings=CONTEXT_SETTINGS,
+             help='Utility to interact with Databricks permissions api.\n\n' +
+                  'Valid object types for --object-type are:\n\n\t' +
+                  json.dumps(PermissionTargets.values())
+             )
+@click.option('--version', '-v', is_flag=True, callback=print_version_callback,
+              expose_value=False, is_eager=True, help=version)
+@debug_option
+@profile_option
+@eat_exceptions
+def permissions_group():  # NOQA
+    # A python doc comment here will override the hand coded help above.
+    pass
+
+
+permissions_group.add_command(add_cli, name='add')
+permissions_group.add_command(directory_cli, name='ls')
+permissions_group.add_command(get_cli, name='get')
+permissions_group.add_command(list_permissions_level_cli, name='levels')
+permissions_group.add_command(list_permissions_targets_cli, name='targets')
+permissions_group.add_command(list_permissions_types_cli, name='list-types')
diff --git a/databricks_cli/permissions/exceptions.py b/databricks_cli/permissions/exceptions.py
new file mode 100644
index 00000000..c986acb6
--- /dev/null
+++ b/databricks_cli/permissions/exceptions.py
@@ -0,0 +1,26 @@
+# Databricks CLI
+# Copyright 2018 Databricks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"), except
+# that the use of services to which certain application programming
+# interfaces (each, an "API") connect requires that the user first obtain
+# a license for the use of the APIs from Databricks, Inc. ("Databricks"),
+# by creating an account at www.databricks.com and agreeing to either (a)
+# the Community Edition Terms of Service, (b) the Databricks Terms of
+# Service, or (c) another written agreement between Licensee and Databricks
+# for the use of the APIs.
+#
+# 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.
+
+
+class PermissionsError(Exception):
+    pass
diff --git a/databricks_cli/sdk/permissions_service.py b/databricks_cli/sdk/permissions_service.py
new file mode 100644
index 00000000..a95fbb3c
--- /dev/null
+++ b/databricks_cli/sdk/permissions_service.py
@@ -0,0 +1,95 @@
+from typing import Optional
+
+from databricks_cli.sdk.preview_service import PreviewService
+
+
+class PermissionsService(PreviewService):
+    def __init__(self, client):
+        super(PermissionsService, self).__init__('permissions')
+        self.client = client
+
+    def create_url(self, object_type, object_id, suffix=''):
+        # type: (str, str, str) -> str
+        return '{base}/{object_type}/{object_id}{suffix}'.format(base=self.url_base,
+                                                                 object_type=object_type,
+                                                                 object_id=object_id, suffix=suffix)
+
+    def get_permissions(self, object_type, object_id, headers=None):
+        # type: (str, str, Optional[dict]) -> dict
+        """
+        Get the permissions for an object type and id
+
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-tokens-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-passwords-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-cluster-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-instance-pool-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-job-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-notebook-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-directory-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-registered-model-permissions
+        """
+
+        return self.client.perform_query('GET', self.create_url(object_type=object_type,
+                                                                object_id=object_id),
+                                         headers=headers)
+
+    def get_possible_permissions(self, object_type, object_id, headers=None):
+        # type: (str, str, Optional[dict]) -> dict
+        """
+        Get the permission levels for an object type.
+
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-tokens-permission-levels
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-password-permission-levels
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-clusters-permission-levels
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-instance-pools-permission-levels
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-jobs-permission-levels
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-notebooks-permission-levels
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-directories-permission-levels
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/get-registered-models-permission-levels
+        """
+
+        return self.client.perform_query('GET', self.create_url(object_type=object_type,
+                                                                object_id=object_id,
+                                                                suffix='/permissionLevels'),
+                                         headers=headers)
+
+    def add_permissions(self, object_type, object_id, data, headers=None):
+        # type: (str, str, dict, Optional[dict]) -> dict
+        """
+        Add permissions, this does not REMOVE.
+        A remove requires an update_permissions call to complete replacement.
+
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/set-tokens-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/set-password-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/set-cluster-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/set-instance-pool-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/set-job-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/set-notebook-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/set-directory-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/set-registered-model-permissions
+        """
+
+        return self.client.perform_query('PATCH', self.create_url(object_type, object_id),
+                                         data=data,
+                                         headers=headers)
+
+    def update_permissions(self, object_type, object_id, data, headers=None):
+        # type: (str, str, dict, Optional[dict]) -> dict
+        """
+        Update/Overwrite all permissions
+        This overwrites all of the permissions for an object.
+        This is how you remove permissions, you call update with a complete set of permissions.
+
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/update-tokens-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/update-all-password-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/update-all-cluster-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/update-all-instance-pool-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/update-all-job-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/update-all-notebook-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/update-all-directory-permissions
+        https://docs.databricks.com/dev-tools/api/latest/permissions.html#operation/update-registered-model-permissions
+        """
+
+        return self.client.perform_query('PUT', self.create_url(object_type, object_id),
+                                         data=data,
+                                         headers=headers)
diff --git a/databricks_cli/sdk/preview_service.py b/databricks_cli/sdk/preview_service.py
new file mode 100644
index 00000000..8ec41849
--- /dev/null
+++ b/databricks_cli/sdk/preview_service.py
@@ -0,0 +1,11 @@
+class PreviewService(object):
+    """
+    This class makes it easier to create preview endpoints.
+    """
+    PREVIEW_BASE = '/preview/'
+
+    def __init__(self, base_url):
+        self.url_base = self.create_preview_url(base_url)
+
+    def create_preview_url(self, base_url):
+        return self.PREVIEW_BASE + base_url
diff --git a/databricks_cli/workspace/api.py b/databricks_cli/workspace/api.py
index 571abed5..83ac5dec 100644
--- a/databricks_cli/workspace/api.py
+++ b/databricks_cli/workspace/api.py
@@ -23,6 +23,7 @@
 
 import os
 from base64 import b64encode, b64decode
+from os.path import dirname
 
 import click
 from requests.exceptions import HTTPError
@@ -37,7 +38,7 @@
 
 
 class WorkspaceFileInfo(object):
-    def __init__(self, path, object_type, object_id, language=None, **kwargs): # noqa
+    def __init__(self, path, object_type, language=None, object_id=None, **kwargs): # noqa
         self.path = path
         self.object_type = object_type
         self.language = language
@@ -62,6 +63,14 @@ def to_row(self, is_long_form, is_absolute, with_object_id=False):
 
         return result
 
+    def to_json(self):
+        return {
+            'path': self.path,
+            'object_type': self.object_type,
+            'language': self.language,
+            'object_id': self.object_id,
+        }
+
     @property
     def is_dir(self):
         return self.object_type == DIRECTORY
@@ -98,6 +107,7 @@ def list_objects(self, workspace_path, headers=None):
         if 'objects' not in response:
             return []
         objects = response['objects']
+
         return [WorkspaceFileInfo.from_json(f) for f in objects]
 
     def mkdirs(self, workspace_path, headers=None):
@@ -186,3 +196,34 @@ def export_workspace_dir(self, source_path, target_path, overwrite, headers=None
                     click.echo('{} already exists locally as {}. Skip.'.format(cur_src, cur_dst))
             else:
                 click.echo('{} is neither a dir or a notebook. Skip.'.format(cur_src))
+
+    def list_directory_info(self, workspace_path, headers=None):
+        # first, we need to trim the workspace_path
+        # then we need the parent
+        workspace_path = workspace_path.rstrip('/')
+        parent_path = dirname(workspace_path)
+        if len(parent_path) == 0:
+            parent_path = '/'
+
+        last_entry = workspace_path.split('/')[-1]
+
+        return [object_value for object_value in self.list_objects(parent_path, headers) if
+                last_entry in object_value.path]
+
+    def get_id_for_directory(self, path):
+        # type: (str) -> list[str]
+        """
+        Given a path, use the workspaces API to look up the object id.
+        :param path: path to a directory
+        :return: object id, [] if not found
+        """
+
+        if not path:
+            return []
+
+        objects = self.list_directory_info(path)
+        if not objects:
+            return []
+
+        # ls -d already filtered for us
+        return [workspace_object.object_id for workspace_object in objects]
diff --git a/tests/permissions/__init__.py b/tests/permissions/__init__.py
new file mode 100644
index 00000000..b0c9feac
--- /dev/null
+++ b/tests/permissions/__init__.py
@@ -0,0 +1,22 @@
+# Databricks CLI
+# Copyright 2017 Databricks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"), except
+# that the use of services to which certain application programming
+# interfaces (each, an "API") connect requires that the user first obtain
+# a license for the use of the APIs from Databricks, Inc. ("Databricks"),
+# by creating an account at www.databricks.com and agreeing to either (a)
+# the Community Edition Terms of Service, (b) the Databricks Terms of
+# Service, or (c) another written agreement between Licensee and Databricks
+# for the use of the APIs.
+#
+# 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.
diff --git a/tests/permissions/test_cli.py b/tests/permissions/test_cli.py
new file mode 100644
index 00000000..c68dc203
--- /dev/null
+++ b/tests/permissions/test_cli.py
@@ -0,0 +1,419 @@
+# Databricks CLI
+# Copyright 2017 Databricks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"), except
+# that the use of services to which certain application programming
+# interfaces (each, an "API") connect requires that the user first obtain
+# a license for the use of the APIs from Databricks, Inc. ("Databricks"),
+# by creating an account at www.databricks.com and agreeing to either (a)
+# the Community Edition Terms of Service, (b) the Databricks Terms of
+# Service, or (c) another written agreement between Licensee and Databricks
+# for the use of the APIs.
+#
+# 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.
+
+# pylint: disable=redefined-outer-name
+
+import re
+
+import mock
+import pytest
+from click.testing import CliRunner
+
+import databricks_cli.permissions.cli as cli
+from databricks_cli.permissions.api import PermissionTargets
+from databricks_cli.utils import pretty_format
+from tests.test_data import TEST_CLUSTER_ID
+from tests.utils import provide_conf
+
+
+def strip_margin(text):
+    # type: (str) -> str
+    return re.sub('\n[ \t]*\\|', '\n', text)
+
+
+PERMISSIONS_RETURNS = {
+    'get': {
+        'clusters': {
+            TEST_CLUSTER_ID: {
+                'object_id': '/clusters/{}'.format(TEST_CLUSTER_ID),
+                'object_type': 'cluster',
+                'access_control_list': [
+                    {
+                        'group_name': 'admins',
+                        'all_permissions': [
+                            {
+                                'permission_level': 'CAN_MANAGE',
+                                'inherited': True,
+                                'inherited_from_object': [
+                                    '/clusters/'
+                                ]
+                            }
+                        ]
+                    }
+                ]
+            }
+        }
+    },
+    'list-permissions': {
+        'clusters': {
+            'permission_levels': [
+                {
+                    'permission_level': 'CAN_MANAGE',
+                    'description': 'Can Manage permission on cluster'
+                },
+                {
+                    'permission_level': 'CAN_RESTART',
+                    'description': 'Can Restart permission on cluster'
+                },
+                {
+                    'permission_level': 'CAN_ATTACH_TO',
+                    'description': 'Can Attach To permission on cluster'
+                }
+            ]
+        },
+        'directories': {
+            'permission_levels': [
+                {
+                    'permission_level': 'CAN_READ',
+                    'description': 'Can view and comment on notebooks in the directory'
+                },
+                {
+                    'permission_level': 'CAN_RUN',
+                    'description': 'Can view, comment, attach/detach, and run commands in ' +
+                                   'notebooks in the directory'
+                },
+                {
+                    'permission_level': 'CAN_EDIT',
+                    'description': 'Can view, comment, attach/detach, run commands, and edit ' +
+                                   'notebooks in the directory'
+                },
+                {
+                    'permission_level': 'CAN_MANAGE',
+                    'description': 'Can view, comment, attach/detach, run commands, and edit ' +
+                                   'notebooks in the folder, and can create, delete, and change ' +
+                                   'permissions of items in the directory'
+                }
+            ]
+        },
+        'instance-pools': {
+            'permission_levels': [
+                {
+                    "permission_level": "CAN_MANAGE",
+                    "description": "Can Manage permission on a pool"
+                },
+                {
+                    "permission_level": "CAN_ATTACH_TO",
+                    "description": "Can Attach To permission on a pool"
+                }
+            ]
+        },
+        'jobs': {
+            'permission_levels': [
+                {
+                    'permission_level': 'IS_OWNER',
+                    'description': 'Is Owner permission on a job'
+                },
+                {
+                    'permission_level': 'CAN_MANAGE_RUN',
+                    'description': 'Can Manage Run permission to trigger or cancel job runs'
+                },
+                {
+                    'permission_level': 'CAN_VIEW',
+                    'description': 'Can View permission to view job run results'
+                }
+            ]
+        },
+        'notebooks': {
+            'permission_levels': [
+                {
+                    'permission_level': 'CAN_READ',
+                    'description': 'Can view and comment on the notebook'
+                },
+                {
+                    'permission_level': 'CAN_RUN',
+                    'description': 'Can view, comment, attach/detach, and run commands in the' +
+                                   'notebook'
+                },
+                {
+                    'permission_level': 'CAN_EDIT',
+                    'description': 'Can view, comment, attach/detach, run commands, and edit ' +
+                                   'the notebook'
+                },
+                {
+                    'permission_level': 'CAN_MANAGE',
+                    'description': 'Can view, comment, attach/detach, run commands, edit, and ' +
+                                   'change permissions of the notebook'
+                }
+            ]
+        },
+        'registered-models': {
+            'permission_levels': [
+                {
+                    'permission_level': 'CAN_READ',
+                    'description': 'Can view the details of the registered model and its model ' +
+                                   'versions, and use the model versions.'
+                },
+                {
+                    'permission_level': 'CAN_EDIT',
+                    'description': 'Can view and edit the details of a registered model and ' +
+                                   'its model versions (except stage changes), and add ' +
+                                   'new model versions.'
+                },
+                {
+                    'permission_level': 'CAN_MANAGE_STAGING_VERSIONS',
+                    'description': 'Can view and edit the details of a registered model and its ' +
+                                   'model versions, add new model versions, and manage stage ' +
+                                   'transitions between non-Production stages.'
+                },
+                {
+                    'permission_level': 'CAN_MANAGE_PRODUCTION_VERSIONS',
+                    'description': 'Can view and edit the details of a registered model and its ' +
+                                   'model versions, add new model versions, and manage stage ' +
+                                   'transitions between any stages.'
+                },
+                {
+                    'permission_level': 'CAN_MANAGE',
+                    'description': 'Can manage permissions on, view all details of, and perform ' +
+                                   'all actions on the registered model and its model versions.'
+                }
+            ]
+        }
+    },
+    'add': {
+        'clusters': {
+            'add-manage-group-name': {
+                'object_id': TEST_CLUSTER_ID,
+                'object_type': 'cluster',
+                'access_control_list': [
+                    {
+                        'group_name': 'admins',
+                        'all_permissions': [
+                            {
+                                'permission_level': 'CAN_MANAGE',
+                                'inherited': True,
+                                'inherited_from_object': [
+                                    '/clusters/'
+                                ]
+                            }
+                        ]
+                    },
+                    {
+                        'group_name': 'sam',
+                        'all_permissions': [
+                            {
+                                'permission_level': 'CAN_MANAGE',
+                                'inherited': False
+                            }
+                        ]
+                    }
+                ]
+            },
+            'add-manage-user-name': {
+                'object_id': TEST_CLUSTER_ID,
+                'object_type': 'cluster',
+                'access_control_list': [
+                    {
+                        'group_name': 'admins',
+                        'all_permissions': [
+                            {
+                                'permission_level': 'CAN_MANAGE',
+                                'inherited': True,
+                                'inherited_from_object': [
+                                    '/clusters/'
+                                ]
+                            }
+                        ]
+                    },
+                    {
+                        'user_name': 'sam',
+                        'all_permissions': [
+                            {
+                                'permission_level': 'CAN_MANAGE',
+                                'inherited': False
+                            }
+                        ]
+                    }
+                ]
+            }
+        }
+    },
+    'ls': {
+        '/': {
+            "object_id": "/directories/1",
+            "object_type": "directory",
+            "access_control_list": [
+                {
+                    "group_name": "admins",
+                    "all_permissions": [
+                        {
+                            "permission_level": "CAN_MANAGE",
+                            "inherited": True,
+                            "inherited_from_object": [
+                                "/directories/"
+                            ]
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}
+
+
+@pytest.fixture()
+def permissions_sdk_mock():
+    with mock.patch('databricks_cli.permissions.api.PermissionsService') as SdkMock:
+        _permissions_sdk_mock = mock.MagicMock()
+        SdkMock.return_value = _permissions_sdk_mock
+        # _permissions_sdk_mock.get_cluster = mock.MagicMock(return_value={})
+
+        yield _permissions_sdk_mock
+
+
+def help_test(cli_function, service_function=None, rv=None, args=None, format_result=True):
+    """
+    This function makes testing the cli functions that just pass data through simpler
+    """
+
+    if args is None:
+        args = []
+
+    with mock.patch('databricks_cli.permissions.cli.click.echo') as echo_mock:
+        if service_function:
+            service_function.return_value = rv
+        runner = CliRunner()
+        result = runner.invoke(cli_function, args)
+        if result.exit_code != 0:
+            print(result.output)
+        if format_result:
+            expected = pretty_format(rv)
+        else:
+            expected = rv
+        assert echo_mock.call_args[0][0] == expected
+
+
+@pytest.fixture()
+def perms_api_mock():
+    with mock.patch('databricks_cli.permissions.cli.PermissionsApi') as PermissionsApiMock:
+        _perms_api_mock = mock.MagicMock()
+        PermissionsApiMock.return_value = _perms_api_mock
+        yield _perms_api_mock
+
+
+@provide_conf
+def test_get_cli(perms_api_mock):
+    return_value = PERMISSIONS_RETURNS['get']['clusters']
+    perms_api_mock.get_permissions.return_value = return_value
+    help_test(cli.get_cli, args=[
+        '--object-type',
+        'clusters',
+        '--object-id',
+        TEST_CLUSTER_ID
+    ], rv=return_value)
+
+    assert perms_api_mock.get_permissions.call_args[0][0] == 'clusters'
+    assert perms_api_mock.get_permissions.call_args[0][1] == TEST_CLUSTER_ID
+
+
+@provide_conf
+def test_list_permissions_types_cli(permissions_sdk_mock):
+    for perm_type in PermissionTargets.values():
+        return_value = PERMISSIONS_RETURNS['list-permissions'][perm_type]
+        permissions_sdk_mock.get_possible_permissions.return_value = return_value
+        help_test(cli.list_permissions_types_cli, args=[
+            '--object-type',
+            perm_type,
+            '--object-id',
+            TEST_CLUSTER_ID
+        ], rv=return_value)
+
+
+@provide_conf
+def test_add_cluster_manage(permissions_sdk_mock):
+    perm_type = 'clusters'
+
+    for add_type in ['user-name', 'group-name']:
+        return_value = PERMISSIONS_RETURNS['add'][perm_type]['add-manage-{}'.format(add_type)]
+        permissions_sdk_mock.add_permissions.return_value = return_value
+        help_test(cli.add_cli, args=[
+            '--object-type',
+            perm_type,
+            '--object-id',
+            TEST_CLUSTER_ID,
+            '--{}'.format(add_type),
+            'sam',
+            '--permission-level',
+            'manage'
+        ], rv=return_value)
+
+
+@provide_conf
+def test_list_permissions_targets_cli():
+    help_test(cli.list_permissions_targets_cli, args=None, rv=cli.POSSIBLE_OBJECT_TYPES,
+              format_result=False)
+
+
+@provide_conf
+def test_list_permissions_level_cli():
+    help_test(cli.list_permissions_level_cli, args=None, rv=cli.POSSIBLE_PERMISSION_LEVELS,
+              format_result=False)
+
+
+@pytest.fixture()
+def workspace_api_mock():
+    with mock.patch('databricks_cli.permissions.cli.WorkspaceApi') as WorkspaceApiMock:
+        workspace_api_mock = mock.MagicMock()
+        WorkspaceApiMock.return_value = workspace_api_mock
+        yield workspace_api_mock
+
+
+@provide_conf
+def test_directory_cli_missing_path(permissions_sdk_mock, workspace_api_mock):
+    workspace_api_mock.get_id_for_directory.return_value = None
+    help_test(cli.directory_cli,
+              args=[
+                  '--path',
+                  '/workspace/missing.txt'
+              ],
+              rv='Failed to find id for /workspace/missing.txt',
+              format_result=False)
+
+
+@provide_conf
+def test_directory_cli(permissions_sdk_mock, workspace_api_mock):
+    workspace_api_mock.get_id_for_directory.return_value = ['1']
+    permissions_sdk_mock.get_permissions.return_value = {
+        'object_id': '/directories/1',
+        'object_type': 'directory',
+        'access_control_list': [
+            {
+                'group_name': 'admins',
+                'all_permissions': [
+                    {
+                        'permission_level': 'CAN_MANAGE',
+                        'inherited': True,
+                        'inherited_from_object': [
+                            '/directories/'
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+
+    help_test(cli.directory_cli,
+              args=[
+                  '--path',
+                  '/'
+              ],
+              rv=PERMISSIONS_RETURNS['ls']['/'])