Repository: TACC/agavepy

Issue:
Typos on documentation
The documentation is available on https://agavepy.readthedocs.io/en/latest/
The source is in [docs/docsite](https://github.com/TACC/agavepy/tree/develop/docs/docsite)
Some typos already found:

1. [About Agavepy](https://agavepy.readthedocs.io/en/latest/index.html) - AgavePy is a client that will allow you to script your `interacttion`
2. [Installation guide](https://agavepy.readthedocs.io/en/latest/docsite/installation_guide/installation.html) - In this guide `wewill`
3. [What version to install](https://agavepy.readthedocs.io/en/latest/docsite/installation_guide/installation.html) - Some features may be missing from `ofifical`

Discussion:

Patch:
diff --git a/agavepy/agave.py b/agavepy/agave.py
index a01a71b..0acbf6b 100644
--- a/agavepy/agave.py
+++ b/agavepy/agave.py
@@ -23,6 +23,8 @@ from agavepy.tenants import tenant_list
 from agavepy.clients import clients_create, clients_list
 from agavepy.tokens import token_create, refresh_token
 from agavepy.utils import load_config, save_config
+from agavepy.files import files_download
+
 
 import sys
 sys.path.insert(0, os.path.dirname(__file__))
@@ -700,6 +702,9 @@ class Agave(object):
 
     def refresh_tokens(self):
         """ Refresh oauth token
+
+        Check if tokens need to be created or refreshed. If tokens need to be
+        refreshed then this method will do so. Otherwise, the method will exit.
         """
         try:
             created_t = int(self.created_at)
@@ -715,13 +720,22 @@ class Agave(object):
             token_data = refresh_token(
                     self.api_key, self.api_secret, self.refresh_token, self.api_server)
 
-            self.token  = token_data["access_token"]
+            self.token         = token_data["access_token"]
             self.refresh_token = token_data["refresh_token"]
             self.expires_in    = token_data["expires_in"]
             self.created_at    = token_data["created_at"]
             self.expires_at    = token_data["expires_at"]
 
 
+    def files_download(self, source, destination):
+        """ Download files from remote system
+        """
+        # Check if tokens need to be refreshed.
+        self.refresh_tokens()
+
+        # Download file.
+        files_download(self.api_server, self.token, source, destination)
+
 
 
 
diff --git a/agavepy/files/__init__.py b/agavepy/files/__init__.py
new file mode 100644
index 0000000..f72d297
--- /dev/null
+++ b/agavepy/files/__init__.py
@@ -0,0 +1,1 @@
+from .download import files_download
diff --git a/agavepy/files/download.py b/agavepy/files/download.py
new file mode 100644
index 0000000..a1a27ad
--- /dev/null
+++ b/agavepy/files/download.py
@@ -0,0 +1,35 @@
+"""
+    copy.py
+"""
+from __future__ import print_function                                           
+import requests                                                                 
+from .exceptions import AgaveFilesError
+from ..utils import handle_bad_response_status_code
+
+
+
+def files_download(tenant_url, access_token, source, destination):
+    """ Copy a file from remote system to host
+
+    PARAMETERS
+    ----------
+    """
+    # Set request endpoint. 
+    endpoint = "{0}/{1}/{2}".format(tenant_url, "files/v2/media/system", source)
+
+    # Make request.
+    try:
+        headers = {"Authorization":"Bearer {0}".format(access_token)}
+        params  = {"pretty": "true"}
+
+        resp = requests.get(endpoint, headers=headers, params=params, stream=True)
+        with open(destination, "wb") as f:
+            for chunk in resp.iter_content(chunk_size=1024):
+                if chunk:
+                    f.write(chunk)
+    except Exception as err:
+        raise AgaveFilesError(err)
+        
+
+    # Handle bad status code.                                               
+    handle_bad_response_status_code(resp)
diff --git a/agavepy/files/exceptions.py b/agavepy/files/exceptions.py
new file mode 100644
index 0000000..feb354c
--- /dev/null
+++ b/agavepy/files/exceptions.py
@@ -0,0 +1,13 @@
+"""
+    exceptions.py
+"""
+
+
+class AgaveFilesError(Exception):
+    """ Handle files-related operation errors
+    """
+    def __init__(self, msg):
+        self.msg = msg
+    
+    def __str__(self):
+        return repr(self.msg)
diff --git a/docs/docsite/authentication/index.rst b/docs/docsite/authentication/index.rst
index 52b65b2..1e60e4f 100644
--- a/docs/docsite/authentication/index.rst
+++ b/docs/docsite/authentication/index.rst
@@ -1,3 +1,5 @@
+.. _authentication:
+
 ==============                                          
 Authentication
 ==============
diff --git a/docs/docsite/contributing_guide/contributing.rst b/docs/docsite/contributing_guide/contributing.rst
index 9d11bfd..8b72ae6 100644
--- a/docs/docsite/contributing_guide/contributing.rst
+++ b/docs/docsite/contributing_guide/contributing.rst
@@ -8,6 +8,7 @@ Contributing Guide
 
 
 .. _reporting_bugs_and_asking_for_features:
+
 Reporting bugs and asking for features
 ``````````````````````````````````````
 
@@ -18,6 +19,7 @@ functionality you believe should be implemented please file an
 
 
 .. _getting_involved:
+
 Getting involved
 ````````````````
 
diff --git a/docs/docsite/files/files-listing.rst b/docs/docsite/files/files-listing.rst
new file mode 100644
index 0000000..3b4b77c
--- /dev/null
+++ b/docs/docsite/files/files-listing.rst
@@ -0,0 +1,731 @@
+*****************
+Methods available
+*****************
+
+Summary: Move and manage data
+
+deleteFromDefaultSystem: Deletes a file or folder.
+==================================================
+``agavepy.files.deleteFromDefaultSystem(sourcefilePath)``
+
+Parameters:
+-----------
+    * **sourcefilePath**: The path of the file relative to the user's default storage location. (string)
+
+
+Response:
+---------
+    * *String*
+
+downloadFromDefaultSystem: Download a file from the user's default storage location.
+====================================================================================
+``agavepy.files.downloadFromDefaultSystem(sourcefilePath)``
+
+Parameters:
+-----------
+    * **sourcefilePath**: The path of the file relative to the user's default storage location. (string)
+
+
+Response:
+---------
+    * *None*
+
+importToDefaultSystem: Import a file via direct upload or importing from a url to the user's default storage location.
+======================================================================================================================
+``agavepy.files.importToDefaultSystem(sourcefilePath, callbackURL=None, fileName=None, fileToUpload=None, fileType=None, urlToIngest=None)``
+
+Parameters:
+-----------
+    * **sourcefilePath**: The path of the file relative to the user's default storage location. (string)
+    * **fileType**: The file format this file is in. Defaults to raw. This will be used in file transform operations. (string)
+    * **callbackURL**: The URI to notify when the import is complete. This can be an email address or http URL. If a URL is given, a GET will be made to this address. URL templating is supported. Valid template values are: ${NAME}, ${SOURCE_FORMAT}, ${DEST_FORMAT}, ${STATUS} (string)
+    * **fileName**: The name of the file after importing. If not specified, the uploaded file name will be used. (string)
+    * **urlToIngest**: The URL to import the file from. This parameter is used if not file is uploaded with this post. (string)
+    * **fileToUpload**: The file object to import. (void)
+
+
+Response:
+---------
+    * *A single RemoteFile object*
+
+**RemoteFile schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/RemoteFile.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "format": {
+          "description": "The file type of the file.", 
+          "type": "string"
+        }, 
+        "lastModified": {
+          "description": "The date this file was last modified in ISO 8601 format.", 
+          "type": "string"
+        }, 
+        "length": {
+          "description": "The length of the file/folder.", 
+          "type": "integer"
+        }, 
+        "mimeType": {
+          "description": "The mime type of the file/folder. If unknown, it defaults to application/binary.", 
+          "type": "string"
+        }, 
+        "name": {
+          "description": "The name of the file/folder.", 
+          "type": "string"
+        }, 
+        "path": {
+          "description": "The absolute path to the file/folder.", 
+          "type": "string"
+        }, 
+        "permissions": {
+          "description": "The system permission of the invoking user on the file/folder.", 
+          "type": "string"
+        }, 
+        "system": {
+          "description": "The systemId of the system where this file lives.", 
+          "type": "string"
+        }, 
+        "type": {
+          "description": "Whether it is a file or folder.", 
+          "type": "string"
+        }
+      }, 
+      "required": [], 
+      "title": "AgavePy RemoteFile schema", 
+      "type": "object"
+    }
+
+manageOnDefaultSystem: Perform an action on a file or folder.
+=============================================================
+``agavepy.files.manageOnDefaultSystem(body, sourcefilePath)``
+
+Parameters:
+-----------
+    * **sourcefilePath**: The path of the file relative to the user's default storage location. (string)
+    * **body**: The operation to perform.  (JSON, FileOperationRequest)
+
+
+**FileOperationRequest schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/FileOperationRequest.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "action": {
+          "description": "Action to perform on the file or folder.", 
+          "enum": [
+            "mkdir", 
+            "rename", 
+            "copy", 
+            "move"
+          ], 
+          "type": "string"
+        }, 
+        "path": {
+          "description": "Destination file or folder.", 
+          "type": "string"
+        }
+      }, 
+      "required": [
+        "action"
+      ], 
+      "title": "AgavePy FileOperationRequest schema", 
+      "type": "object"
+    }
+
+Response:
+---------
+    * *String*
+
+delete: Deletes a file or folder.
+=================================
+``agavepy.files.delete(filePath, systemId)``
+
+Parameters:
+-----------
+    * **systemId**: The unique id of the system on which the data resides. (string)
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+
+
+Response:
+---------
+    * *String*
+
+download: Download a file from the user's default storage location.
+===================================================================
+``agavepy.files.download(filePath, systemId)``
+
+Parameters:
+-----------
+    * **systemId**: The unique id of the system on which the data resides. (string)
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+
+
+Response:
+---------
+    * *None*
+
+importData: Import a file via direct upload or importing from a url to the user's default storage location.
+===========================================================================================================
+``agavepy.files.importData(filePath, systemId, callbackURL=None, fileName=None, fileToUpload=None, fileType=None, notifications=[], urlToIngest=None)``
+
+Parameters:
+-----------
+    * **systemId**: The unique id of the system on which the data resides. (string)
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+    * **fileType**: The file format this file is in. Defaults to raw. This will be used in file transform operations. (string)
+    * **callbackURL**: The URI to notify when the import is complete. This can be an email address or http URL. If a URL is given, a GET will be made to this address. URL templating is supported. Valid template values are: ${NAME}, ${SOURCE_FORMAT}, ${DEST_FORMAT}, ${STATUS} (string)
+    * **fileName**: The name of the file after importing. If not specified, the uploaded file name will be used. (string)
+    * **urlToIngest**: The URL to import the file from. This parameter is used if not file is uploaded with this post. (string)
+    * **fileToUpload**: The file object to import. (void)
+    * **notifications**: A list of notification objects to apply to the transfer.  (FileNotificationRequest)
+
+
+Response:
+---------
+    * *A single RemoteFile object*
+
+**RemoteFile schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/RemoteFile.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "format": {
+          "description": "The file type of the file.", 
+          "type": "string"
+        }, 
+        "lastModified": {
+          "description": "The date this file was last modified in ISO 8601 format.", 
+          "type": "string"
+        }, 
+        "length": {
+          "description": "The length of the file/folder.", 
+          "type": "integer"
+        }, 
+        "mimeType": {
+          "description": "The mime type of the file/folder. If unknown, it defaults to application/binary.", 
+          "type": "string"
+        }, 
+        "name": {
+          "description": "The name of the file/folder.", 
+          "type": "string"
+        }, 
+        "path": {
+          "description": "The absolute path to the file/folder.", 
+          "type": "string"
+        }, 
+        "permissions": {
+          "description": "The system permission of the invoking user on the file/folder.", 
+          "type": "string"
+        }, 
+        "system": {
+          "description": "The systemId of the system where this file lives.", 
+          "type": "string"
+        }, 
+        "type": {
+          "description": "Whether it is a file or folder.", 
+          "type": "string"
+        }
+      }, 
+      "required": [], 
+      "title": "AgavePy RemoteFile schema", 
+      "type": "object"
+    }
+
+manage: Perform an action on a file or folder.
+==============================================
+``agavepy.files.manage(body, filePath, systemId)``
+
+Parameters:
+-----------
+    * **systemId**: The unique id of the system on which the data resides. (string)
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+    * **body**: The operation to perform.  (JSON, FileOperationRequest)
+
+
+**FileOperationRequest schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/FileOperationRequest.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "action": {
+          "description": "Action to perform on the file or folder.", 
+          "enum": [
+            "mkdir", 
+            "rename", 
+            "copy", 
+            "move"
+          ], 
+          "type": "string"
+        }, 
+        "path": {
+          "description": "Destination file or folder.", 
+          "type": "string"
+        }
+      }, 
+      "required": [
+        "action"
+      ], 
+      "title": "AgavePy FileOperationRequest schema", 
+      "type": "object"
+    }
+
+Response:
+---------
+    * *String*
+
+listOnDefaultSystem: Get a remote directory listing.
+====================================================
+``agavepy.files.listOnDefaultSystem(filePath, limit=250, offset=0)``
+
+Parameters:
+-----------
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+    * **limit**: The max number of results. (integer)
+    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
+
+
+Response:
+---------
+    * *Array of RemoteFile objects*
+
+**RemoteFile schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/RemoteFile.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "format": {
+          "description": "The file type of the file.", 
+          "type": "string"
+        }, 
+        "lastModified": {
+          "description": "The date this file was last modified in ISO 8601 format.", 
+          "type": "string"
+        }, 
+        "length": {
+          "description": "The length of the file/folder.", 
+          "type": "integer"
+        }, 
+        "mimeType": {
+          "description": "The mime type of the file/folder. If unknown, it defaults to application/binary.", 
+          "type": "string"
+        }, 
+        "name": {
+          "description": "The name of the file/folder.", 
+          "type": "string"
+        }, 
+        "path": {
+          "description": "The absolute path to the file/folder.", 
+          "type": "string"
+        }, 
+        "permissions": {
+          "description": "The system permission of the invoking user on the file/folder.", 
+          "type": "string"
+        }, 
+        "system": {
+          "description": "The systemId of the system where this file lives.", 
+          "type": "string"
+        }, 
+        "type": {
+          "description": "Whether it is a file or folder.", 
+          "type": "string"
+        }
+      }, 
+      "required": [], 
+      "title": "AgavePy RemoteFile schema", 
+      "type": "object"
+    }
+
+list: Get a remote directory listing on a specific system.
+==========================================================
+``agavepy.files.list(filePath, systemId, limit=250, offset=0)``
+
+Parameters:
+-----------
+    * **systemId**: The unique id of the system on which the data resides. (string)
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+    * **limit**: The max number of results. (integer)
+    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
+
+
+Response:
+---------
+    * *Array of RemoteFile objects*
+
+**RemoteFile schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/RemoteFile.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "format": {
+          "description": "The file type of the file.", 
+          "type": "string"
+        }, 
+        "lastModified": {
+          "description": "The date this file was last modified in ISO 8601 format.", 
+          "type": "string"
+        }, 
+        "length": {
+          "description": "The length of the file/folder.", 
+          "type": "integer"
+        }, 
+        "mimeType": {
+          "description": "The mime type of the file/folder. If unknown, it defaults to application/binary.", 
+          "type": "string"
+        }, 
+        "name": {
+          "description": "The name of the file/folder.", 
+          "type": "string"
+        }, 
+        "path": {
+          "description": "The absolute path to the file/folder.", 
+          "type": "string"
+        }, 
+        "permissions": {
+          "description": "The system permission of the invoking user on the file/folder.", 
+          "type": "string"
+        }, 
+        "system": {
+          "description": "The systemId of the system where this file lives.", 
+          "type": "string"
+        }, 
+        "type": {
+          "description": "Whether it is a file or folder.", 
+          "type": "string"
+        }
+      }, 
+      "required": [], 
+      "title": "AgavePy RemoteFile schema", 
+      "type": "object"
+    }
+
+getHistoryOnDefaultSystem: Download a file from the user's default storage location.
+====================================================================================
+``agavepy.files.getHistoryOnDefaultSystem(filePath, limit=250, offset=0)``
+
+Parameters:
+-----------
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+    * **limit**: The max number of results. (integer)
+    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
+
+
+Response:
+---------
+    * *Array of FileHistory objects*
+
+**FileHistory schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/FileHistory.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "created": {
+          "description": "The date of the event.", 
+          "type": "string"
+        }, 
+        "description": {
+          "description": "A brief description of the event details.", 
+          "type": "String"
+        }, 
+        "status": {
+          "description": "The status of the file/folder after this event.", 
+          "type": "String"
+        }
+      }, 
+      "required": [], 
+      "title": "AgavePy FileHistory schema", 
+      "type": "object"
+    }
+
+getHistory: Return history of api actions.
+==========================================
+``agavepy.files.getHistory(filePath, systemId, limit=250, offset=0)``
+
+Parameters:
+-----------
+    * **systemId**: The unique id of the system on which the data resides. (string)
+    * **filePath**: The path of the file relative to the given system root location. (string)
+    * **limit**: The max number of results. (integer)
+    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
+
+
+Response:
+---------
+    * *Array of FileHistory objects*
+
+**FileHistory schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/FileHistory.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "created": {
+          "description": "The date of the event.", 
+          "type": "string"
+        }, 
+        "description": {
+          "description": "A brief description of the event details.", 
+          "type": "String"
+        }, 
+        "status": {
+          "description": "The status of the file/folder after this event.", 
+          "type": "String"
+        }
+      }, 
+      "required": [], 
+      "title": "AgavePy FileHistory schema", 
+      "type": "object"
+    }
+
+listPermissionsOnDefaultSystem: List all the share permissions for a file or folder.
+====================================================================================
+``agavepy.files.listPermissionsOnDefaultSystem(filePath, limit=250, offset=0)``
+
+Parameters:
+-----------
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+    * **limit**: The max number of results. (integer)
+    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
+
+
+Response:
+---------
+    * *Array of FilePermission objects*
+
+**FilePermission schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/FilePermission.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "name": {
+          "description": "The name of the file/folder.", 
+          "type": "string"
+        }, 
+        "owner": {
+          "description": "Local username of the owner.", 
+          "type": "string"
+        }, 
+        "permissions": {
+          "description": "One or more permission objects", 
+          "type": "array"
+        }
+      }, 
+      "required": [], 
+      "title": "AgavePy FilePermission schema", 
+      "type": "object"
+    }
+
+updatePermissionsOnDefaultSystem: Update permissions for a single user.
+=======================================================================
+``agavepy.files.updatePermissionsOnDefaultSystem(body, filePath)``
+
+Parameters:
+-----------
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+    * **body**: The permission add or update.  (JSON, FilePermissionRequest)
+
+
+**FilePermissionRequest schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/FilePermissionRequest.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "permission": {
+          "description": "The permission to set", 
+          "enum": [
+            "READ", 
+            "WRITE", 
+            "EXECUTE", 
+            "READ_WRITE", 
+            "READ_EXECUTE", 
+            "WRITE_EXECUTE", 
+            "ALL", 
+            "NONE"
+          ], 
+          "type": "string"
+        }, 
+        "recursive": {
+          "description": "Should updated permissions be applied recursively. Defaults to false.", 
+          "type": "boolean"
+        }, 
+        "username": {
+          "description": "The username of the api user whose permission is to be set.", 
+          "type": "string"
+        }
+      }, 
+      "required": [
+        "username", 
+        "permission"
+      ], 
+      "title": "AgavePy FilePermissionRequest schema", 
+      "type": "object"
+    }
+
+Response:
+---------
+    * *String*
+
+deletePermissions: Deletes all permissions on a file except those of the owner.
+===============================================================================
+``agavepy.files.deletePermissions(filePath, systemId)``
+
+Parameters:
+-----------
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+    * **systemId**: The unique id of the system on which the data resides. (string)
+
+
+Response:
+---------
+    * *String*
+
+listPermissions: List all the share permissions for a file or folder.
+=====================================================================
+``agavepy.files.listPermissions(filePath, systemId, limit=250, offset=0)``
+
+Parameters:
+-----------
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+    * **limit**: The max number of results. (integer)
+    * **systemId**: The unique id of the system on which the data resides. (string)
+    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
+
+
+Response:
+---------
+    * *Array of FilePermission objects*
+
+**FilePermission schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/FilePermission.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "name": {
+          "description": "The name of the file/folder.", 
+          "type": "string"
+        }, 
+        "owner": {
+          "description": "Local username of the owner.", 
+          "type": "string"
+        }, 
+        "permissions": {
+          "description": "One or more permission objects", 
+          "type": "array"
+        }
+      }, 
+      "required": [], 
+      "title": "AgavePy FilePermission schema", 
+      "type": "object"
+    }
+
+updatePermissions: Update permissions for a single user.
+========================================================
+``agavepy.files.updatePermissions(body, filePath, systemId)``
+
+Parameters:
+-----------
+    * **filePath**: The path of the file relative to the user's default storage location. (string)
+    * **systemId**: The unique id of the system on which the data resides. (string)
+    * **body**: The permission add or update.  (JSON, FilePermissionRequest)
+
+
+**FilePermissionRequest schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/FilePermissionRequest.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "permission": {
+          "description": "The permission to set", 
+          "enum": [
+            "READ", 
+            "WRITE", 
+            "EXECUTE", 
+            "READ_WRITE", 
+            "READ_EXECUTE", 
+            "WRITE_EXECUTE", 
+            "ALL", 
+            "NONE"
+          ], 
+          "type": "string"
+        }, 
+        "recursive": {
+          "description": "Should updated permissions be applied recursively. Defaults to false.", 
+          "type": "boolean"
+        }, 
+        "username": {
+          "description": "The username of the api user whose permission is to be set.", 
+          "type": "string"
+        }
+      }, 
+      "required": [
+        "username", 
+        "permission"
+      ], 
+      "title": "AgavePy FilePermissionRequest schema", 
+      "type": "object"
+    }
+
+Response:
+---------
+    * *Array of FilePermission objects*
+
+**FilePermission schema**
+
+.. code-block:: javascript
+
+    {
+      "$id": "http://agavepy.readthedocs.io/en/latest/FilePermission.json", 
+      "$schema": "http://json-schema.org/draft-07/schema#", 
+      "properties": {
+        "name": {
+          "description": "The name of the file/folder.", 
+          "type": "string"
+        }, 
+        "owner": {
+          "description": "Local username of the owner.", 
+          "type": "string"
+        }, 
+        "permissions": {
+          "description": "One or more permission objects", 
+          "type": "array"
+        }
+      }, 
+      "required": [], 
+      "title": "AgavePy FilePermission schema", 
+      "type": "object"
+    }
+
diff --git a/docs/docsite/files/files.rst b/docs/docsite/files/files.rst
index 096dbf1..a598f43 100644
--- a/docs/docsite/files/files.rst
+++ b/docs/docsite/files/files.rst
@@ -1,731 +1,21 @@
-*****
-Files
-*****
+.. _files:
 
