diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py
index 7ef42ef7da7aa20561c84d14eb047fe8fc9a44c6..df6b6591bacca081e5e1ea6d2212770059119ee8 100644
--- a/homeassistant/components/zha/api.py
+++ b/homeassistant/components/zha/api.py
@@ -14,7 +14,8 @@ import homeassistant.helpers.config_validation as cv
 from .core.const import (
     DOMAIN, ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, ATTR_ATTRIBUTE, ATTR_VALUE,
     ATTR_MANUFACTURER, ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_ARGS, IN, OUT,
-    CLIENT_COMMANDS, SERVER_COMMANDS, SERVER, NAME, ATTR_ENDPOINT_ID)
+    CLIENT_COMMANDS, SERVER_COMMANDS, SERVER, NAME, ATTR_ENDPOINT_ID,
+    DATA_ZHA_GATEWAY, DATA_ZHA)
 from .core.helpers import get_matched_clusters, async_is_bindable_target
 
 _LOGGER = logging.getLogger(__name__)
@@ -71,73 +72,308 @@ SERVICE_SCHEMAS = {
     }),
 }
 
-WS_RECONFIGURE_NODE = 'zha/devices/reconfigure'
-SCHEMA_WS_RECONFIGURE_NODE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
-    vol.Required(TYPE): WS_RECONFIGURE_NODE,
-    vol.Required(ATTR_IEEE): str
-})
 
-WS_DEVICES = 'zha/devices'
-SCHEMA_WS_LIST_DEVICES = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
-    vol.Required(TYPE): WS_DEVICES,
+@websocket_api.async_response
+@websocket_api.websocket_command({
+    vol.Required(TYPE): 'zha/devices'
 })
-
-WS_DEVICE_CLUSTERS = 'zha/devices/clusters'
-SCHEMA_WS_CLUSTERS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
-    vol.Required(TYPE): WS_DEVICE_CLUSTERS,
+async def websocket_get_devices(hass, connection, msg):
+    """Get ZHA devices."""
+    zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
+    devices = [
+        {
+            **device.device_info,
+            'entities': [{
+                'entity_id': entity_ref.reference_id,
+                NAME: entity_ref.device_info[NAME]
+            } for entity_ref in zha_gateway.device_registry[device.ieee]]
+        } for device in zha_gateway.devices.values()
+    ]
+
+    connection.send_result(msg[ID], devices)
+
+
+@websocket_api.async_response
+@websocket_api.websocket_command({
+    vol.Required(TYPE): 'zha/devices/reconfigure',
     vol.Required(ATTR_IEEE): str
 })