-Summary: Move and manage data
+##################
+Working with files
+##################
 
-deleteFromDefaultSystem: Deletes a file or folder.
-==================================================
-``agavepy.files.deleteFromDefaultSystem(sourcefilePath)``
+If you have already initiated a session, `authentication`, and have a storage
+system setup then you start working with files.
 
-Parameters:
------------
-    * **sourcefilePath**: The path of the file relative to the user's default storage location. (string)
+Downloading a file
+##################
 
+Let's say you have a file name ``SP1.fq`` on your remote storage system which
+is called ``tacc-globalfs-username``.
 
-Response:
----------
-    * *String*
+You can download the file ``SP1.fq`` to your host and specify the location of
+where you want to store the file (i.e., ``/opt/data/SP1.fq``) as such:
 
-downloadFromDefaultSystem: Download a file from the user's default storage location.
-====================================================================================
-``agavepy.files.downloadFromDefaultSystem(sourcefilePath)``
-
-Parameters:
------------
-    * **sourcefilePath**: The path of the file relative to the user's default storage location. (string)
-
-
-Response:
----------
-    * *None*
-
-importToDefaultSystem: Import a file via direct upload or importing from a url to the user's default storage location.
-======================================================================================================================
-``agavepy.files.importToDefaultSystem(sourcefilePath, callbackURL=None, fileName=None, fileToUpload=None, fileType=None, urlToIngest=None)``
-
-Parameters:
------------
-    * **sourcefilePath**: The path of the file relative to the user's default storage location. (string)
-    * **fileType**: The file format this file is in. Defaults to raw. This will be used in file transform operations. (string)
-    * **callbackURL**: The URI to notify when the import is complete. This can be an email address or http URL. If a URL is given, a GET will be made to this address. URL templating is supported. Valid template values are: ${NAME}, ${SOURCE_FORMAT}, ${DEST_FORMAT}, ${STATUS} (string)
-    * **fileName**: The name of the file after importing. If not specified, the uploaded file name will be used. (string)
-    * **urlToIngest**: The URL to import the file from. This parameter is used if not file is uploaded with this post. (string)
-    * **fileToUpload**: The file object to import. (void)
-
-
-Response:
----------
-    * *A single RemoteFile object*
-
-**RemoteFile schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/RemoteFile.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "format": {
-          "description": "The file type of the file.", 
-          "type": "string"
-        }, 
-        "lastModified": {
-          "description": "The date this file was last modified in ISO 8601 format.", 
-          "type": "string"
-        }, 
-        "length": {
-          "description": "The length of the file/folder.", 
-          "type": "integer"
-        }, 
-        "mimeType": {
-          "description": "The mime type of the file/folder. If unknown, it defaults to application/binary.", 
-          "type": "string"
-        }, 
-        "name": {
-          "description": "The name of the file/folder.", 
-          "type": "string"
-        }, 
-        "path": {
-          "description": "The absolute path to the file/folder.", 
-          "type": "string"
-        }, 
-        "permissions": {
-          "description": "The system permission of the invoking user on the file/folder.", 
-          "type": "string"
-        }, 
-        "system": {
-          "description": "The systemId of the system where this file lives.", 
-          "type": "string"
-        }, 
-        "type": {
-          "description": "Whether it is a file or folder.", 
-          "type": "string"
-        }
-      }, 
-      "required": [], 
-      "title": "AgavePy RemoteFile schema", 
-      "type": "object"
-    }
-
-manageOnDefaultSystem: Perform an action on a file or folder.
-=============================================================
-``agavepy.files.manageOnDefaultSystem(body, sourcefilePath)``
-
-Parameters:
------------
-    * **sourcefilePath**: The path of the file relative to the user's default storage location. (string)
-    * **body**: The operation to perform.  (JSON, FileOperationRequest)
-
-
-**FileOperationRequest schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/FileOperationRequest.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "action": {
-          "description": "Action to perform on the file or folder.", 
-          "enum": [
-            "mkdir", 
-            "rename", 
-            "copy", 
-            "move"
-          ], 
-          "type": "string"
-        }, 
-        "path": {
-          "description": "Destination file or folder.", 
-          "type": "string"
-        }
-      }, 
-      "required": [
-        "action"
-      ], 
-      "title": "AgavePy FileOperationRequest schema", 
-      "type": "object"
-    }
-
-Response:
----------
-    * *String*
-
-delete: Deletes a file or folder.
-=================================
-``agavepy.files.delete(filePath, systemId)``
-
-Parameters:
------------
-    * **systemId**: The unique id of the system on which the data resides. (string)
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-
-
-Response:
----------
-    * *String*
-
-download: Download a file from the user's default storage location.
-===================================================================
-``agavepy.files.download(filePath, systemId)``
-
-Parameters:
------------
-    * **systemId**: The unique id of the system on which the data resides. (string)
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-
-
-Response:
----------
-    * *None*
-
-importData: Import a file via direct upload or importing from a url to the user's default storage location.
-===========================================================================================================
-``agavepy.files.importData(filePath, systemId, callbackURL=None, fileName=None, fileToUpload=None, fileType=None, notifications=[], urlToIngest=None)``
-
-Parameters:
------------
-    * **systemId**: The unique id of the system on which the data resides. (string)
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-    * **fileType**: The file format this file is in. Defaults to raw. This will be used in file transform operations. (string)
-    * **callbackURL**: The URI to notify when the import is complete. This can be an email address or http URL. If a URL is given, a GET will be made to this address. URL templating is supported. Valid template values are: ${NAME}, ${SOURCE_FORMAT}, ${DEST_FORMAT}, ${STATUS} (string)
-    * **fileName**: The name of the file after importing. If not specified, the uploaded file name will be used. (string)
-    * **urlToIngest**: The URL to import the file from. This parameter is used if not file is uploaded with this post. (string)
-    * **fileToUpload**: The file object to import. (void)
-    * **notifications**: A list of notification objects to apply to the transfer.  (FileNotificationRequest)
-
-
-Response:
----------
-    * *A single RemoteFile object*
-
-**RemoteFile schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/RemoteFile.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "format": {
-          "description": "The file type of the file.", 
-          "type": "string"
-        }, 
-        "lastModified": {
-          "description": "The date this file was last modified in ISO 8601 format.", 
-          "type": "string"
-        }, 
-        "length": {
-          "description": "The length of the file/folder.", 
-          "type": "integer"
-        }, 
-        "mimeType": {
-          "description": "The mime type of the file/folder. If unknown, it defaults to application/binary.", 
-          "type": "string"
-        }, 
-        "name": {
-          "description": "The name of the file/folder.", 
-          "type": "string"
-        }, 
-        "path": {
-          "description": "The absolute path to the file/folder.", 
-          "type": "string"
-        }, 
-        "permissions": {
-          "description": "The system permission of the invoking user on the file/folder.", 
-          "type": "string"
-        }, 
-        "system": {
-          "description": "The systemId of the system where this file lives.", 
-          "type": "string"
-        }, 
-        "type": {
-          "description": "Whether it is a file or folder.", 
-          "type": "string"
-        }
-      }, 
-      "required": [], 
-      "title": "AgavePy RemoteFile schema", 
-      "type": "object"
-    }
-
-manage: Perform an action on a file or folder.
-==============================================
-``agavepy.files.manage(body, filePath, systemId)``
-
-Parameters:
------------
-    * **systemId**: The unique id of the system on which the data resides. (string)
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-    * **body**: The operation to perform.  (JSON, FileOperationRequest)
-
-
-**FileOperationRequest schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/FileOperationRequest.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "action": {
-          "description": "Action to perform on the file or folder.", 
-          "enum": [
-            "mkdir", 
-            "rename", 
-            "copy", 
-            "move"
-          ], 
-          "type": "string"
-        }, 
-        "path": {
-          "description": "Destination file or folder.", 
-          "type": "string"
-        }
-      }, 
-      "required": [
-        "action"
-      ], 
-      "title": "AgavePy FileOperationRequest schema", 
-      "type": "object"
-    }
-
-Response:
----------
-    * *String*
-
-listOnDefaultSystem: Get a remote directory listing.
-====================================================
-``agavepy.files.listOnDefaultSystem(filePath, limit=250, offset=0)``
-
-Parameters:
------------
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-    * **limit**: The max number of results. (integer)
-    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
-
-
-Response:
----------
-    * *Array of RemoteFile objects*
-
-**RemoteFile schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/RemoteFile.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "format": {
-          "description": "The file type of the file.", 
-          "type": "string"
-        }, 
-        "lastModified": {
-          "description": "The date this file was last modified in ISO 8601 format.", 
-          "type": "string"
-        }, 
-        "length": {
-          "description": "The length of the file/folder.", 
-          "type": "integer"
-        }, 
-        "mimeType": {
-          "description": "The mime type of the file/folder. If unknown, it defaults to application/binary.", 
-          "type": "string"
-        }, 
-        "name": {
-          "description": "The name of the file/folder.", 
-          "type": "string"
-        }, 
-        "path": {
-          "description": "The absolute path to the file/folder.", 
-          "type": "string"
-        }, 
-        "permissions": {
-          "description": "The system permission of the invoking user on the file/folder.", 
-          "type": "string"
-        }, 
-        "system": {
-          "description": "The systemId of the system where this file lives.", 
-          "type": "string"
-        }, 
-        "type": {
-          "description": "Whether it is a file or folder.", 
-          "type": "string"
-        }
-      }, 
-      "required": [], 
-      "title": "AgavePy RemoteFile schema", 
-      "type": "object"
-    }
-
-list: Get a remote directory listing on a specific system.
-==========================================================
-``agavepy.files.list(filePath, systemId, limit=250, offset=0)``
-
-Parameters:
------------
-    * **systemId**: The unique id of the system on which the data resides. (string)
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-    * **limit**: The max number of results. (integer)
-    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
-
-
-Response:
----------
-    * *Array of RemoteFile objects*
-
-**RemoteFile schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/RemoteFile.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "format": {
-          "description": "The file type of the file.", 
-          "type": "string"
-        }, 
-        "lastModified": {
-          "description": "The date this file was last modified in ISO 8601 format.", 
-          "type": "string"
-        }, 
-        "length": {
-          "description": "The length of the file/folder.", 
-          "type": "integer"
-        }, 
-        "mimeType": {
-          "description": "The mime type of the file/folder. If unknown, it defaults to application/binary.", 
-          "type": "string"
-        }, 
-        "name": {
-          "description": "The name of the file/folder.", 
-          "type": "string"
-        }, 
-        "path": {
-          "description": "The absolute path to the file/folder.", 
-          "type": "string"
-        }, 
-        "permissions": {
-          "description": "The system permission of the invoking user on the file/folder.", 
-          "type": "string"
-        }, 
-        "system": {
-          "description": "The systemId of the system where this file lives.", 
-          "type": "string"
-        }, 
-        "type": {
-          "description": "Whether it is a file or folder.", 
-          "type": "string"
-        }
-      }, 
-      "required": [], 
-      "title": "AgavePy RemoteFile schema", 
-      "type": "object"
-    }
-
-getHistoryOnDefaultSystem: Download a file from the user's default storage location.
-====================================================================================
-``agavepy.files.getHistoryOnDefaultSystem(filePath, limit=250, offset=0)``
-
-Parameters:
------------
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-    * **limit**: The max number of results. (integer)
-    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
-
-
-Response:
----------
-    * *Array of FileHistory objects*
-
-**FileHistory schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/FileHistory.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "created": {
-          "description": "The date of the event.", 
-          "type": "string"
-        }, 
-        "description": {
-          "description": "A brief description of the event details.", 
-          "type": "String"
-        }, 
-        "status": {
-          "description": "The status of the file/folder after this event.", 
-          "type": "String"
-        }
-      }, 
-      "required": [], 
-      "title": "AgavePy FileHistory schema", 
-      "type": "object"
-    }
-
-getHistory: Return history of api actions.
-==========================================
-``agavepy.files.getHistory(filePath, systemId, limit=250, offset=0)``
-
-Parameters:
------------
-    * **systemId**: The unique id of the system on which the data resides. (string)
-    * **filePath**: The path of the file relative to the given system root location. (string)
-    * **limit**: The max number of results. (integer)
-    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
-
-
-Response:
----------
-    * *Array of FileHistory objects*
-
-**FileHistory schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/FileHistory.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "created": {
-          "description": "The date of the event.", 
-          "type": "string"
-        }, 
-        "description": {
-          "description": "A brief description of the event details.", 
-          "type": "String"
-        }, 
-        "status": {
-          "description": "The status of the file/folder after this event.", 
-          "type": "String"
-        }
-      }, 
-      "required": [], 
-      "title": "AgavePy FileHistory schema", 
-      "type": "object"
-    }
-
-listPermissionsOnDefaultSystem: List all the share permissions for a file or folder.
-====================================================================================
-``agavepy.files.listPermissionsOnDefaultSystem(filePath, limit=250, offset=0)``
-
-Parameters:
------------
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-    * **limit**: The max number of results. (integer)
-    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
-
-
-Response:
----------
-    * *Array of FilePermission objects*
-
-**FilePermission schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/FilePermission.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "name": {
-          "description": "The name of the file/folder.", 
-          "type": "string"
-        }, 
-        "owner": {
-          "description": "Local username of the owner.", 
-          "type": "string"
-        }, 
-        "permissions": {
-          "description": "One or more permission objects", 
-          "type": "array"
-        }
-      }, 
-      "required": [], 
-      "title": "AgavePy FilePermission schema", 
-      "type": "object"
-    }
-
-updatePermissionsOnDefaultSystem: Update permissions for a single user.
-=======================================================================
-``agavepy.files.updatePermissionsOnDefaultSystem(body, filePath)``
-
-Parameters:
------------
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-    * **body**: The permission add or update.  (JSON, FilePermissionRequest)
-
-
-**FilePermissionRequest schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/FilePermissionRequest.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "permission": {
-          "description": "The permission to set", 
-          "enum": [
-            "READ", 
-            "WRITE", 
-            "EXECUTE", 
-            "READ_WRITE", 
-            "READ_EXECUTE", 
-            "WRITE_EXECUTE", 
-            "ALL", 
-            "NONE"
-          ], 
-          "type": "string"
-        }, 
-        "recursive": {
-          "description": "Should updated permissions be applied recursively. Defaults to false.", 
-          "type": "boolean"
-        }, 
-        "username": {
-          "description": "The username of the api user whose permission is to be set.", 
-          "type": "string"
-        }
-      }, 
-      "required": [
-        "username", 
-        "permission"
-      ], 
-      "title": "AgavePy FilePermissionRequest schema", 
-      "type": "object"
-    }
-
-Response:
----------
-    * *String*
-
-deletePermissions: Deletes all permissions on a file except those of the owner.
-===============================================================================
-``agavepy.files.deletePermissions(filePath, systemId)``
-
-Parameters:
------------
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-    * **systemId**: The unique id of the system on which the data resides. (string)
-
-
-Response:
----------
-    * *String*
-
-listPermissions: List all the share permissions for a file or folder.
-=====================================================================
-``agavepy.files.listPermissions(filePath, systemId, limit=250, offset=0)``
-
-Parameters:
------------
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-    * **limit**: The max number of results. (integer)
-    * **systemId**: The unique id of the system on which the data resides. (string)
-    * **offset**: The number of records to when returning the results. When paginating results, the page number = ceil(offset/limit) (integer)
-
-
-Response:
----------
-    * *Array of FilePermission objects*
-
-**FilePermission schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/FilePermission.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "name": {
-          "description": "The name of the file/folder.", 
-          "type": "string"
-        }, 
-        "owner": {
-          "description": "Local username of the owner.", 
-          "type": "string"
-        }, 
-        "permissions": {
-          "description": "One or more permission objects", 
-          "type": "array"
-        }
-      }, 
-      "required": [], 
-      "title": "AgavePy FilePermission schema", 
-      "type": "object"
-    }
-
-updatePermissions: Update permissions for a single user.
-========================================================
-``agavepy.files.updatePermissions(body, filePath, systemId)``
-
-Parameters:
------------
-    * **filePath**: The path of the file relative to the user's default storage location. (string)
-    * **systemId**: The unique id of the system on which the data resides. (string)
-    * **body**: The permission add or update.  (JSON, FilePermissionRequest)
-
-
-**FilePermissionRequest schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/FilePermissionRequest.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "permission": {
-          "description": "The permission to set", 
-          "enum": [
-            "READ", 
-            "WRITE", 
-            "EXECUTE", 
-            "READ_WRITE", 
-            "READ_EXECUTE", 
-            "WRITE_EXECUTE", 
-            "ALL", 
-            "NONE"
-          ], 
-          "type": "string"
-        }, 
-        "recursive": {
-          "description": "Should updated permissions be applied recursively. Defaults to false.", 
-          "type": "boolean"
-        }, 
-        "username": {
-          "description": "The username of the api user whose permission is to be set.", 
-          "type": "string"
-        }
-      }, 
-      "required": [
-        "username", 
-        "permission"
-      ], 
-      "title": "AgavePy FilePermissionRequest schema", 
-      "type": "object"
-    }
-
-Response:
----------
-    * *Array of FilePermission objects*
-
-**FilePermission schema**
-
-.. code-block:: javascript
-
-    {
-      "$id": "http://agavepy.readthedocs.io/en/latest/FilePermission.json", 
-      "$schema": "http://json-schema.org/draft-07/schema#", 
-      "properties": {
-        "name": {
-          "description": "The name of the file/folder.", 
-          "type": "string"
-        }, 
-        "owner": {
-          "description": "Local username of the owner.", 
-          "type": "string"
-        }, 
-        "permissions": {
-          "description": "One or more permission objects", 
-          "type": "array"
-        }
-      }, 
-      "required": [], 
-      "title": "AgavePy FilePermission schema", 
-      "type": "object"
-    }
+.. code-block:: pycon
 