-
-WS_DEVICE_CLUSTER_ATTRIBUTES = 'zha/devices/clusters/attributes'
-SCHEMA_WS_CLUSTER_ATTRIBUTES = \
-        websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
-            vol.Required(TYPE): WS_DEVICE_CLUSTER_ATTRIBUTES,
-            vol.Required(ATTR_IEEE): str,
-            vol.Required(ATTR_ENDPOINT_ID): int,
-            vol.Required(ATTR_CLUSTER_ID): int,
-            vol.Required(ATTR_CLUSTER_TYPE): str
-        })
-
-WS_READ_CLUSTER_ATTRIBUTE = 'zha/devices/clusters/attributes/value'
-SCHEMA_WS_READ_CLUSTER_ATTRIBUTE = \
-        websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
-            vol.Required(TYPE): WS_READ_CLUSTER_ATTRIBUTE,
-            vol.Required(ATTR_IEEE): str,
-            vol.Required(ATTR_ENDPOINT_ID): int,
-            vol.Required(ATTR_CLUSTER_ID): int,
-            vol.Required(ATTR_CLUSTER_TYPE): str,
-            vol.Required(ATTR_ATTRIBUTE): int,
-            vol.Optional(ATTR_MANUFACTURER): object,
-        })
-
-WS_DEVICE_CLUSTER_COMMANDS = 'zha/devices/clusters/commands'
-SCHEMA_WS_CLUSTER_COMMANDS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
-    vol.Required(TYPE): WS_DEVICE_CLUSTER_COMMANDS,
+async def websocket_reconfigure_node(hass, connection, msg):
+    """Reconfigure a ZHA nodes entities by its ieee address."""
+    zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
+    ieee = msg[ATTR_IEEE]
+    device = zha_gateway.get_device(ieee)
+    _LOGGER.debug("Reconfiguring node with ieee_address: %s", ieee)
+    hass.async_create_task(device.async_configure())
+
+
+@websocket_api.async_response
+@websocket_api.websocket_command({
+    vol.Required(TYPE): 'zha/devices/clusters',
+    vol.Required(ATTR_IEEE): str
+})
+async def websocket_device_clusters(hass, connection, msg):
+    """Return a list of device clusters."""
+    zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
+    ieee = msg[ATTR_IEEE]
+    zha_device = zha_gateway.get_device(ieee)
+    response_clusters = []
+    if zha_device is not None:
+        clusters_by_endpoint = zha_device.async_get_clusters()
+        for ep_id, clusters in clusters_by_endpoint.items():
+            for c_id, cluster in clusters[IN].items():
+                response_clusters.append({
+                    TYPE: IN,
+                    ID: c_id,
+                    NAME: cluster.__class__.__name__,
+                    'endpoint_id': ep_id
+                })
+            for c_id, cluster in clusters[OUT].items():
+                response_clusters.append({
+                    TYPE: OUT,
+                    ID: c_id,
+                    NAME: cluster.__class__.__name__,
+                    'endpoint_id': ep_id
+                })
+
+    connection.send_result(msg[ID], response_clusters)
+
+
+@websocket_api.async_response
+@websocket_api.websocket_command({
+    vol.Required(TYPE): 'zha/devices/clusters/attributes',
     vol.Required(ATTR_IEEE): str,
     vol.Required(ATTR_ENDPOINT_ID): int,
     vol.Required(ATTR_CLUSTER_ID): int,
     vol.Required(ATTR_CLUSTER_TYPE): str
 })