+    >>> agave.files_download("tacc-globalfs-username/SP1.fq", "/opt/data/SP1.fq")
diff --git a/docs/docsite/files/index.rst b/docs/docsite/files/index.rst
index 108df29..b7aba32 100644
--- a/docs/docsite/files/index.rst
+++ b/docs/docsite/files/index.rst
@@ -11,6 +11,7 @@ to use, to share with collaborators, or to setup for long term storage.
    :maxdepth: 2                                                                 
 
    files
+   files-listing
 
 .. only::  subproject and html                                                  
                                                                                 
diff --git a/docs/docsite/installation_guide/installation.rst b/docs/docsite/installation_guide/installation.rst
index 6ddc31c..cd2ef92 100644
--- a/docs/docsite/installation_guide/installation.rst
+++ b/docs/docsite/installation_guide/installation.rst
@@ -11,7 +11,7 @@ Installation Guide
 What will be installed
 ``````````````````````
 
-In this guide wewill cover how to get ``AgavePy`` into your machine.
+In this guide we will cover how to get ``AgavePy`` into your machine.
 We will cover how to install the latest official release.
 Also, we will cover how to install the latest from the development branch in 
 case you want to get the latest features and help the TACC team build the most 
@@ -22,7 +22,7 @@ useful and performant software that meets your needs.
 What version to install
 ```````````````````````
 ``AgavePy`` is under constant development. 
-Some features may be missing from ofifical releases but in general oficial
+Some features may be missing from oficial releases but in general oficial
 releases are pushed when the project has reached a stable state.
 
 .. _from_pipy:
diff --git a/docs/index.rst b/docs/index.rst
index 2b4b375..1e00d20 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -9,7 +9,7 @@ AgavePy documentation
 About AgavePy
 -------------
 
-AgavePy is a client that will allow you to script your interacttion with the
+AgavePy is a client that will allow you to script your interaction with the
 Agave API.
 AgavePy is also the engine powering 
 `TACC/Cloud/agave-cli <https://github.com/TACC-Cloud/agave-cli>`_.