-
-WS_BIND_DEVICE = 'zha/devices/bind'
-SCHEMA_WS_BIND_DEVICE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
-    vol.Required(TYPE): WS_BIND_DEVICE,
+async def websocket_device_cluster_attributes(hass, connection, msg):
+    """Return a list of cluster attributes."""
+    zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
+    ieee = msg[ATTR_IEEE]
+    endpoint_id = msg[ATTR_ENDPOINT_ID]
+    cluster_id = msg[ATTR_CLUSTER_ID]
+    cluster_type = msg[ATTR_CLUSTER_TYPE]
+    cluster_attributes = []
+    zha_device = zha_gateway.get_device(ieee)
+    attributes = None
+    if zha_device is not None:
+        attributes = zha_device.async_get_cluster_attributes(
+            endpoint_id,
+            cluster_id,
+            cluster_type)
+        if attributes is not None:
+            for attr_id in attributes:
+                cluster_attributes.append(
+                    {
+                        ID: attr_id,
+                        NAME: attributes[attr_id][0]
+                    }
+                )
+    _LOGGER.debug("Requested attributes for: %s %s %s %s",
+                  "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id),
+                  "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type),
+                  "{}: [{}]".format(ATTR_ENDPOINT_ID, endpoint_id),
+                  "{}: [{}]".format(RESPONSE, cluster_attributes)
+                  )
+
+    connection.send_result(msg[ID], cluster_attributes)
+
+
+@websocket_api.async_response
+@websocket_api.websocket_command({
+    vol.Required(TYPE): 'zha/devices/clusters/commands',
+    vol.Required(ATTR_IEEE): str,
+    vol.Required(ATTR_ENDPOINT_ID): int,
+    vol.Required(ATTR_CLUSTER_ID): int,
+    vol.Required(ATTR_CLUSTER_TYPE): str
+})
+async def websocket_device_cluster_commands(hass, connection, msg):
+    """Return a list of cluster commands."""
+    zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
+    cluster_id = msg[ATTR_CLUSTER_ID]
+    cluster_type = msg[ATTR_CLUSTER_TYPE]
+    ieee = msg[ATTR_IEEE]
+    endpoint_id = msg[ATTR_ENDPOINT_ID]
+    zha_device = zha_gateway.get_device(ieee)
+    cluster_commands = []
+    commands = None
+    if zha_device is not None:
+        commands = zha_device.async_get_cluster_commands(
+            endpoint_id,
+            cluster_id,
+            cluster_type)
+
+        if commands is not None:
+            for cmd_id in commands[CLIENT_COMMANDS]:
+                cluster_commands.append(
+                    {
+                        TYPE: CLIENT,
+                        ID: cmd_id,
+                        NAME: commands[CLIENT_COMMANDS][cmd_id][0]
+                    }
+                )
+            for cmd_id in commands[SERVER_COMMANDS]:
+                cluster_commands.append(
+                    {
+                        TYPE: SERVER,
+                        ID: cmd_id,
+                        NAME: commands[SERVER_COMMANDS][cmd_id][0]
+                    }
+                )
+    _LOGGER.debug("Requested commands for: %s %s %s %s",
+                  "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id),
+                  "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type),
+                  "{}: [{}]".format(ATTR_ENDPOINT_ID, endpoint_id),
+                  "{}: [{}]".format(RESPONSE, cluster_commands)
+                  )
+
+    connection.send_result(msg[ID], cluster_commands)
+
+
+@websocket_api.async_response
+@websocket_api.websocket_command({
+    vol.Required(TYPE): 'zha/devices/clusters/attributes/value',
+    vol.Required(ATTR_IEEE): str,
+    vol.Required(ATTR_ENDPOINT_ID): int,
+    vol.Required(ATTR_CLUSTER_ID): int,
+    vol.Required(ATTR_CLUSTER_TYPE): str,
+    vol.Required(ATTR_ATTRIBUTE): int,
+    vol.Optional(ATTR_MANUFACTURER): object,
+})
+async def websocket_read_zigbee_cluster_attributes(hass, connection, msg):
+    """Read zigbee attribute for cluster on zha entity."""
+    zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
+    ieee = msg[ATTR_IEEE]
+    endpoint_id = msg[ATTR_ENDPOINT_ID]
+    cluster_id = msg[ATTR_CLUSTER_ID]
+    cluster_type = msg[ATTR_CLUSTER_TYPE]
+    attribute = msg[ATTR_ATTRIBUTE]
+    manufacturer = msg.get(ATTR_MANUFACTURER) or None
+    zha_device = zha_gateway.get_device(ieee)
+    success = failure = None
+    if zha_device is not None:
+        cluster = zha_device.async_get_cluster(
+            endpoint_id, cluster_id, cluster_type=cluster_type)
+        success, failure = await cluster.read_attributes(
+            [attribute],
+            allow_cache=False,
+            only_cache=False,
+            manufacturer=manufacturer
+        )
+    _LOGGER.debug("Read attribute for: %s %s %s %s %s %s %s",
+                  "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id),
+                  "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type),
+                  "{}: [{}]".format(ATTR_ENDPOINT_ID, endpoint_id),
+                  "{}: [{}]".format(ATTR_ATTRIBUTE, attribute),
+                  "{}: [{}]".format(ATTR_MANUFACTURER, manufacturer),
+                  "{}: [{}]".format(RESPONSE, str(success.get(attribute))),
+                  "{}: [{}]".format('failure', failure)
+                  )
+    connection.send_result(msg[ID], str(success.get(attribute)))
+
+
+@websocket_api.async_response
+@websocket_api.websocket_command({
+    vol.Required(TYPE): 'zha/devices/bindable',
+    vol.Required(ATTR_IEEE): str,
+})
+async def websocket_get_bindable_devices(hass, connection, msg):
+    """Directly bind devices."""
+    zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
+    source_ieee = msg[ATTR_IEEE]
+    source_device = zha_gateway.get_device(source_ieee)
+    devices = [
+        {
+            **device.device_info
+        } for device in zha_gateway.devices.values() if
+        async_is_bindable_target(source_device, device)
+    ]
+
+    _LOGGER.debug("Get bindable devices: %s %s",
+                  "{}: [{}]".format(ATTR_SOURCE_IEEE, source_ieee),
+                  "{}: [{}]".format('bindable devices:', devices)
+                  )
+
+    connection.send_message(websocket_api.result_message(
+        msg[ID],
+        devices
+    ))
+
+
+@websocket_api.async_response
+@websocket_api.websocket_command({
+    vol.Required(TYPE): 'zha/devices/bind',
     vol.Required(ATTR_SOURCE_IEEE): str,
     vol.Required(ATTR_TARGET_IEEE): str
 })
-
-WS_UNBIND_DEVICE = 'zha/devices/unbind'
-SCHEMA_WS_UNBIND_DEVICE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
-    vol.Required(TYPE): WS_UNBIND_DEVICE,
+async def websocket_bind_devices(hass, connection, msg):
+    """Directly bind devices."""
+    zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
+    source_ieee = msg[ATTR_SOURCE_IEEE]
+    target_ieee = msg[ATTR_TARGET_IEEE]
+    await async_binding_operation(
+        zha_gateway, source_ieee, target_ieee, BIND_REQUEST)
+    _LOGGER.info("Issue bind devices: %s %s",
+                 "{}: [{}]".format(ATTR_SOURCE_IEEE, source_ieee),
+                 "{}: [{}]".format(ATTR_TARGET_IEEE, target_ieee)
+                 )
+
+
+@websocket_api.async_response
+@websocket_api.websocket_command({
+    vol.Required(TYPE): 'zha/devices/unbind',
     vol.Required(ATTR_SOURCE_IEEE): str,
     vol.Required(ATTR_TARGET_IEEE): str
 })
-
-WS_BINDABLE_DEVICES = 'zha/devices/bindable'
-SCHEMA_WS_BINDABLE_DEVICES = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
-    vol.Required(TYPE): WS_BINDABLE_DEVICES,
-    vol.Required(ATTR_IEEE): str
-})
+async def websocket_unbind_devices(hass, connection, msg):
+    """Remove a direct binding between devices."""
+    zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
+    source_ieee = msg[ATTR_SOURCE_IEEE]
+    target_ieee = msg[ATTR_TARGET_IEEE]
+    await async_binding_operation(
+        zha_gateway, source_ieee, target_ieee, UNBIND_REQUEST)
+    _LOGGER.info("Issue unbind devices: %s %s",
+                 "{}: [{}]".format(ATTR_SOURCE_IEEE, source_ieee),
+                 "{}: [{}]".format(ATTR_TARGET_IEEE, target_ieee)
+                 )
+
+
+async def async_binding_operation(zha_gateway, source_ieee, target_ieee,
+                                  operation):
+    """Create or remove a direct zigbee binding between 2 devices."""
+    from zigpy.zdo import types as zdo_types
+    source_device = zha_gateway.get_device(source_ieee)
+    target_device = zha_gateway.get_device(target_ieee)
+
+    clusters_to_bind = await get_matched_clusters(source_device,
+                                                  target_device)
+
+    bind_tasks = []
+    for cluster_pair in clusters_to_bind:
+        destination_address = zdo_types.MultiAddress()
+        destination_address.addrmode = 3
+        destination_address.ieee = target_device.ieee
+        destination_address.endpoint = \
+            cluster_pair.target_cluster.endpoint.endpoint_id
+
+        zdo = cluster_pair.source_cluster.endpoint.device.zdo
+
+        _LOGGER.debug("processing binding operation for: %s %s %s",
+                      "{}: [{}]".format(ATTR_SOURCE_IEEE, source_ieee),
+                      "{}: [{}]".format(ATTR_TARGET_IEEE, target_ieee),
+                      "{}: {}".format(
+                          'cluster',
+                          cluster_pair.source_cluster.cluster_id)
+                      )
+        bind_tasks.append(zdo.request(
+            operation,
+            source_device.ieee,
+            cluster_pair.source_cluster.endpoint.endpoint_id,
+            cluster_pair.source_cluster.cluster_id,
+            destination_address
+        ))
+    await asyncio.gather(*bind_tasks)
 
 
 def async_load_api(hass, application_controller, zha_gateway):
@@ -237,301 +473,18 @@ def async_load_api(hass, application_controller, zha_gateway):
                                      SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND
                                  ])
 