Test patch:
diff --git a/tests/files_test.py b/tests/files_test.py
new file mode 100644
index 0000000..4c0c2fa
--- /dev/null
+++ b/tests/files_test.py
@@ -0,0 +1,142 @@
+"""
+    files_tests.py
+"""
+import pytest
+import cgi
+import filecmp
+import json
+import mimetypes
+import os
+import shutil
+import tempfile
+import time
+from testsuite_utils import MockServer                                          
+from response_templates import response_template_to_json
+
+try: # python 2
+    from BaseHTTPServer import BaseHTTPRequestHandler
+except ImportError: # python 3
+    from http.server import BaseHTTPRequestHandler
+try:
+    from unittest.mock import patch
+except ImportError:
+    from mock import patch
+
+from agavepy.agave import Agave
+
+
+
+class MockServerFilesEndpoints(BaseHTTPRequestHandler):
+    def send_headers(self):
+        # The path corresponds to the service being queried. For example,
+        # querying "http://localhost:80/f/file.txt" will set the path as
+        # "/f/file.txt". 
+        npath = os.path.normpath(self.path)
+        npath = npath[1:]
+        path_elements = npath.split("/")
+
+        # Check access token.
+        try: # Python 2
+            valid_access_token = self.headers.getheader("Authorization")
+        except AttributeError: # Python 3
+            valid_access_token = self.headers.get("Authorization")
+        if not valid_access_token:
+            self.send_error(401, "Unauthorized")
+            return None
+
+        # Send headers for /files/v2/media/system requests.
+        if "/files/v2/media/system" in self.path: 
+            self.send_response(200)
+            self.send_header("Content-Type", "text/json; character=utf-8")
+            self.end_headers()
+        else:
+            self.send_error(404, "fuck")
+            return None
+    
+        return path_elements
+
+
+    def do_GET(self):
+        """ Get a file
+
+        This route responds with the contents of a file. The file is expected
+        to be in the current working directory and is expected to be created by
+        the test setup.
+        """
+        # elements is a list of path elements, i.e., "/a/b" == ["a", "b"].
+        elements = self.send_headers()
+        if elements is None or not "/files/v2/media/system" in self.path:
+            return
+
+        # Send contents of file to wfile object.
+        filename = elements[-1]
+        if "?pretty=true" in filename: filename = filename[:-len("?pretty=true")]
+        with open(filename, "rb") as f:
+            shutil.copyfileobj(f, self.wfile)
+
+
+
+
+
+class TestMockServer(MockServer):
+    """ Test client-related agave api endpoints
+
+    Tests client creation (HTTP POST request), removal (HTTP DELETE request),
+    and listing (HTTP GET request).
+    """
+
+    @classmethod
+    def setup_class(cls):
+        """ Set up an agave mock server
+
+        Listen and serve mock api as a daemon.
+        """
+        MockServer.serve.__func__(cls, MockServerFilesEndpoints)
+
+    def create_local_dummy_file(self):
+        """ Create a dummy file for testing
+
+        Create a dummy file to test file copying with the cli. Emulate a file
+        on a local system.
+        """
+        _, file_path = tempfile.mkstemp()
+        try:
+            with open(file_path, "w") as tmp:
+                tmp.write("this is a test file\n")
+        except:
+            os.remove(file_path)
+
+        return file_path
+
+    def create_remote_dummy_file(self):
+        """ Create a dummy file for testing
+
+        Create a dummy file to test file copying with the cli. Emulate a file
+        on a remote agave system.
+        """
+        tmp_filename = "thisisatest.txt"
+        with open(tmp_filename, "w") as tmp:
+            tmp.write("this emulates a file on a remote system\n")
+        return tmp_filename
+
+    def test_files_download(self):
+        """ Test file copying from a remote to the local system
+        """
+        # Create a "remote" dummy file.
+        tmp_file = self.create_remote_dummy_file()
+
+        # Destination of downloaded file.
+        local_file = "testfile.txt"
+
+        try:
+            local_uri = "http://localhost:{port}/".format(port=self.mock_server_port)
+            agave = Agave(api_server=local_uri)
+            agave.token = "mock-access-token"
+
+            agave.files_download("tacc-globalfs-user/"+tmp_file, local_file)
+        finally:
+            assert filecmp.cmp(tmp_file, local_file)
+
+            # rm dummy file in current working directory.
+            if os.path.exists(tmp_file):
+                os.remove(tmp_file)