-    @websocket_api.async_response
-    async def websocket_get_devices(hass, connection, msg):
-        """Get ZHA devices."""
-        devices = [
-            {
-                **device.device_info,
-                'entities': [{
-                    'entity_id': entity_ref.reference_id,
-                    NAME: entity_ref.device_info[NAME]
-                } for entity_ref in zha_gateway.device_registry[device.ieee]]
-            } for device in zha_gateway.devices.values()
-        ]
-
-        connection.send_message(websocket_api.result_message(
-            msg[ID],
-            devices
-        ))
-
-    hass.components.websocket_api.async_register_command(
-        WS_DEVICES, websocket_get_devices,
-        SCHEMA_WS_LIST_DEVICES
-    )
-
-    @websocket_api.async_response
-    async def websocket_reconfigure_node(hass, connection, msg):
-        """Reconfigure a ZHA nodes entities by its ieee address."""
-        ieee = msg[ATTR_IEEE]
-        device = zha_gateway.get_device(ieee)
-        _LOGGER.debug("Reconfiguring node with ieee_address: %s", ieee)
-        hass.async_create_task(device.async_configure())
-
-    hass.components.websocket_api.async_register_command(
-        WS_RECONFIGURE_NODE, websocket_reconfigure_node,
-        SCHEMA_WS_RECONFIGURE_NODE
-    )
-
-    @websocket_api.async_response
-    async def websocket_get_bindable_devices(hass, connection, msg):
-        """Directly bind devices."""
-        source_ieee = msg[ATTR_IEEE]
-        source_device = zha_gateway.get_device(source_ieee)
-        devices = [
-            {
-                **device.device_info
-            } for device in zha_gateway.devices.values() if
-            async_is_bindable_target(source_device, device)
-        ]
-
-        _LOGGER.debug("Get bindable devices: %s %s",
-                      "{}: [{}]".format(ATTR_SOURCE_IEEE, source_ieee),
-                      "{}: [{}]".format('bindable devices:', devices)
-                      )
-
-        connection.send_message(websocket_api.result_message(
-            msg[ID],
-            devices
-        ))
-
-    hass.components.websocket_api.async_register_command(
-        WS_BINDABLE_DEVICES, websocket_get_bindable_devices,
-        SCHEMA_WS_BINDABLE_DEVICES
-    )
-
-    @websocket_api.async_response
-    async def websocket_bind_devices(hass, connection, msg):
-        """Directly bind devices."""
-        source_ieee = msg[ATTR_SOURCE_IEEE]
-        target_ieee = msg[ATTR_TARGET_IEEE]
-        await async_binding_operation(
-            source_ieee, target_ieee, BIND_REQUEST)
-        _LOGGER.info("Issue bind devices: %s %s",
-                     "{}: [{}]".format(ATTR_SOURCE_IEEE, source_ieee),
-                     "{}: [{}]".format(ATTR_TARGET_IEEE, target_ieee)
-                     )
-
-    hass.components.websocket_api.async_register_command(
-        WS_BIND_DEVICE, websocket_bind_devices,
-        SCHEMA_WS_BIND_DEVICE
-    )
-
-    @websocket_api.async_response
-    async def websocket_unbind_devices(hass, connection, msg):
-        """Remove a direct binding between devices."""
-        source_ieee = msg[ATTR_SOURCE_IEEE]
-        target_ieee = msg[ATTR_TARGET_IEEE]
-        await async_binding_operation(
-            source_ieee, target_ieee, UNBIND_REQUEST)
-        _LOGGER.info("Issue unbind devices: %s %s",
-                     "{}: [{}]".format(ATTR_SOURCE_IEEE, source_ieee),
-                     "{}: [{}]".format(ATTR_TARGET_IEEE, target_ieee)
-                     )
-
-    hass.components.websocket_api.async_register_command(
-        WS_UNBIND_DEVICE, websocket_unbind_devices,
-        SCHEMA_WS_UNBIND_DEVICE
-    )
-
-    async def async_binding_operation(source_ieee, target_ieee,
-                                      operation):
-        """Create or remove a direct zigbee binding between 2 devices."""
-        from zigpy.zdo import types as zdo_types
-        source_device = zha_gateway.get_device(source_ieee)
-        target_device = zha_gateway.get_device(target_ieee)
-
-        clusters_to_bind = await get_matched_clusters(source_device,
-                                                      target_device)
-
-        bind_tasks = []
-        for cluster_pair in clusters_to_bind:
-            destination_address = zdo_types.MultiAddress()
-            destination_address.addrmode = 3
-            destination_address.ieee = target_device.ieee
-            destination_address.endpoint = \
-                cluster_pair.target_cluster.endpoint.endpoint_id
-
-            zdo = cluster_pair.source_cluster.endpoint.device.zdo
-
-            _LOGGER.debug("processing binding operation for: %s %s %s",
-                          "{}: [{}]".format(ATTR_SOURCE_IEEE, source_ieee),
-                          "{}: [{}]".format(ATTR_TARGET_IEEE, target_ieee),
-                          "{}: {}".format(
-                              'cluster',
-                              cluster_pair.source_cluster.cluster_id)
-                          )
-            bind_tasks.append(zdo.request(
-                operation,
-                source_device.ieee,
-                cluster_pair.source_cluster.endpoint.endpoint_id,
-                cluster_pair.source_cluster.cluster_id,
-                destination_address
-            ))
-        await asyncio.gather(*bind_tasks)
-
-    @websocket_api.async_response
-    async def websocket_device_clusters(hass, connection, msg):
-        """Return a list of device clusters."""
-        ieee = msg[ATTR_IEEE]
-        zha_device = zha_gateway.get_device(ieee)
-        response_clusters = []
-        if zha_device is not None:
-            clusters_by_endpoint = zha_device.async_get_clusters()
-            for ep_id, clusters in clusters_by_endpoint.items():
-                for c_id, cluster in clusters[IN].items():
-                    response_clusters.append({
-                        TYPE: IN,
-                        ID: c_id,
-                        NAME: cluster.__class__.__name__,
-                        'endpoint_id': ep_id
-                    })
-                for c_id, cluster in clusters[OUT].items():
-                    response_clusters.append({
-                        TYPE: OUT,
-                        ID: c_id,
-                        NAME: cluster.__class__.__name__,
-                        'endpoint_id': ep_id
-                    })
-
-        connection.send_message(websocket_api.result_message(
-            msg[ID],
-            response_clusters
-        ))
-
-    hass.components.websocket_api.async_register_command(
-        WS_DEVICE_CLUSTERS, websocket_device_clusters,
-        SCHEMA_WS_CLUSTERS
-    )
-
-    @websocket_api.async_response
-    async def websocket_device_cluster_attributes(hass, connection, msg):
-        """Return a list of cluster attributes."""
-        ieee = msg[ATTR_IEEE]
-        endpoint_id = msg[ATTR_ENDPOINT_ID]
-        cluster_id = msg[ATTR_CLUSTER_ID]
-        cluster_type = msg[ATTR_CLUSTER_TYPE]
-        cluster_attributes = []
-        zha_device = zha_gateway.get_device(ieee)
-        attributes = None
-        if zha_device is not None:
-            attributes = zha_device.async_get_cluster_attributes(
-                endpoint_id,
-                cluster_id,
-                cluster_type)
-            if attributes is not None:
-                for attr_id in attributes:
-                    cluster_attributes.append(
-                        {
-                            ID: attr_id,
-                            NAME: attributes[attr_id][0]
-                        }
-                    )
-        _LOGGER.debug("Requested attributes for: %s %s %s %s",
-                      "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id),
-                      "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type),
-                      "{}: [{}]".format(ATTR_ENDPOINT_ID, endpoint_id),
-                      "{}: [{}]".format(RESPONSE, cluster_attributes)
-                      )
-
-        connection.send_message(websocket_api.result_message(
-            msg[ID],
-            cluster_attributes
-        ))
-
-    hass.components.websocket_api.async_register_command(
-        WS_DEVICE_CLUSTER_ATTRIBUTES, websocket_device_cluster_attributes,
-        SCHEMA_WS_CLUSTER_ATTRIBUTES
-    )
-
-    @websocket_api.async_response
-    async def websocket_device_cluster_commands(hass, connection, msg):
-        """Return a list of cluster commands."""
-        cluster_id = msg[ATTR_CLUSTER_ID]
-        cluster_type = msg[ATTR_CLUSTER_TYPE]
-        ieee = msg[ATTR_IEEE]
-        endpoint_id = msg[ATTR_ENDPOINT_ID]
-        zha_device = zha_gateway.get_device(ieee)
-        cluster_commands = []
-        commands = None
-        if zha_device is not None:
-            commands = zha_device.async_get_cluster_commands(
-                endpoint_id,
-                cluster_id,
-                cluster_type)
-
-            if commands is not None:
-                for cmd_id in commands[CLIENT_COMMANDS]:
-                    cluster_commands.append(
-                        {
-                            TYPE: CLIENT,
-                            ID: cmd_id,
-                            NAME: commands[CLIENT_COMMANDS][cmd_id][0]
-                        }
-                    )
-                for cmd_id in commands[SERVER_COMMANDS]:
-                    cluster_commands.append(
-                        {
-                            TYPE: SERVER,
-                            ID: cmd_id,
-                            NAME: commands[SERVER_COMMANDS][cmd_id][0]
-                        }
-                    )
-        _LOGGER.debug("Requested commands for: %s %s %s %s",
-                      "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id),
-                      "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type),
-                      "{}: [{}]".format(ATTR_ENDPOINT_ID, endpoint_id),
-                      "{}: [{}]".format(RESPONSE, cluster_commands)
-                      )
-
-        connection.send_message(websocket_api.result_message(
-            msg[ID],
-            cluster_commands
-        ))
-
-    hass.components.websocket_api.async_register_command(
-        WS_DEVICE_CLUSTER_COMMANDS, websocket_device_cluster_commands,
-        SCHEMA_WS_CLUSTER_COMMANDS
-    )
-
-    @websocket_api.async_response
-    async def websocket_read_zigbee_cluster_attributes(hass, connection, msg):
-        """Read zigbee attribute for cluster on zha entity."""
-        ieee = msg[ATTR_IEEE]
-        endpoint_id = msg[ATTR_ENDPOINT_ID]
-        cluster_id = msg[ATTR_CLUSTER_ID]
-        cluster_type = msg[ATTR_CLUSTER_TYPE]
-        attribute = msg[ATTR_ATTRIBUTE]
-        manufacturer = msg.get(ATTR_MANUFACTURER) or None
-        zha_device = zha_gateway.get_device(ieee)
-        success = failure = None
-        if zha_device is not None:
-            cluster = zha_device.async_get_cluster(
-                endpoint_id, cluster_id, cluster_type=cluster_type)
-            success, failure = await cluster.read_attributes(
-                [attribute],
-                allow_cache=False,
-                only_cache=False,
-                manufacturer=manufacturer
-            )
-        _LOGGER.debug("Read attribute for: %s %s %s %s %s %s %s",
-                      "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id),
-                      "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type),
-                      "{}: [{}]".format(ATTR_ENDPOINT_ID, endpoint_id),
-                      "{}: [{}]".format(ATTR_ATTRIBUTE, attribute),
-                      "{}: [{}]".format(ATTR_MANUFACTURER, manufacturer),
-                      "{}: [{}]".format(RESPONSE, str(success.get(attribute))),
-                      "{}: [{}]".format('failure', failure)
-                      )
-        connection.send_message(websocket_api.result_message(
-            msg[ID],
-            str(success.get(attribute))
-        ))
-
-    hass.components.websocket_api.async_register_command(
-        WS_READ_CLUSTER_ATTRIBUTE, websocket_read_zigbee_cluster_attributes,
-        SCHEMA_WS_READ_CLUSTER_ATTRIBUTE
-    )
+    websocket_api.async_register_command(hass, websocket_get_devices)
+    websocket_api.async_register_command(hass, websocket_reconfigure_node)
+    websocket_api.async_register_command(hass, websocket_device_clusters)
+    websocket_api.async_register_command(
+        hass, websocket_device_cluster_attributes)
+    websocket_api.async_register_command(
+        hass, websocket_device_cluster_commands)
+    websocket_api.async_register_command(
+        hass, websocket_read_zigbee_cluster_attributes)
+    websocket_api.async_register_command(hass, websocket_get_bindable_devices)
+    websocket_api.async_register_command(hass, websocket_bind_devices)
+    websocket_api.async_register_command(hass, websocket_unbind_devices)
 
 
 def async_unload_api(hass):
diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py
index 3c8adb097482117f0deef03be126dd48d59396d2..757ffbaa328c4b7ce1e7293df686d55091d951a8 100644
--- a/homeassistant/components/zha/core/const.py
+++ b/homeassistant/components/zha/core/const.py
@@ -14,6 +14,7 @@ DATA_ZHA_RADIO = 'zha_radio'
 DATA_ZHA_DISPATCHERS = 'zha_dispatchers'
 DATA_ZHA_CORE_COMPONENT = 'zha_core_component'
 DATA_ZHA_CORE_EVENTS = 'zha_core_events'
+DATA_ZHA_GATEWAY = 'zha_gateway'
 ZHA_DISCOVERY_NEW = 'zha_discovery_new_{}'
 
 COMPONENTS = [
diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py
index 35a79311253e75eb9bbaa7c5962e3a132c71c285..a498e1e8ee17bd425b758298e6d821f4eb3d501a 100644
--- a/homeassistant/components/zha/core/gateway.py
+++ b/homeassistant/components/zha/core/gateway.py
@@ -22,7 +22,8 @@ from .const import (
     OCCUPANCY, CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_IMMEDIATE,
     REPORT_CONFIG_ASAP, REPORT_CONFIG_DEFAULT, REPORT_CONFIG_MIN_INT,
     REPORT_CONFIG_MAX_INT, REPORT_CONFIG_OP, SIGNAL_REMOVE,
-    NO_SENSOR_CLUSTERS, POWER_CONFIGURATION_CHANNEL, BINDABLE_CLUSTERS)
+    NO_SENSOR_CLUSTERS, POWER_CONFIGURATION_CHANNEL, BINDABLE_CLUSTERS,
+    DATA_ZHA_GATEWAY)
 from .device import ZHADevice, DeviceStatus
 from ..device_entity import ZhaDeviceEntity
 from .channels import (
@@ -52,6 +53,7 @@ class ZHAGateway:
         self._devices = {}
         self._device_registry = collections.defaultdict(list)
         hass.data[DATA_ZHA][DATA_ZHA_CORE_COMPONENT] = self._component
+        hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self
 
     def device_joined(self, device):
         """Handle device joined.
diff --git a/tests/components/zha/test_api.py b/tests/components/zha/test_api.py
index 616a94e8b89928ae547fca528ea5a29d3f31d9ca..5858c7560d986221d1d24352e0ec3cad7304e246 100644
--- a/tests/components/zha/test_api.py
+++ b/tests/components/zha/test_api.py
@@ -3,9 +3,7 @@ from unittest.mock import Mock
 import pytest
 from homeassistant.components.switch import DOMAIN
 from homeassistant.components.zha.api import (
-    async_load_api, WS_DEVICE_CLUSTERS, ATTR_IEEE, TYPE,
-    ID, WS_DEVICE_CLUSTER_ATTRIBUTES, WS_DEVICE_CLUSTER_COMMANDS,
-    WS_DEVICES
+    async_load_api, ATTR_IEEE, TYPE, ID
 )
 from homeassistant.components.zha.core.const import (
     ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, IN, IEEE, MODEL, NAME, QUIRK_APPLIED,
@@ -38,7 +36,7 @@ async def test_device_clusters(hass, config_entry, zha_gateway, zha_client):
     """Test getting device cluster info."""
     await zha_client.send_json({
         ID: 5,
-        TYPE: WS_DEVICE_CLUSTERS,
+        TYPE: 'zha/devices/clusters',
         ATTR_IEEE: '00:0d:6f:00:0a:90:69:e7'
     })
 
@@ -64,7 +62,7 @@ async def test_device_cluster_attributes(
     """Test getting device cluster attributes."""
     await zha_client.send_json({
         ID: 5,
-        TYPE: WS_DEVICE_CLUSTER_ATTRIBUTES,
+        TYPE: 'zha/devices/clusters/attributes',
         ATTR_ENDPOINT_ID: 1,
         ATTR_IEEE: '00:0d:6f:00:0a:90:69:e7',
         ATTR_CLUSTER_ID: 6,
@@ -86,7 +84,7 @@ async def test_device_cluster_commands(
     """Test getting device cluster commands."""
     await zha_client.send_json({
         ID: 5,
-        TYPE: WS_DEVICE_CLUSTER_COMMANDS,
+        TYPE: 'zha/devices/clusters/commands',
         ATTR_ENDPOINT_ID: 1,
         ATTR_IEEE: '00:0d:6f:00:0a:90:69:e7',
         ATTR_CLUSTER_ID: 6,
@@ -109,7 +107,7 @@ async def test_list_devices(
     """Test getting entity cluster commands."""
     await zha_client.send_json({
         ID: 5,
-        TYPE: WS_DEVICES
+        TYPE: 'zha/devices'
     })
 
     msg = await zha_client.receive_json()